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]<<"\n"; dfs1(1,0); ll res=ans[1]; for(int i=2;i<=n;i++)res=min(res,ans[i]); printf("%lld\n",res); } }