classCombination{
VI facts, inv_facts;public:Combination(LL N):facts(N +1),inv_facts(N +1){REP(i, N +1) facts[i]= i ==0?1:mod_mul(facts[i -1], i);for(LL i = N; i >=0; i--) inv_facts[i]= i == N ?mod_inv(facts[N]):mod_mul(inv_facts[i +1], i +1);// (i!)^{-1}=((i+1)!)^{-1}*(i+1)}
LL nCr(LL n, LL r){returnmod_mul(facts[n],mod_mul(inv_facts[r], inv_facts[n - r]));}};
Combination c(N);// N は n の最大値
LL ncr = c.nCr(n, r);
順列・組み合わせ列挙
順列列挙
VI X{1,2,..., N};// これらを並び替えるdo{// この中で X は並び替えられており、普通の配列のように使うことができる}while(next_permutation(ALL(X)));// 抜けたら X は元の並びに直っている
重複順列列挙 (N 個の要素から重複を無制限に許して長さ M の順列を全て生成; O(NM))
char elements[]={'1','2','3'};// これらから抽出int N =3;int size = M;voiddfs(int i, string S){if(i == size){// Sが求める順列の1つ(を表す文字列)となる、ここで順列が満たすべき制約でフィルター//cout << S << endl;}else{for(int j =0; j < N; j++){dfs(i +1, S + elements[j]);}}}dfs(0,"");
組み合わせ列挙 (N 個の要素からの全抽出方法)
REP(i,1<< N){// i moves from 0...0 to 1...1 as binary (|i|=N), which indicates choice/no-choice of each elementREP(k, N){// for each element indexif(i &((LL)1<< k)){// element k is selected in this choice}}}
重複組み合わせ列挙 nHr=n+r−1Cr
char elements[]={'1','2','3'};// これらから抽出int N =3;int size = M;// 何個選ぶかvoiddfs(int i,int n, string S){if(i == size){// Sが求める組み合わせの1つ(を表す文字列)となる、ここで順列が満たすべき制約でフィルター//cout << S << endl;}else{for(int j = n; j < N; j++){dfs(i +1, n, S + elements[j]);// ここが順列と違う}}}dfs(0,0,"");
組み合わせで r 回全部選ばなくても良い場合は、選ばないという選択肢を増やした N+1 個の要素からの r 個の組み合わせを計算する
TODO: 組み合わせ公式
累積和・しゃくとり法
連続部分列の要素の総和 S[l,r]=S[1,r]−S[1,l−1]
VI X;// Xが0-indexedなら
VI S(SZ(X)+1);REP(i,SZ(X)) S[i +1]= S[i]+ X[i];auto sum_of_l_to_r = S[r +1]- S[l];// Xが1-indexedなら
VI S(SZ(X));FOR(i,1,SZ(X)) S[i]= S[i -1]+ X[i];auto sum_of_l_to_r = S[r]- S[l -1];
二次元累積和
しゃくとり法(条件を満たす連続部分列の列挙)
TODO: 一般化
VI X(N);
LL right =0;//LL sum = 0; // 部分列の和を扱う場合REP(left, N){while(right < N &&/* 求めたい連続部分列が条件を満たしていない */){/* right を1つ進める *///sum += X[right]; // 部分列の和を扱う場合++right;}if(/* この時点での連続部分列が条件を満たしていない */)break;// right == N で条件を満たせないなら今後満たせることはない場合/* X[left:right) は条件を満たす(leftを固定した時に最短の)連続部分列なので、ここで何かする *//* left を1つ進める準備 */if(right == left)++right;//else sum -= a[left]; // 部分列の和を扱う場合}
その他データ構造・アルゴリズム
Union-Find (同値関係の追加と検索; 無向グラフの連結成分)
classUnionFind{public:
VI parent;UnionFind(LL N):parent(N){iota(ALL(parent),0);// 初期化(親==自分)}
LL getRoot(LL x){// 根に到達するまで親を辿るif(parent[x]== x)return x;// 根は親==自分return parent[x]=getRoot(parent[x]);// 経路圧縮(辿った要素を全て根に直結させる)}boolinSameSet(LL x, LL y){// 根が同じなら同じ集合に属するreturngetRoot(x)==getRoot(y);}voidunite(LL x, LL y){// 同値関係 x~y を追加(xが属する集合とyが属する集合をマージ)
x =getRoot(x);
y =getRoot(y);if(x == y)return;// すでに同じ集合
parent[x]= y;}};
UnionFind uf(N);// N は要素数、1-indexの場合は N + 1 にするEACH(p : pairs){
uf.unite(p.first, p.second);// マージ}REP(i, N){// 1-indexの場合は FOR(i, 1, N + 1) にする/* uf.getRoot(i) が i の属する集合の根 */}// 無向グラフの連結成分
map<LL, set<LL>> ccs;REP(i, N){// 1-indexの場合は FOR(i, 1, N + 1) にする
ccs[uf.getRoot(i)].insert(i);}
VI dijkstra(LL s, vector<vector<Edge>>& G){
VI cost(SZ(G), LLONG_MAX);
cost[s]=0;
priority_queue<PII, VP, greater<PII>> q;// PII = (cost_from_s, to_node)
q.push(MP(cost[s], s));while(!q.empty()){
LL v = q.top().second; q.pop();EACH(x, G[v]){
LL w = x.first, c = x.second;// c = cost of v -> wif(cost[w]> cost[v]+ c){// s -> v -> w is better than current s -> w
cost[w]= cost[v]+ c;
q.push(MP(cost[w], w));}}}return cost;}/* 隣接リストでエッジに属性を乗せたい場合 [ダイクストラなど] */structEdge{ LL to, weight;};
vector<vector<Edge>>G(N +1);
G[x].PB(Edge{y, w});// エッジ x -> y を追加
G[y].PB(Edge{x, w});// 無向グラフの場合はこれも
VI cost(N +1, LLONG_MAX);// LLONG_MAX はできれば別の最大値にしたい(全コストの和とか)dijkstra(v, G, cost);
LL min_cost_v_to_w = cost[w];