ZOJ 3949 Edge to the Root( 树形dp)

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3949

题解:树dp真的很直觉,或者说dp真的很直觉。就上周末比赛时其实前一半的观察我都找到了,也感觉就是O(N)的,但就是不会转移。观察其实就是说这条边会使得x这个子树的距离变少,以及从1到x这条链的中点+1的这个点的子树的距离变少。那么怎么从fa(x)转移过来呢。对于dep[x]/2+1的子树,因为x相对于fa(x)变深了,所以子树内的每一个都要+1,然而对于x的子树中的每一个点深度相对于从fa(x)变浅了所以-1,那么也就等价一个+1,一个-2。然后记录答案的那个数组要开ll。PS:为什么会有第一发超时,第二发AC的操作,也许有点小卡常。。。这好像是我第一次在ZOJ上交题诶。代码不长,想了很久。

#include<bits/stdc++.h>
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define ls (x<<1)
#define rs (x<<1|1)
#define all(x)	x.begin(),x.end()
#define ll long long
#define For(i,a,b) for(int i=a;i<=b;i++)
#define Forr(i,a,b) for(int i=a;i>=b;i--)
#define Fr(i,a,b) for(int i=a;i<b;i++)
#define Frr(i,a,b) for(int i=a;i>b;i--)
#define pll pair<ll,ll>
using namespace std;
const int maxn=2e5+100;
const ll inf=0x3f3f3f3f;
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int T;
int n;
int siz[maxn],depth[maxn];
int d[maxn];
ll ans[maxn];
vector<int>vec[maxn];
void dfs(int x,int fa)
{
	siz[x]=1;depth[x]=depth[fa]+1;
	for(int i=0;i<vec[x].size();i++){
		int tt=vec[x][i];
		if(tt!=fa){
			dfs(tt,x);
			siz[x]+=siz[tt];
		}
	}
}
void dfs1(int x,int fa)
{	
	d[depth[x]]=x;
	int tt=depth[x]/2+1;
	if(fa==0||fa==1){
		ans[x]=ans[1];
	}
	else
	{
		ans[x]=ans[fa]+siz[d[tt]]-2*siz[x];	
	}
	for(int i=0;i<vec[x].size();i++)
	{
		int tk=vec[x][i];
		if(tk!=fa)dfs1(tk,x);
	}
}
int main()
{
	int p,q;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;i++)vec[i].clear();
		for(int i=1;i<n;i++)
		{
			scanf("%d",&p);scanf("%d",&q);
			vec[p].pb(q);vec[q].pb(p);
		}
		depth[0]=-1;
		dfs(1,0);
		ans[1]=0;
		for(int i=2;i<=n;i++)ans[i]=1e18;
		for(int i=1;i<=n;i++)ans[1]+=depth[i];
		//cout<<ans[1]<<"
";
		dfs1(1,0);
		ll res=ans[1];
		for(int i=2;i<=n;i++)res=min(res,ans[i]);
		printf("%lld
",res);
	}	
}

  

原文地址:https://www.cnblogs.com/intwentieth/p/10575100.html