SPOJ COT2 Count on a tree II (树上莫队,倍增算法求LCA)
题意:给一个树图,每个点的点权(比如颜色编号),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+
#include <bits/stdc++.h>
#define pii pair<int,int>
#define INF 0x3f3f3f3f
#define LL long long
using namespace std;
const int N=; struct Que
{
int L, R, pos, ans;
Que(){};
Que(int L,int R,int pos):L(L),R(R),pos(pos){ans=;};
};
int belongto[N], dfn[N], pre[N], stac[N], w[N];
int depth[N], anc[N][], inp[N], cnt, bit[];
int n, m, block, block_cnt, stac_top, dfn_clock, up;
vector<int> vect[N];
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=; i<vect[x].size(); i++)
{
int t=vect[x][i];
if(!dfn[t])
{
pre[t]=x;
depth[t]=depth[x]+; //深度
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, , sizeof(anc));
for(int i=; i<=n; i++) anc[i][]=pre[i]; //0是他们自己的父亲。 for(int k=; (<<k)<=n; k++) //第2的k次方个父亲。
{
for(int i=; i<=n; i++)
{
if(anc[i][k-])
{
int a=anc[i][k-];
anc[i][k]=anc[a][k-];
}
}
}
} int LCA(int a,int b)
{
//提到同层
if(depth[a]<depth[b]) swap(a, b);
int log;
for(log=; (<<log)<=depth[a]; log++ );
log--; for(int i=log; i>=; i-- ) //一定要从大到小
if( depth[a]-(<<i)>=depth[b] )
a=anc[a][i]; if(a==b) return a; //b就是lca for(int i=log; i>=; 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个
{
if(bit[x]) bit[x]+=;
else bit[x]=,cnt++;
}
else //减去1个
{
bit[x]--;
if(bit[x]==) cnt--;
} } void backtofar(int ed,int lca) //但是lca不碰
{
while( ed!=lca )
{
inp[ed]*=-;
deal(w[ed], inp[ed]);
ed=pre[ed];
}
}
void biyao(int x,int b) //保证x在inpath上的状态必为b
{
if(inp[x]!=b)
{
inp[x]*=-;
deal(w[x], inp[x]);
}
} int update(int L,int s,int e,int lca,int lca2,int lca3) //将s转移到e
{
if(inp[e]==) //第一种:缩短
{
backtofar(s, lca);
backtofar(e, lca);
biyao(s, -);
biyao(lca, -); //s和e的LCA一定不要,若L到e经过了,那就肯定是他们的LCA,再补回来。
}
else //其他两种
{
int ed, s_e=INF,L_s=INF, L_e=INF, Clock=; //靠flag判断是:(1)伸长 (2)缩短+伸长
ed=s;
while( ed!=lca ) //lca先不碰
{
if(ed==lca) s_e=Clock;
if(ed==lca3) L_s=Clock;
inp[ed]*=-; //取反
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]*=-; //取反
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, -);
if(s_e1>=L_e) biyao(lca, -); if(L_s<s_e) biyao(lca3, );
if(s_e1<L_e) biyao(lca, );
}
biyao(L, );
biyao(e, );
biyao(lca2, );
return cnt;
} void cal(int n,int m)
{
for(int i=; i<=n; i++) inp[i]=-; //初始,无妨问过.
DFS(); //分块
while(stac_top>=) //可能有余下的点,另开新块
{
int p=stac[stac_top--];
belongto[p]=block_cnt;
}
pre_lca(n); //倍增处理lca
sort(que.begin(), que.end(), cmp); int ans=, L=, R=; //先将L和R随便弄到一个区间上,比如(1,1)。
cnt=inp[]=bit[w[]]=;
for(int i=; 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=; 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=;
mapp.clear();
que.clear();
for(int i=; i<=n; i++) vect[i].clear();
memset(bit, , sizeof(bit));
memset(dfn, , sizeof(dfn));
memset(pre, , sizeof(pre));
memset(depth, , sizeof(depth));
}
int main()
{
freopen("input.txt", "r", stdin);
int a, b, t;
while(cin>>n>>m)
{
init();
for(int i=; i<=n; i++)
{
scanf("%d", &t); //点权
if(mapp[t]==) mapp[t]=++up; //映射为小一点的值。
w[i]=mapp[t];
}
for(int i=; i<n; i++) //树边
{
scanf("%d%d",&a,&b);
add_edge(a, b);
}
for(int i=; i<m; i++) //询问
{
scanf("%d%d",&a,&b);
que.push_back(Que(a,b,i));
}
cal(n,m);
}
return ;
}
AC代码
SPOJ COT2 Count on a tree II (树上莫队,倍增算法求LCA)的更多相关文章
- spoj COT2 - Count on a tree II 树上莫队
题目链接 http://codeforces.com/blog/entry/43230树上莫队从这里学的, 受益匪浅.. #include <iostream> #include < ...
- SPOJ COT2 Count on a tree II 树上莫队算法
题意: 给出一棵\(n(n \leq 4 \times 10^4)\)个节点的树,每个节点上有个权值,和\(m(m \leq 10^5)\)个询问. 每次询问路径\(u \to v\)上有多少个权值不 ...
- SP10707 COT2 - Count on a tree II (树上莫队)
大概学了下树上莫队, 其实就是在欧拉序上跑莫队, 特判lca即可. #include <iostream> #include <algorithm> #include < ...
- SP10707 COT2 - Count on a tree II [树上莫队学习笔记]
树上莫队就是把莫队搬到树上-利用欧拉序乱搞.. 子树自然是普通莫队轻松解决了 链上的话 只能用树上莫队了吧.. 考虑多种情况 [X=LCA(X,Y)] [Y=LCA(X,Y)] else void d ...
- [SPOJ]Count on a tree II(树上莫队)
树上莫队模板题. 使用欧拉序将树上路径转化为普通区间. 之后莫队维护即可.不要忘记特判LCA #include<iostream> #include<cstdio> #incl ...
- SPOJ COT2 - Count on a tree II(LCA+离散化+树上莫队)
COT2 - Count on a tree II #tree You are given a tree with N nodes. The tree nodes are numbered from ...
- spoj COT2 - Count on a tree II
COT2 - Count on a tree II http://www.spoj.com/problems/COT2/ #tree You are given a tree with N nodes ...
- SPOJ COT2 Count on a tree II (树上莫队)
题目链接:http://www.spoj.com/problems/COT2/ 参考博客:http://www.cnblogs.com/xcw0754/p/4763804.html上面这个人推导部分写 ...
- SPOJ COT2 Count on a tree II(树上莫队)
题目链接:http://www.spoj.com/problems/COT2/ You are given a tree with N nodes.The tree nodes are numbere ...
随机推荐
- golang defer使用——资源关闭时候多用
defer Go语言中有种不错的设计,即延迟(defer)语句,你可以在函数中添加多个defer语句.当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回.特别是当你在进行一些打开资源 ...
- mac系统下查看端口占用情况
localhost:~ mhx$ lsof -i tcp:8080 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NA ...
- BZOJ_2726_[SDOI2012]任务安排_斜率优化+二分
BZOJ_2726_[SDOI2012]任务安排_斜率优化+二分 Description 机器上有N个需要处理的任务,它们构成了一个序列.这些任务被标号为1到N,因此序列的排列为1,2,3...N.这 ...
- BZOJ_1406_[AHOI2007]密码箱_枚举+数学
BZOJ_1406_[AHOI2007]密码箱_枚举+数学 Description 在一次偶然的情况下,小可可得到了一个密码箱,听说里面藏着一份古代流传下来的藏宝图,只要能破解密码就能打开箱子,而箱子 ...
- Golang项目的测试实践
Golang项目的测试实践 最近有一个项目,链路涉及了4个服务.最核心的是一个配时服务.要如何对这个项目进行测试,保证输出质量,是最近思考和实践的重点.这篇就说下最近这个实践的过程总结. 测试金字塔 ...
- Gradle系列之三 Gradle概述以及生命周期
1 Gradle是一种编程框架 gradle主要由以下三部分组成 1 groovy核心语法 2 build script block 3 gradle api 注:本章所有的代码都在 https:// ...
- TP3.2单字母函数
A方法 A方法用于在内部实例化控制器 调用格式:A(‘[项目://][分组/]模块’,’控制器层名称’) 最简单的用法: $User = A('User'); 表示实例化当前项目的UserAction ...
- Oracle数据库创建表空间及用户授权
/*分为四步 */ /*第1步:创建临时表空间 */ create temporary tablespace test_temp tempfile 'E:\app\Administrator\orad ...
- 进程与线程(2)- python实现多进程
python 实现多进程 参考链接: https://morvanzhou.github.io/tutorials/python-basic/multiprocessing/ python中实现多进程 ...
- Centos 内存释放
原因:最近发现服务器老师提示内存不足的警报,很多时候内存都占用百分之80以上,查看运行的服务似乎并没有占用很大的内存,top查看运行的服务,然后按shift+m排名第一的才百分之1.x,看了别人的博客 ...