普通莫隊算法:
//給定一個以1爲根的樹,樹上每個節點都有權值,然後問以某點v爲根的子樹中有多少個出現次數等於k的權值
//先用dfs標號把樹轉換爲線性,然後套用線性的莫隊算法
const int N = 100010;
struct edge
{
int to, next;
} g[N*2];
struct node
{
int l, r, id;
} q[N];
int cnt, head[N];
int n, m, k, block, tmp, cas;
int a[N], b[N], c[N], arr[N], in[N], out[N], num[N], val[N], ans[N], tot;
void add_edge(int v, int u)
{
g[cnt].to = u, g[cnt].next = head[v], head[v] = cnt++;
}
void dfs(int v, int fa)
{
in[v] = ++tot;
val[tot] = a[v];//把節點的權值映射到線性區間上
for(int i = head[v]; i != -1; i = g[i].next)
{
int u = g[i].to;
if(u != fa) dfs(u, v);
}
out[v] = tot;
}
bool cmp(node a, node b)
{
return a.l/block != b.l/block ? a.l/block < b.l/block : a.r < b.r;
}
void add(int x)
{
num[x]++;
if(num[x] == k) tmp++;
else if(num[x] == k + 1) tmp--;
}
void del(int x)
{
num[x]--;
if(num[x] == k) tmp++;
else if(num[x] == k - 1) tmp--;
}
void work()
{
tot = 0;
dfs(1, -1);
for(int i = 1; i <= m; i++)
q[i].id = i, q[i].l = in[arr[i]], q[i].r = out[arr[i]];
block = (int)sqrt(1.0*n);
sort(q+1, q+1+m, cmp);
memset(num, 0, sizeof num);
int l = 1, r = 0;
tmp = 0;
for(int i = 1; i <= m; i++)
{
while(r < q[i].r) add(val[++r]);
while(r > q[i].r) del(val[r--]);
while(l < q[i].l) del(val[l++]);
while(l > q[i].l) add(val[--l]);
ans[q[i].id] = tmp;
}
printf("Case #%d:\n", ++cas);
for(int i = 1; i <= m; i++) printf("%d\n", ans[i]);
}
int main()
{
int t;
scanf("%d", &t);
while(t--)
{
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]), b[i] = a[i];
sort(b+1, b+1+n);
for(int i = 1; i <= n; i++) a[i] = lower_bound(b+1, b+1+n, a[i]) - b;
cnt = 0;
memset(head, -1, sizeof head);
int v, u;
for(int i = 1; i <= n - 1; i++)
{
scanf("%d%d", &v, &u);
add_edge(v, u), add_edge(u, v);
}
scanf("%d", &m);
for(int i = 1; i <= m; i++) scanf("%d", &arr[i]);
work();
if(t) printf("\n");
}
return 0;
}
帶修改莫隊算法:
帶修改莫隊和普通莫隊其實差不多,普通莫隊按兩個關鍵字對詢問排序,首先按左端點所在的塊從小到大排序,否則按右端點排序。帶修改莫隊按三個關鍵字排序,第三個關鍵字是本次詢問時前一個修改的位置,首先按左端點所在的塊從小到大排序,否則按右端點所在的塊從小到大排序,否則按第三個關鍵字直接排序。然後查詢時,暴力還原修改到本次查詢時的狀態,其他的就都一樣了。代碼中的vis[x]爲1時代表x位置的值被統計在答案中,否則不是
//給出一個有n個元素的數組,有以下兩種操作:Q x y,求出區間[x,y)內不同元素的個數,M x y,把第x個元素的值修改爲y。題目中的下標是從0開始的
const int N = 50000 + 10, M = 1000000 + 10;
struct node
{
int l, r, id, pre;
}q[N];
struct
{
int x, v, o;
}que[N];
int n, m;
int block, tot, top, val;
int a[N], last[N], pos[N], ans[N], num[M];
bool vis[N];
bool cmp(node a, node b)
{
// return (pos[a.l] < pos[b.l]) || (pos[a.l]==pos[b.l] && pos[a.r] < pos[b.r])
// || (pos[a.l] == pos[b.l] && pos[a.r] == pos[b.r] && a.pre < b.pre);
if(pos[a.l] == pos[b.l] && pos[a.r] == pos[b.r]) return a.pre < b.pre;
else if(pos[a.l] == pos[b.l]) return pos[a.r] < pos[b.r];
else return pos[a.l] < pos[b.l];
}
void init()
{
//block = sqrt(n);
block = 1300;//設爲1300比sqrt(n)塊,蜜汁。。。
for(int i = 1; i <= n; i++) pos[i] = (i-1) / block + 1;
tot = top = 0;
}
void update(int x)
{
if(vis[x])
{
if(--num[a[x]] == 0) val--;
}
else
{
if(++num[a[x]] == 1) val++;
}
vis[x] ^= 1;
}
void restore(int x, int v)
{
if(vis[x])
{
update(x); a[x] = v; update(x);
}
else a[x] = v;
}
void work()
{
sort(q + 1, q + 1 + tot, cmp);
val = 0;
int l = 1, r = 0, now = 0;
for(int i = 1; i <= tot; i++)
{
while(now < q[i].pre) ++now, restore(que[now].x, que[now].v);
while(now > q[i].pre) restore(que[now].x, que[now].o), --now;
while(r < q[i].r) update(++r);
while(r > q[i].r) update(r--);
while(l < q[i].l) update(l++);
while(l > q[i].l) update(--l);
ans[q[i].id] = val;
}
for(int i = 1; i <= tot; i++) printf("%d\n", ans[i]);
}
int main()
{
scanf("%d%d", &n, &m);
init();
for(int i = 1; i <= n; i++) scanf("%d", &a[i]), last[i] = a[i];
char ch;
int x, y;
for(int i = 1; i <= m; i++)
{
scanf(" %c%d%d", &ch, &x, &y);
x++;
if(ch == 'Q') q[++tot].l = x, q[tot].r = y, q[tot].id = tot, q[tot].pre = top;
else que[++top].x = x, que[top].v = y, que[top].o = last[x], last[x] = y;
//que[x].x記錄修改位置,que[top].v記錄修改後的值,que[top].o記錄修改前的值
}
work();
return 0;
}
樹上莫隊
//給定一棵樹n個點,樹上每個點都有一個權值。有m組查詢,每個查詢給出兩個點,問這兩點之間的路徑上有多少種不同的權值
const int N = 500010;
int n, m, block;
int arr[N], brr[N];
struct edge
{
int to, next;
} g[N*2];
struct node
{
int l, r, v, u, id;
friend bool operator< (node a, node b)
{
return a.l != b.l ? a.l < b.l : a.r < b.r;
}
}q[N*2];
int cnt, head[N];
int dep[N], par[N][21];
int pos[N], st[N], res[N*2];
int tmp, top, tag;
int num[N];
bool vis[N];
void add_edge(int v, int u)
{
g[cnt].to = u, g[cnt].next = head[v], head[v] = cnt++;
}
int dfs(int v, int fa, int d)
{
int tot = 0;
for(int i = head[v]; i != -1; i = g[i].next)
{
int u = g[i].to;
if(u == fa) continue;
dep[u] = dep[v] + 1, par[u][0] = v;
tot += dfs(u, v, d+1);
if(tot >= block) //分塊
{
while(tot--) pos[st[--top]] = tag;
tag++;
}
}
st[top++] = v; //儲存待分塊的序列
return tot + 1; //向上一層返回已被訪問還未分塊的點的個數
}
int LCA(int v, int u) //倍增法求LCA
{
if(dep[v] < dep[u]) swap(v, u);
int d = dep[v] - dep[u];
for(int i = 0; (d>>i) != 0; i++)
if((d>>i) & 1) v = par[v][i];
if(v == u) return v;
for(int i = 20; i >= 0; i--)
if(par[v][i] != par[u][i]) v = par[v][i], u = par[u][i];
return par[v][0];
}
void work(int &v)
{
if(vis[v]) //已被記錄,則本次去掉此點
{
if(--num[arr[v]] == 0) tmp--;
}
else if(++num[arr[v]] == 1) tmp++;
vis[v] ^= 1;
v = par[v][0];
}
void solve()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%d", &arr[i]), brr[i] = arr[i];
sort(brr + 1, brr + 1 + n);
for(int i = 1; i <= n; i++) arr[i] = lower_bound(brr + 1, brr + 1 + n, arr[i]) - brr;
int a, b;
cnt = 0;
memset(head, -1, sizeof head);
for(int i = 1; i < n; i++)
scanf("%d%d", &a, &b), add_edge(a, b), add_edge(b, a);
block = (int)sqrt(n);
dfs(1, 0, 1);
while(top) pos[st[--top]] = tag; //最後一部分沒有分塊的點,分塊
for(int j = 1; (1<<j) <= n; j++)
for(int i = 1; i <= n; i++)
par[i][j] = par[par[i][j-1]][j-1];
for(int i = 1; i <= m; i++)
{
scanf("%d%d", &q[i].v, &q[i].u);
if(pos[q[i].v] > pos[q[i].u]) swap(q[i].v, q[i].u);
q[i].id = i, q[i].l = pos[q[i].v], q[i].r = pos[q[i].u]; //確定兩個點分別位於的塊
}
sort(q + 1, q + 1 + m); //分塊排序
tmp = 0;
int cv = 1, cu = 1;
for(int i = 1; i <= m; i++)
{
int nv = q[i].v, nu = q[i].u;
int lca = LCA(cv, nv);//兩點朝lca移動,處理路徑上的點
while(cv != lca) work(cv);
while(nv != lca) work(nv);
lca = LCA(cu, nu);
while(cu != lca) work(cu);
while(nu != lca) work(nu);
cv = q[i].v, cu = q[i].u;
lca = LCA(cv, cu);
res[q[i].id] = tmp + (!num[arr[lca]]);//對lca特殊處理
}
for(int i = 1; i <= m; i++) printf("%d\n", res[i]);
}
int main()
{
solve();
return 0;
}