對於剛學樹剖的同學比如我這種大大大蒟蒻來說,做這題會給你帶來很大的提公升:不僅可以對樹剖有更深刻的理解,還可以更好的理解線段樹,所以這是一道好題哦
為了更好懂,我一點一點說說思路吧
首先這題題意不難懂,只有兩個操作:區間顏色修改和區間查詢顏色數量,我們分開來看:
這是這題的難點,弄懂了以後可以對線段樹有個蠻大的提公升吧
我們先把問題簡化一下,假設這不是一棵樹,只是一條連(已經樹剖過了),給定每個元素的顏色,問有幾段顏色(就是這題中顏色數量的定義),我們怎麼做呢?
首先我們可以知道,為了保障時間複雜度,這題肯定是用線段樹求解的,最先想到的是葉子節點的顏色個數為1,因為此時不存在有顏色會重複,按線段樹的做法,現在要回溯求更大區間的顏色個數了,我們怎樣求解呢?
其實分情況討論一下就可以知道了:見下
第一種情況:
左區間:1231(顏色個數為4) 右區間:222(個數為1)
合併後:1231222(顏色個數為4+1=5)
這是第一種情況:沒有重複
我們再來看第二種:
左區間:1231(顏色個數為4)右區間:121(個數為3)
合併後:1231121(顏色個數為4+3-1)
這就是第二種情況了,左區間的最後乙個顏色和右區間的第乙個顏色重合,也就重複了,所以總數減一
綜上所述:我們用陣列lc[ ]和rc[ ]表示區間左右顏色,線段樹維護區間顏色總數,就可以解決鏈情況下的此問題了
int lc[maxn << 2];//這裡要開4倍大小,因為是對應線段樹節點的
int rc[maxn << 2];
void build(int id,int l,int r)
int mid = l + r >> 1;
build(lid,l,mid);
build(rid,mid + 1,r);
tree[id].sum = tree[lid].sum + tree[rid].sum;
if(rc[lid] == lc[rid])tree[id].sum -= 1;
lc[id] = lc[lid];
rc[id] = rc[rid];
}void pushdown(int id)
}int query(int id,int l,int r)
int mid = tree[id].l + tree[id].r >> 1;
if(mid < l)
else if(mid >= r)
else
}
值得注意的是:不要忘記大區間要繼承小區間的左右端點顏色我們的操作時基於樹形的,所以樹剖過後,我們樹剖的查詢函式要略作修改。
如上圖:樹剖就是把兩點之間剖成了若干條鏈,我們還是要解決不同的鏈之間顏色重複問題。上圖已經很明朗了:解決top[a]與fa[top[a]]顏色重複問題即可:
我寫了個函式qc來查詢單點的顏色,其他學過樹剖的應該不會太陌生:
int query(int id,int l,int r)
int mid = tree[id].l + tree[id].r >> 1;
if(mid < l)
else if(mid >= r)
else
}int qc(int id,int l,int r)
int mid = tree[id].l + tree[id].r >> 1;
if(mid < l)return qc(rid,l,r);
else return qc(lid,l,r);
}int qsum(int x,int y)
if(dep[x] > dep[y])swap(x,y);
ans += query(1,pos[x],pos[y]);
return ans;
}
與普通的樹剖題修改無大異,注意線段樹中的顏色數量更新即區間端點繼承即可
void update(int id,int c,int l,int r)
int mid = tree[id].l + tree[id].r >> 1;
if(mid < l)
else if(mid >= r)
else
tree[id].sum = tree[lid].sum + tree[rid].sum;
if(rc[lid] == lc[rid])tree[id].sum -= 1;
lc[id] = lc[lid];
rc[id] = rc[rid];
}void uprange(int x,int y,int c)
if(dep[x] > dep[y])swap(x,y);
update(1,c,pos[x],pos[y]);
}
就不多提啦,祝大家天天ac!
(重要注釋已經打在上面思路部分了,這裡直接給**)
#include#include#include#include#includeusing namespace std;
int rd()
while(c >= '0' && c <= '9')
return flag * out;
}const int maxn = 100019;
int num,na,nume,cnt;
int head[maxn];
struct nodee[maxn * 2];
void add(int u,int v)
int size[maxn],wson[maxn],dep[maxn],fa[maxn],top[maxn],pos[maxn],ori[maxn];
int col[maxn];
void dfs1(int id,int f)
}void dfs2(int id,int tp)
}int lc[maxn << 2];
int rc[maxn << 2];
#define lid (id << 1)
#define rid (id << 1) | 1
struct sag_treetree[maxn << 2];
void build(int id,int l,int r)
int mid = l + r >> 1;
build(lid,l,mid);
build(rid,mid + 1,r);
tree[id].sum = tree[lid].sum + tree[rid].sum;
if(rc[lid] == lc[rid])tree[id].sum -= 1;
lc[id] = lc[lid];
rc[id] = rc[rid];
}void pushdown(int id)
}void update(int id,int c,int l,int r)
int mid = tree[id].l + tree[id].r >> 1;
if(mid < l)
else if(mid >= r)
else
tree[id].sum = tree[lid].sum + tree[rid].sum;
if(rc[lid] == lc[rid])tree[id].sum -= 1;
lc[id] = lc[lid];
rc[id] = rc[rid];
}int query(int id,int l,int r)
int mid = tree[id].l + tree[id].r >> 1;
if(mid < l)
else if(mid >= r)
else
}int qc(int id,int l,int r)
int mid = tree[id].l + tree[id].r >> 1;
if(mid < l)return qc(rid,l,r);
else return qc(lid,l,r);
}void uprange(int x,int y,int c)
if(dep[x] > dep[y])swap(x,y);
update(1,c,pos[x],pos[y]);
}int qsum(int x,int y)
if(dep[x] > dep[y])swap(x,y);
ans += query(1,pos[x],pos[y]);
return ans;
}int main()
dfs1(1,-1);
dfs2(1,1);
build(1,1,num);
char ask;
int c;
for(int i = 1;i <= na;i++)
else
} return 0;
}
最後,雖然dalao沒幫啥忙,可是我是看大佬以前的**查出錯的,宣傳一下大佬
最後當然是大家最喜歡的廣告啦
洛谷P2486 SDOI2011 染色
輸入格式 輸出格式 對於每個詢問操作,輸出一行答案。輸入樣例 1 6 5 2 2 1 2 1 1 1 21 3 2 42 5 2 6q 3 5 c 2 1 1 q 3 5 c 5 1 2 q 3 5 輸出樣例 1 典型的樹鏈剖分 線段樹,線段樹維護 區間左右端點顏色 和 區間顏色數。注意 當 上傳或...
洛谷 P2486 SDOI2011 染色
洛谷傳送門 給定一棵 nn 個節點的無根樹,共有 mm 個操作,操作分為兩種 將節點 aa 到節點 bb 的路徑上的所有點 包括 aa 和 bb 都染成顏色 cc。詢問節點 aa 到節點 bb 的路徑上的顏色段數量。顏色段的定義是極長的連續相同顏色被認為是一段。例如112221由三段組成 11 22...
SDOI2011 染色 題解
題目大意 給定一棵有n個節點的無根樹和m個操作,操作有2類 1 將節點a到節點b路徑上所有點都染成顏色c 2 詢問節點a到節點b路徑上的顏色段數量 連續相同顏色被認為是同一段 思路 樹剖之後,維護其兩端的顏色 答案和標記即可。include include define n 100001 using...