bzoj3697(点分治)

来源:互联网 发布:淘宝一元拍是真的吗 编辑:程序博客网 时间:2024/04/30 15:30

采药人的药田是一个树状结构,每条路径上都种植着同种药材。
采药人以自己对药材独到的见解,对每种药材进行了分类。大致分为两类,一种是阴性的,一种是阳性的。
采药人每天都要进行采药活动。他选择的路径是很有讲究的,他认为阴阳平衡是很重要的,所以他走的一定是两种药材数目相等的路径。采药工作是很辛苦的,所以他希望他选出的路径中有一个可以作为休息站的节点(不包括起点和终点),满足起点到休息站和休息站到终点的路径也是阴阳平衡的。他想知道他一共可以选择多少种不同的路径。

 

还是点分治,这道题就没有那么裸的点分治了。

 

本题可以考虑树的点分治。问题就变成求过根满足条件的路径数。

路径上的休息站一定是在起点到根的路径上,或者根到终点的路径上。

如何判断一条从根出发的路径是否包含休息站?只要在dfs中记录下这条路径的和x,同时用个标志数组判断这条路径是否存在前缀和为x的节点。(这个判断是否有休息站,是否有一段路径权值和为0的方法,就是这个标志数组非常巧妙)

这样我们枚举根节点的每个子树。用f[i][0…1],g[i][0…1]分别表示前面几个子树以及当前子树和为i的路径数目,0和1用于区分路径上是否存在前缀和为i的节点。那么当前子树的贡献就是f[0][0] * g[0][0] + Σf [i][0] * g [-i][1] + f[i][1] * g[-i][0] + f[i][1] * g[-i][1],其中i的范围[-d,d],d为当前子树的深度。

 

 

#include<cstdio>#include<cstring>#include<cmath>#include<algorithm>#include<cstdlib>using namespace std;typedef long long ll;const int N=100005;const int inf=0x3f3f3f3f;int n,mi,sum,rt,depth;int size[N];bool vis[N];int head[N],tot;struct aa{int to,pre,dis;}edge[N*2];void addedge(int x,int y,int z){edge[++tot].to=y;edge[tot].pre=head[x];head[x]=tot;edge[tot].dis= z ? 1:-1;}void getsize(int u,int fa){size[u]=1;for (int i=head[u];i;i=edge[i].pre)if (!vis[edge[i].to]&&edge[i].to!=fa) {getsize(edge[i].to,u);size[u]+=size[edge[i].to];}}void getrt(int u,int fa){int tmp=sum-size[u];for (int i=head[u];i;i=edge[i].pre)if (edge[i].to!=fa&&!vis[edge[i].to]){getrt(edge[i].to,u);tmp=max(tmp,size[edge[i].to]);}if (tmp<mi) mi=tmp,rt=u;}ll f[N*2][2],g[N*2][2],t[N*2],ans;void cal(int u,int fa,int dis,int deep){depth=max(depth,deep);if (t[dis+n]) g[n+dis][1]++;else g[n+dis][0]++;t[dis+n]++;for (int i=head[u];i;i=edge[i].pre)if (edge[i].to!=fa&&!vis[edge[i].to])cal(edge[i].to,u,dis+edge[i].dis,deep+1);t[dis+n]--;}void work(int u){f[n][0]=1;int mx=0;for (int i=head[u];i;i=edge[i].pre)if (!vis[edge[i].to]){depth=0;cal(edge[i].to,u,edge[i].dis,1);mx=max(mx,depth);ans+=(f[n][0]-1)*g[n][0];for (int j=n-depth;j<=n+depth;j++) ans+=f[j][0]*g[n*2-j][1]+f[j][1]*g[n*2-j][0]+f[j][1]*g[n*2-j][1];for (int j=n-depth;j<=n+depth;j++){f[j][0]+=g[j][0];f[j][1]+=g[j][1];g[j][0]=g[j][1]=0;}}for (int i=n-mx;i<=n+mx;i++) f[i][0]=f[i][1]=0;}void dfs(int u){getsize(u,0);mi=inf;sum=size[u];getrt(u,0);u=rt;vis[u]=true;work(u);for (int i=head[u];i;i=edge[i].pre)if (!vis[edge[i].to]) dfs(edge[i].to);}int main(){scanf("%d",&n);int x,z,y;for (int i=1;i<n;i++){scanf("%d%d%d",&x,&y,&z);addedge(x,y,z);addedge(y,x,z);}dfs(1);printf("%lld",ans);return 0;}


总结

1:这道题的复杂度并不是那么明确,但是也算是一种方法。

2:前缀路径和,求差如果是0,那么就代表这段区间之间的权值和为0,也就是出现休息站了。

3:mx和depth,控制最大的范围,在这个范围之内计算和更新。并且在点分治中,只clear()刚刚访问过的点(或是在范围内的),避免memset等出现,防止没有必要的计算出现还是很重要的。

4:好题,须仔细回顾

0 0
原创粉丝点击