zoukankan      html  css  js  c++  java
  • [bzoj2599][IOI2011]Race_树上点分治

    Race bzoj-2599

        题目大意:询问一颗树上最短的、长度为k的链,边有边权,n个节点。

        注释:$1le n le 2cdot 10^5$,$1le k le 10^6$。

          想法:树上点分治的另一种表现方式。首先,由于题目中要求的是最小值,我们发现这东西可加不可减。不可减意味着什么?意味着我们递归计算当前树时无法将它的单个子树的情况减掉。所以之前的单步容斥的算法就收到了打压qwq。我们思考另一种方法。首先,类似于dfs的,我一定是对于当前root一颗子树一颗子树地递归,只有当前子树的信息已经完全处理好的情况下我才会去处理下一颗子树。这就相当于我在处理当前子树的时候之前的子树已经是完善的了。所以,我可以开一个桶,记录长度为i的链的最短长度,然后对于当前子树我可以直接调用桶中信息,即可。然后,还原桶的操作是简单的,就是说如果当前桶所代表的边权小于等于k,那么久有可能是被更新过的,将其还原即可。

        最后,附上丑陋的代码... ...

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define N 200010 
    using namespace std;
    const int inf=1<<30;
    int to[2*N],nxt[2*N],head[N],val[N*2],tot;
    int size[N],vis[N],v[N*5],f[N],dis[N],deep[N];
    //dis数组表示当前节点到根节点之间的边权和
    //deep数组表示当前节点到根节点路径深度,即链长度
    //vis数组表示当前节点是否已经被删除(当过根节点)
    //v数组是桶
    int ms,root;
    int n,k;
    int ans=inf;
    inline void add(int x,int y,int z)//加边
    {
    	to[++tot]=y;
    	val[tot]=z;
    	nxt[tot]=head[x];
    	head[x]=tot;
    }
    void getroot(int pos,int fa)//找重心、处理size
    {
    	size[pos]=1;
    	f[pos]=0;
    	for(int i=head[pos];i;i=nxt[i])
    	{
    		if(to[i]==fa||vis[to[i]]) continue;
    		getroot(to[i],pos);
    		size[pos]+=size[to[i]];
    		f[pos]=max(size[to[i]],f[pos]);
    	}
    	f[pos]=max(f[pos],ms-size[pos]);
    	if(f[root]>f[pos]) root=pos;
    	// puts("getroot");
    }
    void clear(int pos,int fa)//还原桶
    {
    	if(dis[pos]<=k) v[dis[pos]]=inf;
    	for(int i=head[pos];i;i=nxt[i])
    	{
    		if(to[i]==fa||vis[to[i]]) continue;
    		clear(to[i],pos);
    	}
    	// puts("clear");
    }
    void insert(int pos,int fa)//修改桶
    {
    	if(dis[pos]<=k)
    	{
    		v[dis[pos]]=min(v[dis[pos]],deep[pos]);
    	}
    	for(int i=head[pos];i;i=nxt[i])
    	{
    		if(vis[to[i]]||to[i]==fa) continue;
    		insert(to[i],pos);
    		// puts("insert");
    	}
    }
    void calc(int pos,int fa)//计算答案
    {
    	if(dis[pos]<=k)
    	{
    		ans=min(ans,deep[pos]+v[k-dis[pos]]);
    	}
    	for(int i=head[pos];i;i=nxt[i])
    	{
    		if(vis[to[i]]||to[i]==fa) continue;
    		deep[to[i]]=deep[pos]+1;
    		dis[to[i]]=dis[pos]+val[i];
    		calc(to[i],pos);
    	}
    	// puts("calc");
    }
    void solve(int pos)//点分治过程
    {
    	vis[pos]=true;
    	// deep[pos]=0;
    	v[0]=0;
    	for(int i=head[pos];i;i=nxt[i])
    	{
    		if(vis[to[i]]) continue;
    		deep[to[i]]=1;
    		dis[to[i]]=val[i];
    		calc(to[i],0);
    		insert(to[i],0);
    	}
    	for(int i=head[pos];i;i=nxt[i])
    	{
    		if(vis[to[i]]) continue;
    		clear(to[i],0);
    	}
    	for(int i=head[pos];i;i=nxt[i])
    	{
    		if(vis[to[i]]) continue;
    		ms=size[to[i]];
    		root=0;
    		getroot(to[i],0);
    		solve(root);
    	}
    	// puts("solve");
    }
    int main()
    {
    	// int n,k;
    	scanf("%d%d",&n,&k);
    	for(int x,y,z,i=1;i<n;i++)
    	{
    		scanf("%d%d%d",&x,&y,&z);
    		add(x+1,y+1,z);//点编号是从0开始的
    		add(y+1,x+1,z);
    	}
    	// v[0]=0;
    	for(int i=1;i<=k;i++)
    	{
    		v[i]=inf;
    	}
    	root=0;
    	f[0]=n;
    	// dfs(1);
    	ms=n;
    	getroot(1,0);
    	// printf("%d
    ",root);
    	solve(root);
    	if(ans==inf) printf("-1
    ");
    	else printf("%d
    ",ans);
    	return 0;
    }
    

        小结:一定要注意dis数组和deep数组分别的含义。然后主函数里所有函数的fa都是0,因为是递归处理,但是其实将fa改成pos也没有问题,因为pos节点已经当过重心、被删除了。

  • 相关阅读:
    て和で用法的总结
    假如程序员上热搜是什么样的?网友:毫无违和感!
    一年精通,三年熟悉,五年了解,十年用过!C++真的这么难吗?
    新手上路,“hello word”其实是在告诉计算机又有菜鸟来了!
    从原理到方法,一步到位,教你如何应对C语言内存泄露!
    冰冷的英语字母,枯燥的编程教程,果断选择了放弃!真的吗?
    只有了解程序员的黑话,和他们打成一片获得buff加成,产品才能尽早上线!
    C语言编程小游戏「石头剪刀布」!源码分享~
    一行代码卖出570美元,什么样的代码能这么值钱?带你揭秘天价代码的内幕!
    源码解剖HashMap
  • 原文地址:https://www.cnblogs.com/ShuraK/p/8829642.html
Copyright © 2011-2022 走看看