3757: 苹果树 树上莫队 位运算技巧
2016-01-22 08:14
495 查看
引用Vfleaking神犇[WC2013]糖果公园的一些题解。
本题也可以如此解决,神奇的莫队:
上一次询问用(curV, curU, curTi)表示,并且我们还保留了
visited[v]:v节点在不在curV到curU的路径上
col[v]:v节点的颜色(原题好像是糖果来着?我就叫颜色了。)
occur[c]:颜色c在curV到curU的路径上出现的次数
outcome:当前的答案。
这些信息。
现在又来了一个神奇的询问!(targetV, targetU, targetTi)
那么将curV 移动到targetV去,curU移动到targetU, 时间curTi移动到targetTi上去,然后再回答询问。
先说时间的移动好了,这个比较简单。
预处理出来每个修改在修改之前的颜色。(修改之后的颜色是输入)
如果targetTi > curTi
那么不停地curTi++,执行当前修改。
如果targetTi <= curTi
那么不停地curTi++,撤销当前修改,利用刚才的预处理。
给一个节点改变颜色的方式是,如果visited了,就XXXXXX,如果没visited,就XXXXXX。讨论一下就行了囧。
然后是节点的移动。
好像巨纠结啊囧!!!
我只会sb方法,求神犇赐教。
用S(v, u)代表 v到u的路径上的结点的集合。
用root来代表根结点,用lca(v, u)来代表v、u的最近公共祖先。
那么
S(v, u) = S(root, v) xor S(root, u) xor lca(v, u)
其中xor是集合的对称差。
简单来说就是节点出现两次消掉。
lca很讨厌,于是再定义
T(v, u) = S(root, v) xor S(root, u)
观察将curV移动到targetV前后T(curV, curU)变化:
T(curV, curU) = S(root, curV) xor S(root, curU)
T(targetV, curU) = S(root, targetV) xor S(root, curU)
取对称差:
T(curV, curU) xor T(targetV, curU)= (S(root, curV) xor S(root, curU)) xor (S(root, targetV) xor S(root, curU))
由于对称差的交换律、结合律:
T(curV, curU) xor T(targetV, curU)= S(root, curV) xor S(root, targetV)
两边同时xor T(curV, curU):
T(targetV, curU)= T(curV, curU) xor S(root, curV) xor S(root, targetV)
发现最后两项很爽……哇哈哈
T(targetV, curU)= T(curV, curU) xor T(curV, targetV)
(有公式恐惧症的不要走啊 T_T)
也就是说,更新的时候,xor T(curV, targetV)就行了。
即,对curV到targetV路径(除开lca(curV, targetV))上的结点,将它们的存在性取反即可。
我之前说的visited[]、occur[]、outcome的定义并不方便,因为有个lca搀和。
干脆用这仨记录集合T(curV, curU)的情况,更加方便处理。
我觉得还是上代码比较有亲切感。
verXor函数的作用是将一个结点的存在性取反。
val 即题目中的V。
sumCoe是题目中的W的前缀和。
father[v][0]是结点v的父亲。
depth[v]是结点的深度。
T_T为什么我写的这么慢。。。
本题也可以如此解决,神奇的莫队:
上一次询问用(curV, curU, curTi)表示,并且我们还保留了
visited[v]:v节点在不在curV到curU的路径上
col[v]:v节点的颜色(原题好像是糖果来着?我就叫颜色了。)
occur[c]:颜色c在curV到curU的路径上出现的次数
outcome:当前的答案。
这些信息。
现在又来了一个神奇的询问!(targetV, targetU, targetTi)
那么将curV 移动到targetV去,curU移动到targetU, 时间curTi移动到targetTi上去,然后再回答询问。
先说时间的移动好了,这个比较简单。
预处理出来每个修改在修改之前的颜色。(修改之后的颜色是输入)
如果targetTi > curTi
那么不停地curTi++,执行当前修改。
如果targetTi <= curTi
那么不停地curTi++,撤销当前修改,利用刚才的预处理。
给一个节点改变颜色的方式是,如果visited了,就XXXXXX,如果没visited,就XXXXXX。讨论一下就行了囧。
然后是节点的移动。
好像巨纠结啊囧!!!
我只会sb方法,求神犇赐教。
用S(v, u)代表 v到u的路径上的结点的集合。
用root来代表根结点,用lca(v, u)来代表v、u的最近公共祖先。
那么
S(v, u) = S(root, v) xor S(root, u) xor lca(v, u)
其中xor是集合的对称差。
简单来说就是节点出现两次消掉。
lca很讨厌,于是再定义
T(v, u) = S(root, v) xor S(root, u)
观察将curV移动到targetV前后T(curV, curU)变化:
T(curV, curU) = S(root, curV) xor S(root, curU)
T(targetV, curU) = S(root, targetV) xor S(root, curU)
取对称差:
T(curV, curU) xor T(targetV, curU)= (S(root, curV) xor S(root, curU)) xor (S(root, targetV) xor S(root, curU))
由于对称差的交换律、结合律:
T(curV, curU) xor T(targetV, curU)= S(root, curV) xor S(root, targetV)
两边同时xor T(curV, curU):
T(targetV, curU)= T(curV, curU) xor S(root, curV) xor S(root, targetV)
发现最后两项很爽……哇哈哈
T(targetV, curU)= T(curV, curU) xor T(curV, targetV)
(有公式恐惧症的不要走啊 T_T)
也就是说,更新的时候,xor T(curV, targetV)就行了。
即,对curV到targetV路径(除开lca(curV, targetV))上的结点,将它们的存在性取反即可。
我之前说的visited[]、occur[]、outcome的定义并不方便,因为有个lca搀和。
干脆用这仨记录集合T(curV, curU)的情况,更加方便处理。
我觉得还是上代码比较有亲切感。
verXor函数的作用是将一个结点的存在性取反。
val 即题目中的V。
sumCoe是题目中的W的前缀和。
father[v][0]是结点v的父亲。
depth[v]是结点的深度。
T_T为什么我写的这么慢。。。
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> using namespace std; int n,m,cnt,top,ans,scc,ind,root,block; int head[50005],dfn[50005],s[50005],c[50005],pos[50005],stack[50005],deep[50005],fa[50005][17]; int next[100005],list[100005]; bool vis[50005]; struct node {int u,v,a,b,id,ans;} a[100005]; inline int read() { int a=0,f=1; char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();} while (c>='0'&&c<='9') {a=a*10+c-'0'; c=getchar();} return a*f; } inline void insert(int x,int y) { next[++cnt]=head[x]; head[x]=cnt; list[cnt]=y; } inline bool cmp(node a,node b) { return pos[a.u]==pos[b.u]?dfn[a.v]<dfn[b.v]:pos[a.u]<pos[b.u]; } inline bool cmp0(node a,node b) { return a.id<b.id; } int dfs(int x) { int size=0; dfn[x]=++ind; for (int i=1;(1<<i)<=deep[x];i++) fa[x][i]=fa[fa[x][i-1]][i-1]; for (int i=head[x];i;i=next[i]) if (list[i]!=fa[x][0]) { fa[list[i]][0]=x; deep[list[i]]=deep[x]+1; size+=dfs(list[i]); if (size>=block) { scc++; for (int j=1;j<=size;j++) pos[stack[top--]]=scc; size=0; } } stack[++top]=x; return size+1; } inline int lca(int x,int y) { if (deep[x]<deep[y]) swap(x,y); int t=deep[x]-deep[y]; for (int i=0;(1<<i)<=t;i++) if (t&(1<<i)) x=fa[x][i]; for (int i=16;~i;i--) if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i]; return x==y?x:fa[x][0]; } inline void rever(int x) { if (!vis[x]) {vis[x]=1; s[c[x]]++; if (s[c[x]]==1) ans++;} else {vis[x]=0; s[c[x]]--; if (s[c[x]]==0) ans--;} } inline void solve(int u,int v) { while (u!=v) if (deep[u]>deep[v]) rever(u),u=fa[u][0]; else rever(v),v=fa[v][0]; } int main() { n=read(); m=read(); block=(int)(sqrt(n)); for (int i=1;i<=n;i++) c[i]=read(); for (int i=1;i<=n;i++) { int u=read(),v=read(); if (!u) root=v; else if (!v) root=u; else insert(u,v),insert(v,u); } dfs(root); scc++; while (top) pos[stack[top--]]=scc; for (int i=1;i<=m;i++) { a[i].u=read(); a[i].v=read(); a[i].id=i; a[i].a=read(); a[i].b=read(); if (dfn[a[i].u]>dfn[a[i].v]) swap(a[i].u,a[i].v); } sort(a+1,a+m+1,cmp); int t=lca(a[1].u,a[1].v); solve(a[1].u,a[1].v); rever(t); a[1].ans=ans; if (s[a[1].a]&&s[a[1].b]&&a[1].a!=a[1].b) a[1].ans--; rever(t); for (int i=2;i<=m;i++) { solve(a[i-1].u,a[i].u); solve(a[i-1].v,a[i].v); int t=lca(a[i].u,a[i].v); rever(t); a[i].ans=ans; if (s[a[i].a]&&s[a[i].b]&&a[i].a!=a[i].b) a[i].ans--; rever(t); } sort(a+1,a+m+1,cmp0); for (int i=1;i<=m;i++) printf("%d\n",a[i].ans); return 0; }
相关文章推荐
- 滴滴快车奖励政策,高峰奖励,翻倍奖励,按成交率,指派单数分级(1月22日)
- iOS学习之移除Main.storyboard
- 正确使用SQL Server中的count()函数
- Linux 基金会悄悄移除社区推荐代表的资格
- JSP与ASP.PHP的比較
- 设计模式(十八):责任链模式
- Eclipse 快捷键
- Eclipse 快捷键
- android-studio 离线安装
- 菱形继承
- AngularJS学习之指令作用域
- iOS开发-Alpha,Hidden与Opaque区别
- AngularJS的生命周期:complie和link
- AngularJS学习之绑定策略
- 给各位聚聚和大大介绍一个开源项目 Expression2Sql(二)
- 用AFNetworking3.0封装网络请求
- Excel导入→JS拼出来的Table
- 安卓Viewpager的使用--简单的欢迎页
- BZOJ 1342: [Baltic2007]Sound静音问题|单调队列
- TortoiseGit客户端安装及使用(上传代码到git@osc