• P4930「FJ2014集训」采药人的路径


    题目:P4930「FJ2014集训」采药人的路径

    思路:

    这篇不算题解,是让自己复习的,什么都没说清楚。
    很久没有写点分治了,以前为了赶课件学的太急,板子都没打对就照着题解写题,导致学得很不扎实。
    这道题差不多是在郭老师的指导下一点点凑出来的,还是没能自己完整写出一道题,惭愧。
    这道题大意是:给出一棵边权为0/1的树,求满足以下条件的路径总数:0的个数等于1的个数,且路径上存在一点到路径两端也满足该条件。
    这种求路径总数的题,可以想到用点分治。
    把0看作-1,就可以转化为路径边权和为0。
    如果没有休息点这个条件是很容易统计的,加上这个条件我们就要多记一些信息了。
    点分治的容斥写法应用范围有限,用另一种写法会更方便。
    类似树形背包,我们在统计经过u点的路径条数时,维护了一个之前遍历过的子树上的路径集合,每次进入一棵新子树,记录一下现在走的链,和之前集合里的链拼在一起形成答案,最后把现在的链加入集合中。
    所以对这道题,我们维护现在走的路径和之前的路径集合时都分作两类:有休息点和没有休息点。
    现在有休息点的路径某段后缀和为0,有一段和为0等价于有两点前缀和相等,开桶记录前缀和,类似P3941 入阵曲那道题。
    没有休息点的路径用路径集合中有休息点的路径匹配,有休息点的路径之前有没有休息点都能匹配。
    现在走的有休息点的路径可能本身已经满足条件了,要记入答案。
    注意:休息点也可以不在路径上某点,而在根节点,即两边各有一条“1、-1”的路径,要加上这种情况。


    Code:

    #include <bits/stdc++.h>
    using namespace std;
    const int N=3e5+5,inf=0x3f3f3f3f;
    int n,rt,rtsiz,tot[2],d[2][N],siz[N];
    long long ans;
    bool del[N];
    struct bucket{//桶+时间戳 
    	int tim,vis[N],c[N];
    	inline void add(int pos,int val=1){
    		if(vis[n+pos]==tim) c[n+pos]+=val;
    		else vis[n+pos]=tim,c[n+pos]=val;
    	}
    	inline int ask(int pos){
    		if(vis[n+pos]==tim) return c[n+pos];
    		else return 0;
    	}
    }cnt[2],vis;
    int Top,ver[N],val[N],nxt[N],head[N];
    inline void add(int x,int y,int z){
    	ver[++Top]=y;val[Top]=z;nxt[Top]=head[x];head[x]=Top;
    }
    void findrt(int u,int fa,int all){//找重心 
    	siz[u]=1;
    	int mxsiz=0;
    	for(int i=head[u];i;i=nxt[i]){
    		int v=ver[i];
    		if(v==fa||del[v]) continue;
    		findrt(v,u,all);
    		siz[u]+=siz[v];
    		mxsiz=max(mxsiz,siz[v]);
    	}
    	mxsiz=max(mxsiz,all-siz[u]);
    	if(mxsiz<rtsiz) rtsiz=mxsiz,rt=u;
    }
    void getdis(int u,int fa,int dis){
    	int op=1;
    	if(vis.ask(dis)==0) vis.add(dis),op=0;//之前没有出现相同路径长度 不存在休息点  
    	d[op][++tot[op]]=dis;//记录路径长 
    	for(int i=head[u];i;i=nxt[i]){
    		int v=ver[i];
    		if(v==fa||del[v]) continue;
    		getdis(v,u,dis+val[i]);
    	}
    	if(!op) vis.add(dis,-1);//如果是第一次出现 就退栈 倒出桶 
    }
    void calc(int u){
    	++cnt[0].tim;++cnt[1].tim;++vis.tim;//更新时间戳 相当于memset 
    	for(int i=head[u];i;i=nxt[i]){
    		int v=ver[i];
    		if(del[v]) continue;
    		tot[0]=tot[1]=0;
    		getdis(v,u,val[i]);
    		for(int j=1;j<=tot[0];++j) ans+=cnt[1].ask(-d[0][j]);//没有休息点的路径 用以前有休息点的路径匹配 
    		for(int j=1;j<=tot[1];++j) ans+=cnt[0].ask(-d[1][j])+cnt[1].ask(-d[1][j]);//有休息点的路径 之前有没有休息点都能匹配 
    		for(int j=1;j<=tot[0];++j) if(d[0][j]==0) ans+=cnt[0].ask(0);//特判 
    		for(int j=1;j<=tot[0];++j) cnt[0].add(d[0][j]);//把当前路径加入路径集合 
    		for(int j=1;j<=tot[1];++j) {
    			if(d[1][j]==0) ++ans;//注意当前路径也可能贡献答案 
    			cnt[1].add(d[1][j]);
    		}
    	}
    }
    void dfs(int u){
    	del[u]=true;//删掉u点 
    	calc(u);//计算过u点路径条数 
    	for(int i=head[u];i;i=nxt[i]){
    		int v=ver[i];
    		if(del[v]) continue;
    		rtsiz=inf;
    		findrt(v,u,siz[v]);
    		dfs(rt);
    	}
    }
    int main(){
    	scanf("%d",&n);
    	for(int i=1,u,v,w;i<=n-1;++i){
    		scanf("%d%d%d",&u,&v,&w);
    		if(!w) w=-1;
    		add(u,v,w);
    		add(v,u,w);
    	}
    	rtsiz=inf;
    	findrt(1,0,n);
    	dfs(rt);
    	printf("%lld
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    移动视频开发让我们把会议室装进口袋里
    音视频 开发技术,让智能家居更智能!
    视频对讲SDK 大厅好友解决方案
    视频开发的安全保障措施
    手机视频开发即时通讯软件
    借音视频开发技术,开发招聘新途径
    移动视频技术的先驱
    tf.variable_scope
    sklearn 数据预处理
    Tensorflow自编码器及多层感知机
  • 原文地址:https://www.cnblogs.com/yu-xing/p/11252744.html
Copyright © 2020-2023  润新知