1. 程式人生 > >2018年9月15日提高組模擬賽 T2 拆網線

2018年9月15日提高組模擬賽 T2 拆網線

大意

給定一張n個點,n1條邊的無向聯通圖,現要在圖中至少有一個由m個點組成的聯通分量中的點數必須不小於2的情況下,割去儘量多的邊。

思路

樹形dp

一條邊可以用兩隻企鵝站,這樣的一條點對,越多越好。 如果是ans對點,2ansk,那麼只需要(k+1)2 條邊。 否則,需要ans+(k2ans)條邊。 現在問題就轉為求這樣的點對有多少。

g[x]表示以i為根的子樹中能夠兩兩配對的最大點數,不包含節點i

f[x]表示以i為根的子樹中能夠兩兩配對的最大點數,包含節點i

得到方程

g[x]=yyxmax{g[y],f[y]}

f[x]=max{yyxf[y]f[y]+g[y]+1}

程式碼

#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;int n,m,t,f[100001],g[100001],ans;
vector<int>son[100001];
bool vis[100001];
inline
int read()//輸入優化 { int f=0,d=1;char c; while(c=getchar(),c<48||c>57)if(c=='-')d=-1;f=(f<<3)+(f<<1)+c-48; while(c=getchar(),c>47&&c<58)f=(f<<3)+(f<<1)+c-48; return d*f; } inline void write(register int x){if(x>9)write(x/10);putchar(x%10+48);return
;}//輸出優化 inline void dfs(register int x,register int fa)//樹形dp { if(vis[x]) return; vis[x]=true;//記得標記 f[x]=0;g[x]=0;//初始化 int sum=0; for(register int i=0;i<son[x].size();i++) { int y=son[x][i]; if(y==fa) continue; dfs(y,x); g[x]+=max(f[y],g[y]);//動態轉移 sum+=f[y];//計算cgm f[y] } for(register int i=0;i<son[x].size();i++) { int y=son[x][i]; if(y==fa) continue; f[x]=max(f[x],sum-f[y]+g[y]+1);//動態轉移 } return; } signed main() { t=read(); while(t--) { n=read();m=read(); for(register int i=0;i<=n;i++)son[i].clear(); memset(vis,0,sizeof(vis));//初始化 for(register int i=1,u;i<n;i++) son[u=read()].push_back(i+1),son[i+1].push_back(u);//建邊 dfs(1,-1); ans=max(f[1],g[1]);//得出點對 if((ans<<1)>=m) write((m+1)>>1),putchar(10); else write(ans+m-(ans<<1)),putchar(10);//輸出 } }