【模版】莫隊(修正莫隊、樹上莫隊を含む)アルゴリズム


アルゴリズム
チームアルゴリズムはオフラインで問題を解決するための効率的な暴力アルゴリズムです.前知識点:①①ブロック分け②②s o r t sortキーワード並び替え③③l c a lca lcaクエリー(ツリーツリーツリー列)時間複雑度:O(n(\sqrt{n})O(n*)O(n∗n)空間複雑度:O(n+n)O(n+n)O(n+n+n)@
アルゴリズムの流れ:一、ブロックサイズを決定します.ブロックサイズはn\sqrt{n}nです.各要素がどのブロックに属するかを決定します.
int part = sqrt(n);
int num = ceil((double)n/part);
for(int i = 1; i <= num; i++)
    for(int j = (i-1)*part+1; j <= i*part; j++)
        bel[j] = i;
二、構造体で問い合わせを記憶して、s o r t sortで並べ替えます.問い合わせごとに左端lを注文する場合、ブロックによって小さいから大きいまで並べ替えます.左区間lのブロックが同じであれば、右端の点に従って小さいから大きいまで並べ替えます.
bool cmp(pro a, pro b)  {return bel[a.l] == bel[b.l] ? a.r < b.r : bel[a.l] < bel[b.l];}
(このように複雑度が最適で、左端点はブロックで処理し、右端点は小さい時から大きい順に処理する)3、左端点を1 1 1、右端点を0とする.左右の端を動かすのは初めてです.位置の端を動かすたびに、題意によって答えを変える必要があります.現在の質問に対して、左右の端点が左右の区間にそれぞれ対応する場合、現在の質問の答えを記録します.
追加説明:l l l、r r rを現在の照会区間の左右端点とし、L L L、R R Rは照会区間が必要な左右端点とする.
1 1.左の端を右に動かすと、答えに対する貢献はマイナスです.①L Lに迫るL Lをl lで右に移動すると、1桁ずつ移動して、答えを更新します.現在位置の答えを先に更新して、右に移動します.②最終的に私たちの更新答案の区間は[l,L−1][l,L−1][L,L−1]です.③L L Lという位置は私達は答えを更新していません.l lを右に移動する時、私達は無関係の照会区間の答えだけを更新します.
2 2.右の端を右に動かすと、答えに対する貢献がプラスになります.①R R R Rに近づいているr rを右に移動すると、1ビットずつ移動して、答えを更新します.まず右に移動して、新しい位置の答えを更新します.②最終的に私たちが解答を更新する区間は[r,R][r,R][r,R]です.③R R R Rという位置は、上記左端点が右に移動する時に照応し、左端点が減少し、右端点が加算された答えを更新しました.多减すると补正されますが、最终的に両者の交差区間は区間[L,R][L,R][L,R]の答えです.
3.左の端を左に移動すると、答えに対する貢献は正しいです.もうこれ以上説明しないで、先に移動して、解答を更新して、[L,l−1][L,l−1][L,l−1]部分の答えを補った.
4.右の端を左に動かすと、答えに対する貢献はマイナスです.もうこれ以上説明しないで、先に解答を更新して、また移動して、[R+1,r][R+1,r]部分の答えを削除しました.
先に更新しますか?それとも先に移動しますか?順番に注意してください.絵を描く方法で先着順を判断することができます.
莫隊の例題:【国家合宿隊】小Zの靴下の例題コード:
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
int n,m;
int col[200200],bel[200200],le[200200],ri[200200],cnt[200200],ans[202020];
struct pro
{
    int l,r,in;
}q[200200];
int read()
{
    int rt = 0, in = 1; char ch = getchar();
    while(ch < '0' || ch > '9') {if(ch == '-') in = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') {rt = rt * 10 + ch - '0'; ch = getchar();}
    return rt * in;
}
bool cmp(pro a, pro b)  {return bel[a.l] == bel[b.l] ? a.r < b.r : bel[a.l] < bel[b.l];}
int gcd(int a, int b)   {return b == 0 ? a : gcd(b, a%b);}
int calc(int x) {return x * (x-1) / 2;}
int main()
{
    n = read(), m = read();
    for(int i = 1; i <= n; i++) col[i] = read(); 
    for(int i = 1; i <= m; i++) 
    {
        le[i] = read(), ri[i] = read();
        q[i].l = le[i], q[i].r = ri[i], q[i].in = i;
    }
    int part = sqrt(n);
    int num = ceil((double)n/part);
    for(int i = 1; i <= num; i++)
        for(int j = (i-1)*part+1; j <= i*part; j++)
            bel[j] = i;
    sort(q+1, q+1+m, cmp);
    int l = 1, r = 0, Ans = 0;
    for(int i = 1; i <= m; i++)
    {
        int L = q[i].l, R = q[i].r;
        while(l < L)
        {
            Ans += calc(cnt[col[l]]-1) - calc(cnt[col[l]]);
            cnt[col[l++]]--;
        }
        while(l > L)
        {
            cnt[col[--l]]++;
            Ans += calc(cnt[col[l]]) - calc(cnt[col[l]]-1);
        }
        while(r < R)
        {
            cnt[col[++r]]++;
            Ans += calc(cnt[col[r]]) - calc(cnt[col[r]]-1);
        }
        while(r > R)
        {
            Ans += calc(cnt[col[r]]-1) - calc(cnt[col[r]]);
            cnt[col[r--]]--;
        }
        ans[q[i].in] = Ans;
    }
    for(int i = 1; i <= m; i++)
    {
        int a = ans[i], b = calc(ri[i] - le[i] + 1);
        if(a == 0)
        {
            printf("0/1
"
); continue; } int k = gcd(a, b); a /= k, b /= k; printf("%d/%d
"
,a,b); } system("pause"); return 0; }
莫隊(修正付き)
莫隊の基礎の上で、改正の操作を加えました.[国家合宿隊]数色/メンテナンスキューの修正と検索が交互に行われます.難しい点は、検索操作は順序付けされたもので、どのような修正がされたかは確認できません.元々は左右の区間を固定すれば、答えを記録することができます.
修正チームを持って延長します.一、修正操作もエンドポイントとして移動します.左右のエンドポイントが検索の左右のエンドポイントに対応する時に、時間の順序を調整して、時間的にも該当するようにします.そのため、s o r t sortの並び替えを調整する必要があります.ブロックサイズはn 2/3 n^{2/3}n 2/3です.できないと証明します①左の端のブロックを小さいから大きいまで並べ替えます.②左端点がブロック相であると同時に、右端点があるブロック(右端点でもかまいません.両者はほとんど区別がありません.)を小から大へ並べ替えます.③右エンドポイントのブロックも同じで、クエリの時間順に小さいものから順に並べ替えます.
if(bel[a.l] == bel[b.l])    return bel[a.r] == bel[b.r] ? a.time < b.time : a.r < b.r;
else    return bel[a.l] < bel[b.l];
二、毎回の修正操作について、修正する時、簡単なカバーができなくて、修正前後の値を交換します.私たちはこれから復元する時にはまだ調整が必要です.
三、現在の時間を記録する変数t_i m e timeが右(新しい時間)に近づいてきた時、まず自分で増加してから、答えを更新します.現在時刻を記録している変数t_i m_timeが左(古い時間)に近づいてきたら、まず答えを更新してから、自減します.初期値は0 0のため、現在の所在時間として記録されています.
例題コード:
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
int n, m, cntq, cntm;
int col[2002000], bel[2002000], cnt[2002000], ans[2002000];
struct query
{
    int l, r, time, id;
}q[2002000];
struct modify
{
    int pos, val, last;
}p[2002000];
int read()
{
    int rt = 0, in = 1; char ch = getchar();
    while(ch < '0' || ch > '9') {if(ch == '-') in = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') {rt = rt * 10 + ch - '0'; ch = getchar();}
    return rt * in;
}
bool cmp(query a, query b)
{
    if(bel[a.l] == bel[b.l])    return bel[a.r] == bel[b.r] ? a.time < b.time : a.r < b.r;
    else    return bel[a.l] < bel[b.l];
}
int main()
{
    n = read(), m = read();
    for(int i = 1; i <= n; i++) col[i] = read();
    for(int i = 1; i <= m; i++)
    {
        char ch;
        cin >> ch;
        if(ch == 'Q')
        {
            ++cntq;
            q[cntq].l = read(), q[cntq].r = read(), q[cntq].id = cntq, q[cntq].time = cntm;
        }
        if(ch == 'R')
        {
            ++cntm;
            p[cntm].pos = read(), p[cntm].val = read();
        }
    }
    int part = pow(n, 2.0 / 3.0);
    int num = ceil((double)n / part);
    for(int i = 1; i <= num; i++)
        for(int j = (i-1)*part+1; j <= i*part; j++)
            bel[j] = i;
    sort(q+1, q+1+cntq, cmp);
    int l = 1, r = 0, Ans = 0, time = 0;
    for(int i = 1; i <= cntq; i++)
    {
        int L = q[i].l, R = q[i].r, T = q[i].time;
        while(l < L)
        {
            cnt[col[l]]--;
            if(cnt[col[l]] == 0)    Ans--;
            l++;
        }
        while(l > L)
        {
            l--;
            cnt[col[l]]++;
            if(cnt[col[l]] == 1)    Ans++;
        }
        while(r < R)
        {
            r++;
            cnt[col[r]]++;
            if(cnt[col[r]] == 1)    Ans++;
        }
        while(r > R)
        {
            cnt[col[r]]--;
            if(cnt[col[r]] == 0)    Ans--;
            r--;
        }
        while(time < T)
        {
            time++;
            if(L <= p[time].pos && p[time].pos <= R)
            {
                cnt[col[p[time].pos]]--;
                if(cnt[col[p[time].pos]] == 0)  Ans--;
                cnt[p[time].val]++;
                if(cnt[p[time].val] == 1)  Ans++;
            }
            swap(col[p[time].pos], p[time].val);
        }
        while(time > T)
        {
            if(L <= p[time].pos && p[time].pos <= R)
            {
                cnt[col[p[time].pos]]--;
                if(cnt[col[p[time].pos]] == 0)  Ans--;
                cnt[p[time].val]++;
                if(cnt[p[time].val] == 1)  Ans++;
            }
            swap(col[p[time].pos], p[time].val);
            time--;
        }
        ans[q[i].id] = Ans;
    }
    for(int i = 1; i <= cntq; i++)  printf("%d
"
,ans[i]); system("pause"); return 0;0000000 }
木に列なし
ノードをシーケンスに記録すると、ツリー上の問題はシーケンス問題になります.それで普通のチームの状況になりました.
どのように適切なシーケンスを見つけて、ツリーをシーケンスに変換しますか?普通のd f s dfs dfs順序では問題が解決できない場合、オラ順序の概念を導入する必要があります.オーラル・シーケンスは、ある点を巡回する前と後の両方において、現在のノードを記録する特殊なd f s dfs dfsシーケンスである.このように、シーケンス長は2∗n2*n 2∗nである.各点は2回記録されます.f_i r_fir[]fir[]とl s t[]lst[]の2つの配列は、各ノードがシーケンスにおいて最初に出現し、最後に出現した位置をそれぞれ記録する.v's[]vis[]vis[]は、その時点までに訪問されたことがあるかどうかを示しています.
アルゴリズムプロセス:
ツリー上の2点u u uとv vのl c a lcaをwとし、f_i r[u]<=f_i r[v]fir[u]]<=fir[v]fir[u]]==fir[v]とする.
①w wがドットu uであれば(2点はチェーン上にある).経路u−−v−v−vが経験する点は、「f_i r[u],f_i r[v]」[fir]、[u],fir[v]、[u]とラベル付けされたサブストリングにある.このような点は経路u−−v−v−u−vが経験した点ではないので、2回の出現点は0 0 0に寄与する.②経路u−−v>v−u−v上の点はサブストリングの中で一回だけ現れて、それぞれ計算に寄与します.
②w w wがドットu uでない場合(2点は同じチェーンにない).このとき取ったサブストリングの下付きは[l s t[u],f i r[v][lst[u],fir[v][lst[u],fir[v]]に対応します.この2つの点のl c a lcaを記憶します.2点の経路はl c a lca lcaだけがこの段の串内にないからです.左区間の端点はf_i r_fir[u]fir[u]を取る必要がないので、[f_i r[u],l s t[u]−1]、[fir],lst[u]-1]、[fir],lst[u]−1]の区間は余分です.②類の質問に対しては、l c a lca lcaは追加的な添加が必要であり、答えを更新したらl c a lca lcaのv_v i s visを元に戻す(l c a lcaは連続的なサブ串内にないため).
例題:Count on a tree II例題コード:
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
int n,m,depth,Ans;
int fa[404040][20],fir[404040],lst[404040],ans[404040],bel[404040],cnt[404040];
int head[404040],bas[404040],val[404040],tem[404040],deep[404040];
bool vis[400400];
struct list
{
    int to, nxt;
}e[402020];
struct query
{
    int l, r, lca, id;
}q[404040];
int read()
{
    int rt = 0, in = 1; char ch = getchar();
    while(ch < '0' || ch > '9') {if(ch == '-') in = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') {rt = rt * 10 + ch - '0'; ch = getchar();}
    return rt * in;
}
bool cmp(query a, query b)  {  return bel[a.l] == bel[b.l] ? a.r < b.r : bel[a.l] < bel[b.l];  }
void add_edge(int u, int v)
{
    e[++head[0]].to = v;
    e[head[0]].nxt = head[u];
    head[u] = head[0];
}
void dfs(int x)
{
    bas[++bas[0]] = x;
    fir[x] = bas[0];
    for(int i = head[x]; i; i = e[i].nxt)
    {
        if(deep[e[i].to]) continue;
        deep[e[i].to] = deep[x] + 1;
        fa[e[i].to][0] = x;
        dfs(e[i].to);
    }
    bas[++bas[0]] = x;
    lst[x] = bas[0];
}
int query_lca(int u, int v)
{
    if(deep[u] > deep[v])   swap(u, v);
    int d = deep[v] - deep[u];
    for(int i = depth; i >= 0; i--)
        if( (1 << i) & d)
            v = fa[v][i];
    if(u == v)  return v;
    for(int i = depth; i >= 0; i--)
        if(fa[u][i] != fa[v][i])
            u = fa[u][i], v = fa[v][i];
    return fa[u][0];
}
void work(int pos)
{
    if(!vis[pos])
    {
        vis[pos] = 1;
        cnt[val[pos]]++;
        if(cnt[val[pos]] == 1)  Ans++;
    }
    else if(vis[pos])
    {
        vis[pos] = 0;
        cnt[val[pos]]--;
        if(cnt[val[pos]] == 0)  Ans--;
    }
}
int main()
{
    n = read(), m = read();
    depth = log(n) / log(2) + 1;
    
    for(int i = 1; i <= n; i++) val[i] = tem[i] = read();
    sort(tem+1, tem+1+n);
    int len = unique(tem+1, tem+1+n) - tem - 1;
    for(int i = 1; i <= n; i++) val[i] = lower_bound(tem+1, tem+1+len, val[i]) - tem;
    for(int i = 1; i < n; i++)
    {
        int u = read(), v = read();
        add_edge(u, v); add_edge(v, u);
    }
    deep[1] = 1;
    dfs(1);
    for(int i = 1; i <= depth; i++)
        for(int j = 1; j <= n; j++)
            fa[j][i] = fa[fa[j][i-1]][i-1];
    int part = sqrt(bas[0]);
    int num = ceil((double)bas[0] / part);
    for(int i = 1; i <= num; i++)
        for(int j = (i-1)*part+1; j <= i*part; j++)
            bel[j] = i;
    for(int i = 1; i <= m; i++)
    {
        int l = read(), r = read();
        int lca = query_lca(l, r);
        if(fir[l] > fir[r]) swap(l, r);
        q[i].id = i;
        if(l == lca)    q[i].l = fir[l], q[i].r = fir[r];
        else    q[i].l = lst[l], q[i].r = fir[r], q[i].lca = lca;
    }
    sort(q+1, q+1+m, cmp);
    int l = 1, r = 0;
    for(int i = 1; i <= m; i++)
    {
        int L = q[i].l, R = q[i].r, lca = q[i].lca;
        while(l < L)    work(bas[l++]);
		while(l > L)    work(bas[--l]);
		while(r < R)    work(bas[++r]);
		while(r > R)    work(bas[r--]);
        if(lca) work(lca);
        ans[q[i].id] = Ans;
        if(lca) work(lca);
    }
    for(int i = 1; i <= m; i++) printf("%d
"
,ans[i]); system("pause"); return 0; }