Skip to content

競技プログラミング関連はここにまとめます. Atcoder, Yukicoder, Codeforceの問題をつらつらといています.そのうちブログに解説みたいなの書きたい.

Notifications You must be signed in to change notification settings

MikamiTetsuro365/Atcoder

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Atcoder

Atcoder関連はここにまとめます(精進).

以下のライブラリとかは課題が山積みなので自分以外がコピペして使うの非推奨です(糞)

便利リンク

テンプレ

bitsフォルダをコピペして以下を貼り付け

#include "bits/stdc++.h"

using namespace std;
typedef long long int ll;
typedef pair<ll, ll > pi;  
typedef pair<pair<ll, ll >, ll > pii;  
vector<ll > vec;
vector<vector<ll > > vec2;
ll MOD = 1000000007;
ll INF = 11451419194545;

int main(){

}

多次元ベクター(下は二次元)

ll n_rows = 5;
ll n_cols = 5;
ll value = 0;
vector<vector<ll > > vec(n_rows, vector<ll >(n_cols, value));

map全列挙

map<ll, ll> mp;
//ここになにか入力
auto begin = mp.begin(), end = mp.end();
for (auto iter = begin; iter != end; iter++) {
    cout << iter -> first << " " <<  iter -> second << endl;
}

整数と小数の判定

クソどうでも良いけど...ceil(切り上げ)とfloor(切り捨て)で同じだと整数(少数ではない)

double ans;
cin >> ans;
//cout << ans << endl;
if(ceil(ans) != floor(ans)){
    cout << "No" << endl;
}else{
    cout << "Yes" << endl;
}

複数(3つ以上)の変数を減らして考える

式変形の発想が重要 AGC024 ABC006

8近傍(2個飛ばしで4近傍)

ll dy[8] = {1, 1, 1, 0, -1, -1, -1, 0};
ll dx[8] = {-1, 0, 1, 1, 1, 0, -1, -1};

Priority queue(優先度付きQueue)

priority_queue<ll > que;
//昇順にしたい時
//priority_queue<ll ,vector<ll >, greater<ll > > que;

que.push(1);
que.push(2);
que.push(3);

while(!que.empty()){
    cout << que.top() << endl;
    que.pop()
}

最大公倍数

N個の最大公倍数=最大公倍数の最大公倍数の最大公倍数(ry

ll gcd(ll a, ll b){return b?gcd(b,a%b):a;}
ll gcd(ll a, ll b){
    if(b == 0){
        return a;
    }
    return gcd(b, a % b);
}

どちゃくそくそバカでかい数字同士のGCDを取れと言われたら?多言語(Pythonとか)を使うか,考察がんばるか...No442和と積

最小公倍数

ll lcm(ll a, ll b){
    return a / gcd(a, b) * b;
}

立っているビットをカウント

GCCの__builtin関数ここ

ll n;
cin >> n;
ll bit_count = __builtin_popcount(n);

ビット全探査

全探査が楽(ほんまか?)

ll N; cin >> N;
for(ll i = 0; i <= (1<<N); i++){
    //ビットの位置を数える
    for(ll j = 0; j < N; j++){
        //右シフトして最下位で立っているかどうか判定
        if((i >> j) & 1 == 1){
            //jがビットが立っている位置
        }
    }
}

約数

約数を返します

vector<ll > divisor(ll n) {
  vector< ll > div;
  for(ll i = 1; i * i <= n; i++) {
    if(n % i == 0) {
      div.push_back(i);
      //重複許すマジ
      if(i * i != n) div.push_back(n / i);
    }
  }
  sort(div.begin(), div.end());
  return (div);
}

直線交差判定

役に立つときがくるかは...

bool check(ll x1, ll x2, ll y1, ll y2, ll tx1, ll tx2, ll ty1, ll ty2){
    ll ta = (x1-x2)*(ty1-y1)+(y1-y2)*(x1-tx1);
    ll tb = (x1-x2)*(ty2-y1)+(y1-y2)*(x1-tx2);

    if(ta * tb < 0){
        //交差してる
        return true;
    }else{
        //交差してない
        return false;
    }
}

木やグラフ

辺に重みも設定できます

//to (行き先), weight の順で設定
vector<vector<pair<ll, ll > > > G;
vector<ll > ans;
int main(){
    ll N;
    cin >> N;
    
    //初期化
    G.assign(N, vector<pair<ll, ll > >());
    ans.assign(N, 0);
 
    //グラフ入力
    for(ll i = 0; i < N-1; i++){
        ll u, v, w;
        cin >> u >> v >> w;
        u--;
        v--;
        //双方向に貼りましょうね
        G[u].push_back(make_pair(v, w));
        G[v].push_back(make_pair(u, w));
    }
}

dfs

グラフや木を作って実行してくれい.ABC070_D ABC_067_DABC054_C←忘れた頃にもう一度解け

#include "bits/stdc++.h"

using namespace std;
typedef long long int ll;
typedef pair<ll, ll > pi;  
vector<vector<pi > > G;
vector<bool > seen;
ll N, M;

void dfs(ll idx, ll from = -1){
    seen[idx] = true;

    for(ll i = 0; i < G[idx].size(); i++){
        //次の行き先は?
        ll to = G[idx][i].first;
        //探査済み
        if(seen[to] == true){
            continue;
        }
 
        dfs(to , idx);
    }
}

int main(){
    
    cin >> N >> M;
    //初期化
    G.assign(N, vector<pi >());
    seen.assign(N, false);
 
    //グラフ入力
    for(ll i = 0; i < M; i++){
        ll u, v, w;
        w = 0;
        cin >> u >> v;
        u--;
        v--;
        //双方向に貼りましょうね
        G[u].push_back(make_pair(v, w));
        G[v].push_back(make_pair(u, w));
    }

    dfs(0);
}

BFS

辺の重みが1の場合のみお手軽に最短経路を求めることができる

ここ

#include "bits/stdc++.h"

using namespace std;
typedef long long int ll;
typedef pair<ll, ll > pi;  
typedef pair<pair<ll, ll >, ll > pii;  
vector<ll > vec;
vector<vector<ll > > vec2;
ll MOD = 1000000007;

ll ans = INFINITY;
ll N;

vector<ll > bfs(vector<vector<ll > > G, ll start){
    start;
    //距離を初期化
    vector<ll > dist(G.size(), -1);
    queue<ll > que;

    //始点start 初期化
    dist[start] = 0;
    que.push(start);

    while(!que.empty()){
        ll from = que.front();
        que.pop();

        for(ll i = 0; i < G[from].size(); i++){
            ll to = G[from][i];
            //すでに探査済み
            if(dist[to] != -1) continue;

            dist[to] = dist[from] + 1;
            que.push(to);
        }
    }

    return dist;

}
int main(){


    cin >> N >> M;

    vector<vector<ll > > G;
    G.assign(N, vector<ll >());

    for(ll i = 0; i < M; i++){
        //入力
    }

    vector<ll > dist = bfs(G, 0);
    cout << dist[N] << endl;
}

迷路とか(タイピング速度爆速ではないので一々書くのめんどくさいよ...)

#include "bits/stdc++.h"
 
using namespace std;
typedef long long int ll;
typedef pair<ll, ll > pi;  
typedef pair<pair<ll, ll >, ll > pii;  
vector<ll > vec;
vector<vector<ll > > vec2;
ll MOD = 1000000007;
ll INF = 1145141919454519;
ll G[1919][1919];
ll H, W;
ll dx[4] = {1, 0, -1 ,0};
ll dy[4] = {0, 1, 0 ,-1};

ll bfs(ll y, ll x, ll id){
    //準備
    //vector<vector<ll > > dist(N, vector<ll >(M, -1));
    queue<pi> que;
    //初期化
    //dist[x][y] = 0;
    que.push(make_pair(y, x));
    G[y][x] = id;
    
    ll size = 0;

    while(!que.empty()){
        ll from_y = que.front().first;
        ll from_x = que.front().second;
        que.pop();
        size++;

        for(ll i = 0; i < 4; i++){
            if(from_y + dy[i] >= 0 && from_y + dy[i] <= H+19 && from_x + dx[i] >= 0 && from_x + dx[i] <= W+19){
                ll to_y = from_y + dy[i];
                ll to_x = from_x + dx[i];
                //連結された島がある時
                if(G[to_y][to_x] == 1){
                    G[to_y][to_x] = id;
                    que.push(make_pair(to_y, to_x));
                }
            }
        }
    }
    return size;
}

int main() {

    cin >> H >> W;

    for(ll i = 0; i < H; i++){
        string S;
        cin >> S;
        for(ll j = 0; j < W; j++){
            if(S[j] == '#') G[i+19][j+19] = 1;
            else G[i+19][j+19] = 0;
        }
    }
}

とてもわかり易い説明.特に6段目がよさみあふれるここ1 ここ2 ここ3

累積和

基本的なイメージはここよぉpaiza

まとめる(圧縮)処理

たまに数列とかを同じ者同士でまとめる必要があるので

for(ll i = 0; i < s.size();){
    ll j = i;
    //一緒の間ループを回し続けて,切れ目を探す
    while(j < s.size() && s[j] == s[i]){
        ++j;
    }
    vec.push_back(j - i);
    //次の始点へすっ飛ばす
    i = j;
}

累積和のメイン処理

合計を順番にとっていく,leftとrightを操作しながらある区間の合計を取っていく

//先頭は必ず0からスタート
vector<ll > sum(vec.size() + 1, 0);
for(ll i = 0; i < vec.size(); i++){
    //1つ前との合計を取っていく
    sum[i + 1] = sum[i] + vec[i];
}

//累積和メイン
ll ans = 0;
for(ll left = 0; left < sum.size(); left += 2){
    //K * 2 + 1の部分を合計を取りたい任意の区間の要素数に合わせる
    ll right = left + K * 2 + 1;
    //バグらせ防止, rightが伸びきったらもう動かさない終端でleftが上がってくるのを待つ
    if(right >= sum.size()){
        right = sum.size() - 1;
    }
    //sum[right] - sum[left]である区間の合計がわかります.
    ans = max(ans, sum[right] - sum[left]);
}

Binary Indexed Tree

区間和やそれに対して任意の値に更新することがO(lngN)で高速に実行できる

ARC043_B

#include "bits/stdc++.h"

using namespace std;
typedef long long int ll;
typedef pair<ll, ll > pi;  
typedef pair<pair<ll, ll >, ll > pii;  
vector<ll > vec;
vector<vector<ll > > vec2;
ll MOD = 1000000007;
ll INF = 11451419194545;

ll N;
//Binary Indexed Treeの本体
vector<ll > bit;

//要素の追加
void add(ll a, ll w){
    //区間の長さと現在追加しようとしている値の添字を足すと求めることができる
    //区間の長さは区間番号を2進数にした時,最も下位にある立ったビットで分かる
    //x & -xで最も下位の立ったbitが分かる
    for(ll i = a; i <= N; i += i & -i){
        bit[i] += w;
    }
}

//区間1~aの和の計算
ll intervalSum_1_a(ll a){
    //次にどこを足せばよいかは区間番号からその区間番号の長さを引くと求まる
    ll ret = 0;
    for(ll i = a; i > 0; i -= i & -i){
        ret += bit[i];
    }
    return ret;
}

//区間a~bの和の計算
ll intervalSum_a_b(ll a, ll b){
    return intervalSum_1_a(b) - intervalSum_1_a(a-1);
}

//

//転倒数
ll invNum(vector<ll > num){
    //BITに順番に数を追加していく.
    ll ans = 0;
    for(ll i = 0; i < N; i++){
        //数列の値を追加する前
        //num[i]が追加される前,intervalSum(num[i])でそれより小さい数がいくつ追加されているか調べる.
        //iは何個目にnum[i]がBITに追加されるか
        //つまりi - intervalSum(num[i])で実際の数列で左端から
        //数を転倒していったときi個目にいる自分が自身より大きい数と何回すれ違うかを表す.
        ans += i - intervalSum_1_a(num[i]);
        //最後にnum[i]を区間に足しておく.
        add(num[i], 1);
    }
    return ans;
}

//二分探査 累積和がw以上になる最小のx
/*
ll lowerBound(ll w){
    if(w <= 0) return 0;
    ll x = 0;
    ll k = 1;
    while(k * 2 <= N) k *= 2;
    for(ll i = k; i > 0; i /= 2){
        if(x + i <= N && bit[x + i] < w){
            w -= bit[x + i];
            x += i;
        }
    }
    return x + 1;
}
*/

int main() {    
    ll q;
    cin >> N;
    bit.assign(N+1, 0);

    //立っているビットが常に1つある状態にしたいので添字は1からスタート
    //構築
    for(ll i = 1; i <= N; i++){
        ll t = 0; cin >> t;
        add(i, t);
    }

    /*クエリの処理
    vector<ll > ans;
    for(ll i = 0; i < q; i++){
        ll com; cin >> com;
        ll x, y; cin >> x >> y; 
        if(com == 0){
            add(x, y);
        }else{
            ans.push_back(intervalSum(y) - intervalSum(x-1));
            //cout << intervalSum(y) - intervalSum(x-1) << endl;
        }
    }
    */
    
    //転倒数
    /*
    vector<ll > num(N, 0);
    for(ll i = 0; i < N; i++){
        cin >> num[i];
    }
    cout << invNum(num) << endl;
    */
}

尺取り法

例1ARC098_D

#include "bits/stdc++.h"

using namespace std;
typedef long long int ll;
typedef pair<ll, ll > pi;  
typedef pair<pair<ll, ll >, ll > pii;  
vector<ll > vec;
vector<vector<ll > > vec2;
ll MOD = 1000000007;
ll INF = 11451419194545;

int main() {

    ll N; cin >> N;
    vector<ll > A(N, 0);

    for(ll i = 0; i < N; i++){
        cin >> A[i];
    }

    //ll left = 0;
    ll right = 0;
    ll SUM = 0;
    ll XOR = 0;
    ll ans = 0;

    for(ll left = 0; left < N; left++){
        //rightがデータ数の最大を超えず 条件を満たし続ける限り右へ伸ばし続ける
        while(right < N && (SUM + A[right]) == (XOR ^ A[right])){
            SUM += A[right];
            XOR ^= A[right];
            right++;
        }

        //leftからrightまでの条件を満たす個数
        ans += right - left;
        
        //right側が条件を満たさない限りleftを右へ伸ばし続ける
        if(right == left){
            right++;
        }else{
            SUM -= A[left];
            //同じものをXORするともとに戻る
            XOR ^= A[left];
        }
    }

    cout << ans << endl;
}

DP(思い出したときに書き足し)

VectorでDPテーブルを作ってしまうと膨大なメモリ空間にアクセスする必要があるのでTLE必須→Mapで頑張ったARC073_B

要素が2つあったABC054_D

挿入DPCPSCO2019_Session3_F

要復習:木DPABC036_D

取捨選択forでやる(好み)メモ化再帰

要復習:桁DP(使える数字使えない数字があって数を数える)ABC007_D

next_permutation

順列を生成する便利な奴アルゴリズム

この考え方が役に立った問題AGC022_A

ll N;
cin >> N;
//数列入力
vector<ll > A(N, 0);
for(ll i = 0; i < N; i++){
    cin >> A[i];  
}

do{
    for(ll i = 0; i < A.size(); i++){
        cout << A[i];
    }
    cout << endl;
}while(next_permutation(A.begin(), A.end()));

エラトステネスの篩

高速に素数を求めるABC084-D

#include "bits/stdc++.h"
using namespace std;
typedef long long ll;
ll N;

vector<bool > prime;

//primeListには素数は入ってません
void isPrime(ll N){
    prime[0] = false;
    prime[1] = false;

    //ルートN以下まで繰り返す
    for(ll i = 2; i < ceil(sqrt(N)) ; i++){
        //素数になりえないとき
        //cout << i << endl;
        if(!prime[i]) continue;
        
        //Nまでのi(素数)の倍数を斑入り落とす
        for(ll j = i * i; j <= N; j += i){
            prime[j] = false;
        }    
    }
}

int main(){

    cin >> N;
    prime.assign(N + 1, true);
    isPrime(N);

    for(ll i = 0; i < prime.size(); i++){
        cout << prime[i] << endl;
    }
} 

素因数分解

素因数分解の気持ちABC052-C

#include "bits/stdc++.h"

using namespace std;
typedef long long int ll;

map<ll, ll> primeFactor(ll num){

    //素因数と指数部の並び
    map<ll, ll > PF;

    ll i = 2;

    while(num >= i * i){
        if(num % i == 0){
            PF[i]++;
            num /= i;
        }else{
            i++;
        }
    }
    //1は素因数分解できない
    if(num != 1){
        PF[num]++;
    }
    
    return PF;
}

int main(){

    ll num;
    cin >> num;

    map<ll, ll> PF = primeFactor(num);

    auto begin = PF.begin(), end = PF.end();
    for (auto iter = begin; iter != end; iter++) {
        //ここで出力
    }
}

高速な累乗計算

早い.べき数を半分に分けてまとめていく気持ち

#include "bits/stdc++.h"
using namespace std;
typedef long long ll;

ll mod = 1e9 + 7;

ll fast_pow(ll a, ll n){
    if(n == 0) return 1;
    //べき数nが奇数, aを前にだして, a^n-1の気持ちに
    if(n % 2 == 1){
        return a * fast_pow(a, n - 1) % mod;
    }else{
        //べき数nが偶数のとき,べき数を半分にして, aをまとめる. べき数を半分にする
        return fast_pow(a * a % mod, n / 2) % mod;
    }
}

int main(){
    ll A, N;
    cin >> A >> N;
    fast_pow(A, N)
    cout << fast_pow(A, N) << endl;
} 

ダイクストラ法

ある頂点から他の頂点までの最短経路を求める.ただし辺の長さ(重み)は正に限る.計算量はヒープを使ってO((V+E)logV)

二分探査+ダイクストラ

#include "bits/stdc++.h"
 
using namespace std;
typedef long long ll;
ll N, M, L;
ll ans = 0;

vector<vector<pair<ll, ll> > > G;
vector<ll > prever;
 
vector<ll > dijkstra(ll start){
    //準備
    vector<bool > used(N, false);
    vector<ll > dist(N, INFINITY);
    prever.assign(N, -1);
    priority_queue<pair<ll, ll > , vector<pair<ll, ll > >, greater<pair<ll, ll > > > que;
    //初期化
    que.push(make_pair(0, start));
 
    //cout << que.top().first << endl;
 
    while(!que.empty()){
        //距離
        ll d = 0;
        //次の頂点
        ll t_v = 0;
        d = que.top().first;
        t_v = que.top().second;
        //topから削除
        que.pop();
        //探査済みなら処理しない
        if(used[t_v] == true) continue;
        //探査済みにする
        used[t_v] = true;
        //キューの上には既に最小距離が来ているので
        dist[t_v] = d;
        
        //次探査する頂点はt_vなのでt_vから更に次にの頂点の繋がりをキューにいれる
        for(ll i = 0; i < G[t_v].size(); i++){
            //もし探査済みの頂点で,そこまでの累積距離より今更新しようとしている距離が
            //大きいときは更新しない
            ll to = G[t_v][i].first;
            ll cost = d+G[t_v][i].second;
            if(dist[to] <= cost) continue;
            prever[to] = t_v;
            //累積距離と次の頂点
            que.push(make_pair(cost, to));
            //仮dist
            dist[to] = cost;
        }
        //cout << endl;
    }   
    
    return dist;
 
}

vector<ll > get_path(ll t){
    vector<ll > path;
    for(; t != -1; t = prever[t]){
        //cout << prever[t] << endl;
        path.push_back(t);
    }
    //reverse(path.begin(), path.end());
    return path;
}
 
 
int main() {
    //頂点数
    cin >> N >> M >> L;

    G.assign(N, vector<pair<ll, ll > >());
 
    for(ll i = 0; i < M; i++){   
        ll u, v, w;
        cin >> u >> v >> w;
        u--;
        v--;
        //双方向に貼りましょうね
        G[u].push_back(make_pair(v, w));
        G[v].push_back(make_pair(u, w));    
    }

    dijkstra(0);
    //頂点0からget_path(ある頂点)までの経路
    vector<ll > ans = get_path(4);
    for(ll i=0; i < ans.size();i++){
        cout << ans[i]+1 << endl;
    }
}

ベルマンフォワード法

負の辺があってもよい.負の閉路が検出できる.

#include "bits/stdc++.h"

using namespace std;
typedef long long int ll;
typedef pair<ll, ll > pi;  
typedef pair<pair<ll, ll >, ll > pii;  
vector<ll > vec;
vector<vector<ll > > vec2;
ll MOD = 1000000007;
ll INF = 114514191945;
ll N, M; 
vector<vector<pi > > G;
vector<ll > dist;

//ベルマンフォード
bool bellman(ll start){
    //準備
    //vector<ll > dist(N, INF);
    //初期化
    dist[start] = 0;
    
    for(ll i = 0; i < N; i++){
        for(ll from = 0; from < N; from++){
            for(ll j = 0; j < G[from].size(); j++){
                ll to = G[from][j].first;
                ll cost = G[from][j].second;
                if(dist[from] != INF && dist[to] > dist[from] + cost){
                    dist[to] = dist[from] + cost;
                    if(i == N-1){
                        //負の経路が会って頂点の数以上にループが回ってしまう
                        return false;
                    }
                }
            }       
        }
    }

    //確認
    /*
    for(ll i = 0; i < N; i++){
        if(dist[i] == INF){
            cout << "INF" << endl;
        }else{
            cout << dist[i] << endl;
        }
    }
    */

   return true;
}

int main() {
    //探査開始点
    ll S;
    cin >> N >> M >> S;

    G.assign(N, vector<pi >());
    dist.assign(N, INF);

    for(ll i = 0; i < M; i++){   
        ll u, v, w;
        cin >> u >> v >> w;
        //u--;
        //v--;
        //双方向に貼りましょうね
        G[u].push_back(make_pair(v, w));
        //G[v].push_back(make_pair(u, w)); 
    }
    
    if(bellman(S) == false){
        cout << "NEGATIVE CYCLE" << endl;
    }
}

ワーシャルフロイド法

あらかじめ全ての頂点間の最短距離を求めておく 計算量O(V^3) 負の辺があってもOK

ABC143_E

ABC061_D遠回りになるような経路を選んでいく.普段のminをmaxに,INFはMIN_INFに

下のコードは制限が厳しいとき用,多重辺,自己ループ,距離が超巨大になる,負の辺がある,負の辺の閉路がある

#include "bits/stdc++.h"

using namespace std;
typedef long long int ll;
typedef pair<ll, ll > pi;  
typedef pair<pair<ll, ll >, ll > pii;  
vector<ll > vec;
vector<vector<ll > > vec2;
ll MOD = 1000000007;
ll INF = INFINITY;
ll N, M; 

vector<vector<ll > > G;
vector<vector<ll > > path;

bool warshall(){
    //中継点に関して
    for(ll reley = 0; reley < N; reley++){
        for(ll from = 0; from < N; from++){
            for(ll to = 0; to < N; to++){
                //中継した方がコストがかからないか否か
                //小さい順からコストが自明に決まっていく
                if(G[from][to] > G[from][reley] + G[reley][to]){
                    //負の辺対策 INFINITY+INFINITYはマイナスになるので
                    //たまに最短距離を更新してしまう. 
                    if(G[from][reley] != INF && G[reley][to] != INF){
                        G[from][to] = G[from][reley] + G[reley][to];
                        //fromからtoへ到達する最短経路上のfromの1つ前の位置
                        path[from][to] = path[from][reley];
                    }
                }
            }

            //負の閉路がある時
            if(G[from][from] < 0){
                cout << "NEGATIVE CYCLE" << endl;
                return false;
            }
        }
    }

    return true;
}

vector<ll > get_path(ll start, ll end){
    vector<ll > ans;
    //endに向けて進んでいく中継地があれば拾っていく
    for(ll i = start; i != end; i = path[i][end]){
        cout << i << endl;
        ans.push_back(i);
    }
     ans.push_back(end);
    return ans;
}

int main(){

    //頂点数と辺の数
    cin >> N >> M;

    G.assign(N, vector<ll >());
    path.assign(N, vector<ll >());
    
    //初期化
    for(ll i = 0; i < N; i++){
        for(ll j = 0; j < N; j++){
            if(i == j){
                //自分から自分へ
                G[i].push_back(0);
            }else{
                G[i].push_back(INF);
            }
            path[i].push_back(j);
        }       
    }
    //cout << G.size() << " " << G[0].size() << endl;
    //辺入力
    for(ll i = 0; i < M; i++){   
        ll u, v, w;
        cin >> u >> v >> w;
        //適宜揃える
        //u--;
        //v--;
        //有向か無向かでコメントアウト 多重辺も防止
        G[u][v] = min(G[u][v], w);
        //G[v][u] = min(G[v][u], w);
    }

    //実行
    if(warshall() == false){
        return 0;
    }

    //確認
    for(ll i = 0; i < N; i++){
        for(ll j = 0; j < N; j++){
            if(G[i][j] == INF){ 
                cout <<  ( j ? " " : "" ) << "INF"; 
            }else{
                cout <<  ( j ? " " : "" ) <<  G[i][j]; 
            }
            
        }       
        cout << endl;
    }

    get_path(2, 0);
    get_path(0, 3);
}

制限が厳しくないとき用

vector<vector<ll > > warshall(vector<vector<ll > > GRAPH){
    //中継点に関して
    for(ll reley = 0; reley < N; reley++){
        for(ll from = 0; from < N; from++){
            for(ll to = 0; to < N; to++){
                //中継した方がコストがかからないか否か
                //小さい順からコストが自明に決まっていく
                GRAPH[from][to] = min(GRAPH[from][to], GRAPH[from][reley] + GRAPH[reley][to]);
            }
        }
    }

    return GRAPH;
}

二分探査

完全にメモ

lower_bound()

指定した値__以上__の要素の内,最左の位置を返す

upper_bound()

指定した値__超過__の要素の内,最左の位置を返す

先頭イテレータや末尾イテレータを引くことで任意の区間の個数を求められる

#include "bits/stdc++.h"
using namespace std;
typedef long long int ll;
//本番は問題に応じてバグらせないよう番兵を入れる
int main(){
    ll N, K; cin >> N >> K;
    vector<ll > vec(N, 0);
    for(ll i = 0; i < N; i++){
        cin >> vec[i];
    }
    sort(vec.begin(), vec.end());
    //インデックス
    //以上(の最大)
    ll ijo = lower_bound(vec.begin(), vec.end(), K) - vec.begin();
    //未満(の最大)
    ll miman = ijo - 1;
    //超過(の最小))
    ll choka = upper_bound(vec.begin(), vec.end(), K) - vec.begin();
    //以下(の最大)
    ll ika = choka - 1;
}

条件を満たすような最小値を求める

ARC075_D CPSCO2019_Session4_D

check関数にmidを満たすか確認する処理をかく

    //最小の値なのでngは当てはまらない数, okはずっと条件に当てはまる数
    ll ng = -1; ll ok = 100;
 
    //最小値を求める
    //rightを寄せていく
    while(ng + 1 < ok){
        ll mid = (ng + ok) / 2;
        if(check(mid)){
            ok = mid;
        }else{
            ng = mid;
        }
    }
    //最小値
    cout << ok << endl;

条件を満たすような最大値を求める

    //okはずっと条件が当てはまり続ける条件, ngは条件が当てはまらない条件
    ll ok = 0; ll ng = INF;
 
    //最小値を求める
    //rightを寄せていく
    while(ok + 1 < ng){
        ll mid = (ng + ok) / 2;
        if(check(mid)){
            ok = mid;
        }else{
            ng = mid;
        }
    }
    //最小値
    cout << ok << endl;

実数

    double ok = 0, ng = 1145141919;
    for(int i = 0; i < 5000; i++){
        double p = (ok + ng) * 0.5;
        //cout << p << endl;
        if(check(p))ok = p;
        else ng = p;
    }

逆元

ある数aにある数xをかけると1になる.この時,ある数xを「逆元」と言う.割り算を掛け算で表すために逆元を使う.つまり,割れる数数×割る数の逆元.

mod Pにおける逆元

ある数aにある数xをかけてmod Pを取ると余りが1になる.この時,ある数xを「mod Pにおける逆元」という.

式で表すと,ax ≡ 1(mod P)が成立するような数xを選ぶと良い.

例えば,a=5 mod P=13の時,x=8のとき余りが1になる.式に書くと5×8=40で40/13は3余り1. これを1つの式にまとめると,5×8=13×3+1で移項して,13×(-3)+5×8=1

これはP×k+a×x=1と解くことに他ならない.よって,拡張ユークリッドの互除法*1*2*3*4*5*6でkとxを同時に求める.

二項係数を効率よく求めたいときにも逆元をうまく使うと良い

いろはちゃんコンテストDay2

#include "bits/stdc++.h"

using namespace std;
typedef long long int ll;
typedef pair<ll, ll > pi;  
typedef pair<pair<ll, ll >, ll > pii;  
vector<ll > vec;
vector<vector<ll > > vec2;
ll MOD = 1000000007;
ll INF = 11451419194545;

//階乗
vector<ll>fact;
//逆元
vector<ll>inv;
//逆元階乗
vector<ll>finv;

pii extgcd(ll a, ll b){
    //前提:aとbが互いに素であること.
    pii x(make_pair(1,0), a);
    pii y(make_pair(0,1), b);

    ll div = 0;

    //終了条件:(GCD(a,b), 0)または(0,GCD(a,b))
    while(true){
        if(y.second == 0) return x;
        
        div = x.second / y.second;
        x.second = x.second  - div * y.second;
        x.first.first = x.first.first - div * y.first.first;
        x.first.second = x.first.second - div * y.first.second;

        if(x.second == 0) return y;

        div = y.second / x.second;
        y.second = y.second - div * x.second;
        y.first.first = y.first.first - div * x.first.first;
        y.first.second = y.first.second - div * x.first.second;
    }
}

ll modinv(ll a, ll m = MOD){
    //前提:aとmが互いに素であること.
    pii ans;
    ans = extgcd(a, m);

    ll invNum = 0;
    invNum = ans.first.first;
    invNum %= m;
    //C++特有の剰余がマイナスになってしまう対策
    if(invNum < 0) invNum += m;

    return invNum;
}

ll warizan_mod(ll warareru, ll waru, ll m = MOD){
    //割られる数に逆元をかけるだけ
    warareru %= m;
    return warareru * modinv(waru, m) % m;
}

//階乗と逆元を列挙
void combination(ll num, ll m = MOD){
    fact.assign(num + 1919, 0);
    finv.assign(num + 1919, 0);
    inv.assign(num  + 1919, 0);

    //初期化
    fact[0] = 1; inv[0] = 1;
    //テーブルに列挙
    for(ll i = 1; i < fact.size(); i++){
        fact[i] = fact[i - 1] * i % m;
        inv[i]  = modinv(fact[i]);
    }
}

//階乗 n!
ll factrial(ll n){
    return fact[n];
}

//順列 nPk n!/(n-k)!
ll nPk(ll n, ll k, ll m = MOD){
    if(n < 0 || k < 0 || n < k) return 0;
    return fact[n] * inv[n - k] % m;
}

//二項係数 nCk n!/(k!*(n-k)!)
ll nCk(ll n, ll k, ll m = MOD){
    if(n < 0 || k < 0 || n < k) return 0;
    return fact[n] * (inv[k] * inv[n - k] % m) % m;
}

//重複組み合わせ n+k-1Ck
ll nHk(ll n, ll k, ll m = MOD){
    return nCk(n+k-1, k);
}

int main(){

    pii ans;
    ll a, b; cin >> a >> b ;

    //extgcd確認
    ans = extgcd(a, b);
    cout << ans.first.first << " " << ans.first.second << endl;
    //modinv確認
    for (int i = 1; i < 13; ++i) {
        cout << i << " 's inv: " << modinv(i, 13) << endl;
    }
    //warizan_mod確認
    ll warareru = 12345678900000;
    ll waru = 100000;

    cout << warizan_mod(warareru, waru, MOD) << endl;

    //二項係数確認
    if(a < b) swap(a, b);
    combination(a + b);
    //cout << nCk(X, Y) << endl; 
    cout << nCk(a + b, a) << endl; 

}

何通り系の早見表

いつもキレそうになる.

べき乗:pow(x, y)

階乗:factrial(n)

順列:nPk(n, k)

組み合わせ:nCk(n, k)

重複組み合わせ:nHk(n, k)

髭男と刑務所をイメージする

参考1(youtube) 参考2

公平ゲーム

  • 偶数と奇数で勝敗が別れる
  • 操作を真似ると有利ポジションにつける
  • Nimへの帰着(二進数表記における各桁のMOD)

Grundy数(必ず勝つルートに入れば勝つ,逆も然り)

まとめて数え上げる

ある値から見てその値がある条件の元で何回数えられるかを考える

最長増加部分列問題

    //最長増加部分列
    ll mx = 0;
    vector<ll > dp(N, INF);
    for(ll i = 0; i < N; i++){
        //注目している数字以上になる最初の最小の箇所を見つける
        //その方がのちのちに最長なりやすい
        ll it = lower_bound(dp.begin(), dp.end(), vec[i]) - dp.begin();
        dp[it] = vec[i];
        mx = max(mx, it);
    }

あばばば

概要

About

競技プログラミング関連はここにまとめます. Atcoder, Yukicoder, Codeforceの問題をつらつらといています.そのうちブログに解説みたいなの書きたい.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Languages