SPOJ COT2 Count on a tree II (树上莫队,倍增算法求LCA)
2015-08-27 16:45
441 查看
题意:给一个树图,每个点的点权(比如颜色编号),m个询问,每个询问是一个区间[a,b],图中两点之间唯一路径上有多少个不同点权(即多少种颜色)。n<40000,m<100000。
思路:无意中看到树上莫队,只是拿来练练,没有想到这题的难点不在于树上莫队,而是判断LCA是否在两点之间的路径上的问题。耗时1天。
树上莫队的搞法就是:
(1)DFS一次,对树进行分块,分成sqrt(n)块,每个点属于一个块。并记录每个点的DFS序。
(2)将m个询问区间用所属块号作为第一关键字,DFS序作为第二关键字进行排序。
(3)转移都是差不多的,靠具体问题分析转移方式。
这题的搞法:
(1)点权可能过大,但是只有4万点权,所以映射到1~4w进行处理。
(2)DFS对树进行分块,标记dfs序,记录每个点的深度(倍增用)。
(3)LCA预处理,以及求LCA的函数。
(4)对m个询问进行排序。
(5)莫队开始搞起。
如何转移(抄别人的):
(1)定义S(u,v)为u−v路径上的顶点集合,root表示根节点。
(2)S(u,v)=S(root,u)△S(root,v)△lca(u,v) (△表示集合中的对称差,相当于xor)
(3)定义T(u,v)=S(root,u)△S(root,v),我们先不管lca的事情
(4)如果我们从u−v的路径变成u−v′的路径的话对答案有什么影响呢?
(5)根据定义我们可以得到T(u,v′)=S(root,u)△S(root,v′)
(6)T(u,v)△T(u,v′)=S(root,u)△S(root,v)△S(root,u)△S(root,v′)=S(root,v)△S(root,v′)=T(v,v′)。
也就是说,(a,b)要转移到(a,c)的话,将a点固定不动,b点逐个点走到c点就行了,肯定是将b走到Lca,c也走到Lca就行了。假设(a,b)路径上的点已经作了标记,只需要在走的时候,(b,c)路径上有标记的点就去掉,无标记的就加标记。
这样的做法理论上很容易理解,想起来不简单。因为当你走过a,b,c其中两个点的Lca时,你得想清楚这个Lca究竟要不要,而一共有3个Lca可以玩。当然如果某个点在(a,c)路径上,那么肯定是要的,所以任务就是判断这3个Lca是否在这段路径上。这才是难点!
虽然有很多种情况,但归类为3种:(1)单纯缩短 (2)单纯伸长 (3)先缩短+再伸长。
只要分这3类处理就OK了。而LCA(a,c)肯定是要的,这就可以利用了,判断LCA(b,c)是否在(a,c)上,可以根据从c走到LCA(b,c)遍历的点的顺序,是先到达LCA(a,c)还是LCA(b,c)?如果先到达LCA(b,c),后达LCA(a,c),则LCA(b,c)就要,否则就不要。同理这样判断LCA(a,b)要不要。
3500ms+
AC代码
思路:无意中看到树上莫队,只是拿来练练,没有想到这题的难点不在于树上莫队,而是判断LCA是否在两点之间的路径上的问题。耗时1天。
树上莫队的搞法就是:
(1)DFS一次,对树进行分块,分成sqrt(n)块,每个点属于一个块。并记录每个点的DFS序。
(2)将m个询问区间用所属块号作为第一关键字,DFS序作为第二关键字进行排序。
(3)转移都是差不多的,靠具体问题分析转移方式。
这题的搞法:
(1)点权可能过大,但是只有4万点权,所以映射到1~4w进行处理。
(2)DFS对树进行分块,标记dfs序,记录每个点的深度(倍增用)。
(3)LCA预处理,以及求LCA的函数。
(4)对m个询问进行排序。
(5)莫队开始搞起。
如何转移(抄别人的):
(1)定义S(u,v)为u−v路径上的顶点集合,root表示根节点。
(2)S(u,v)=S(root,u)△S(root,v)△lca(u,v) (△表示集合中的对称差,相当于xor)
(3)定义T(u,v)=S(root,u)△S(root,v),我们先不管lca的事情
(4)如果我们从u−v的路径变成u−v′的路径的话对答案有什么影响呢?
(5)根据定义我们可以得到T(u,v′)=S(root,u)△S(root,v′)
(6)T(u,v)△T(u,v′)=S(root,u)△S(root,v)△S(root,u)△S(root,v′)=S(root,v)△S(root,v′)=T(v,v′)。
也就是说,(a,b)要转移到(a,c)的话,将a点固定不动,b点逐个点走到c点就行了,肯定是将b走到Lca,c也走到Lca就行了。假设(a,b)路径上的点已经作了标记,只需要在走的时候,(b,c)路径上有标记的点就去掉,无标记的就加标记。
这样的做法理论上很容易理解,想起来不简单。因为当你走过a,b,c其中两个点的Lca时,你得想清楚这个Lca究竟要不要,而一共有3个Lca可以玩。当然如果某个点在(a,c)路径上,那么肯定是要的,所以任务就是判断这3个Lca是否在这段路径上。这才是难点!
虽然有很多种情况,但归类为3种:(1)单纯缩短 (2)单纯伸长 (3)先缩短+再伸长。
只要分这3类处理就OK了。而LCA(a,c)肯定是要的,这就可以利用了,判断LCA(b,c)是否在(a,c)上,可以根据从c走到LCA(b,c)遍历的点的顺序,是先到达LCA(a,c)还是LCA(b,c)?如果先到达LCA(b,c),后达LCA(a,c),则LCA(b,c)就要,否则就不要。同理这样判断LCA(a,b)要不要。
3500ms+
#include <bits/stdc++.h> #define pii pair<int,int> #define INF 0x3f3f3f3f #define LL long long using namespace std; const int N=40010; struct Que { int L, R, pos, ans; Que(){}; Que(int L,int R,int pos):L(L),R(R),pos(pos){ans=0;}; }; int belongto , dfn , pre , stac , w ; int depth , anc [32], inp , cnt, bit[101000]; int n, m, block, block_cnt, stac_top, dfn_clock, up; vector<int> vect ; vector<Que> que; void add_edge(int from,int to) { vect[from].push_back(to); vect[to].push_back(from); } inline int cmp(Que a,Que b) //第一关键字:块。第二关键字:DFS时间戳。 { if(belongto[a.L]==belongto[b.L]) return dfn[a.R]<dfn[b.R]; return belongto[a.L] < belongto[b.L]; } inline int cmp1(Que a,Que b){return a.pos<b.pos;} void DFS(int x) //树的分块。 { dfn[x]=++dfn_clock; //dfs序 int cur=stac_top; for(int i=0; i<vect[x].size(); i++) { int t=vect[x][i]; if(!dfn[t]) { pre[t]=x; depth[t]=depth[x]+1; //深度 DFS(t); if(stac_top-cur >= block) //够block个就组 { block_cnt++; while(--stac_top>=cur) { int p=stac[stac_top]; belongto[p]=block_cnt; } stac_top++; //栈顶必须为空 } } } stac[stac_top++]=x;//栈 } void pre_lca(int n) //倍增 { memset(anc, 0, sizeof(anc)); for(int i=1; i<=n; i++) anc[i][0]=pre[i]; //0是他们自己的父亲。 for(int k=1; (1<<k)<=n; k++) //第2的k次方个父亲。 { for(int i=1; i<=n; i++) { if(anc[i][k-1]) { int a=anc[i][k-1]; anc[i][k]=anc[a][k-1]; } } } } int LCA(int a,int b) { //提到同层 if(depth[a]<depth[b]) swap(a, b); int log; for(log=1; (1<<log)<=depth[a]; log++ ); log--; for(int i=log; i>=0; i-- ) //一定要从大到小 if( depth[a]-(1<<i)>=depth[b] ) a=anc[a][i]; if(a==b) return a; //b就是lca for(int i=log; i>=0; i--) //同时往上提。 { if(anc[a][i] && anc[a][i]!=anc[b][i] ) { a=anc[a][i]; b=anc[b][i]; } } return pre[a]; } void deal(int x,int d) //将值异或一下。 { if(d==1) //加上1个 { if(bit[x]) bit[x]+=1; else bit[x]=1,cnt++; } else //减去1个 { bit[x]--; if(bit[x]==0) cnt--; } } void backtofar(int ed,int lca) //但是lca不碰 { while( ed!=lca ) { inp[ed]*=-1; deal(w[ed], inp[ed]); ed=pre[ed]; } } void biyao(int x,int b) //保证x在inpath上的状态必为b { if(inp[x]!=b) { inp[x]*=-1; deal(w[x], inp[x]); } } int update(int L,int s,int e,int lca,int lca2,int lca3) //将s转移到e { if(inp[e]==1) //第一种:缩短 { backtofar(s, lca); backtofar(e, lca); biyao(s, -1); biyao(lca, -1); //s和e的LCA一定不要,若L到e经过了,那就肯定是他们的LCA,再补回来。 } else //其他两种 { int ed, s_e=INF,L_s=INF, L_e=INF, Clock=1; //靠flag判断是:(1)伸长 (2)缩短+伸长 ed=s; while( ed!=lca ) //lca先不碰 { if(ed==lca) s_e=Clock; if(ed==lca3) L_s=Clock; inp[ed]*=-1; //取反 deal(w[ed], inp[ed]); ed=pre[ed]; Clock++; } ed=e; int s_e1=INF; while( ed!=lca ) //lca先不碰 { if(ed==lca) s_e1=Clock; if(ed==lca2) L_e=Clock; inp[ed]*=-1; //取反 deal(w[ed], inp[ed]); ed=pre[ed]; Clock++; } if(ed==lca) s_e1=Clock; if(ed==lca2) L_e=Clock; if(L_s>=s_e) biyao(lca3, -1); if(s_e1>=L_e) biyao(lca, -1); if(L_s<s_e) biyao(lca3, 1); if(s_e1<L_e) biyao(lca, 1); } biyao(L, 1); biyao(e, 1); biyao(lca2, 1); return cnt; } void cal(int n,int m) { for(int i=1; i<=n; i++) inp[i]=-1; //初始,无妨问过. DFS(1); //分块 while(stac_top>=0) //可能有余下的点,另开新块 { int p=stac[stac_top--]; belongto[p]=block_cnt; } pre_lca(n); //倍增处理lca sort(que.begin(), que.end(), cmp); int ans=0, L=1, R=1; //先将L和R随便弄到一个区间上,比如(1,1)。 cnt=inp[1]=bit[w[1]]=1; for(int i=0; i<que.size(); i++) //莫队 { if(R!=que[i].R) ans=update(L, R, que[i].R, LCA(R, que[i].R), LCA(L,que[i].R), LCA(L,R) ); //左 R=que[i].R; if(L!=que[i].L) ans=update(R, L, que[i].L, LCA(L, que[i].L), LCA(R,que[i].L), LCA(L,R) ); //右 L=que[i].L; que[i].ans=ans; } sort(que.begin(), que.end(), cmp1); //输出 for(int i=0; i<que.size(); i++) printf("%d\n", que[i].ans); } map<int,int> mapp; void init() { block=sqrt(n); dfn_clock=block_cnt=stac_top=up=0; mapp.clear(); que.clear(); for(int i=0; i<=n; i++) vect[i].clear(); memset(bit, 0, sizeof(bit)); memset(dfn, 0, sizeof(dfn)); memset(pre, 0, sizeof(pre)); memset(depth, 0, sizeof(depth)); } int main() { freopen("input.txt", "r", stdin); int a, b, t; while(cin>>n>>m) { init(); for(int i=1; i<=n; i++) { scanf("%d", &t); //点权 if(mapp[t]==0) mapp[t]=++up; //映射为小一点的值。 w[i]=mapp[t]; } for(int i=1; i<n; i++) //树边 { scanf("%d%d",&a,&b); add_edge(a, b); } for(int i=0; i<m; i++) //询问 { scanf("%d%d",&a,&b); que.push_back(Que(a,b,i)); } cal(n,m); } return 0; }
AC代码
相关文章推荐
- 母板页 难点---数据交换
- 对比MIUI7和Flyme4.5 期待神秘的Flyme5
- 纯CSS实现酷黑风格三级下拉菜单效果代码
- 松下TDA-200开启呼叫转移功能
- Vim学习笔记6---多文件操作以及标签
- USACO-Section 1.5 Number Triangles(DP)
- mina高并发短连接报java.io.IOException: Too many open files
- 最小公倍数
- httpClient设置代理
- 为什么上传文件的表单里面要加一个属性ENCTYPE=MULTIPART/FORM-DATA?
- 常用的邮箱服务器(SMTP、POP3)域名、端口汇总
- SVN设置必须锁定
- Android应用层View绘制流程与源码分析
- core data 深入解析
- Tslib步骤以及出现问题的解决方案(转)
- 队列的插入 和出列 阻塞 时间 问题
- Windows Windows7 显示和隐藏 Administrator 账户
- 求镜像的二叉树
- c++实现ascii转unicode
- 新版flume+kafka+storm安装部署