[六省联考2017]摧毁"树状图"

Description:

给定一棵树,要删除两条不相交的链上的所有点,求删除后最大联通块个数

Hint:

\(n \le 10^5\)

Solution:

毒瘤树型dp,设链的dp确实蛮套路,但是这题的转移是谁能想的不重不漏?
详见代码:

#include <map>
#include <set>
#include <stack>
#include <cmath>
#include <queue>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#define ls p<<1 
#define rs p<<1|1
using namespace std;
typedef long long ll;
const int mxn=2e5+5;
int T,n,m,ans,cnt,opt,in[mxn],hd[mxn],vis[mxn];
int dp[mxn][4];

inline int read() {
	char c=getchar(); int x=0,f=1;
	while(c>'9'||c<'0') {if(c=='-') f=-1;c=getchar();}
	while(c<='9'&&c>='0') {x=(x<<3)+(x<<1)+(c&15);c=getchar();}
	return x*f;
}
inline void chkmax(int &x,int y) {if(x<y) x=y;}
inline void chkmin(int &x,int y) {if(x>y) x=y;}

struct ed {
	int to,nxt;
}t[mxn<<1];

inline void add(int u,int v) {
	t[++cnt]=(ed) {v,hd[u]}; hd[u]=cnt; ++in[v];
}

// 路径有折返,链表示端点为根的不折返路径
// dp[u][0]表示过根的一条链
// dp[u][1]表示不过根的一条路径
// dp[u][2]表示过根的一条路径
// dp[u][3]表示过根的一条链+一条路径

void dfs(int u) {
	dp[u][0]=dp[u][2]=dp[u][3]=in[u]; //删掉该点本身
	dp[u][1]=1; vis[u]=1; /*起始状态,只能删叶节点*/ int res=0;
	for(int i=hd[u];i;i=t[i].nxt) {
		int v=t[i].to;
		if(vis[v]) continue ;
		dfs(v); 
		chkmax(ans,dp[u][3]+dp[v][0]-(u==1)); 
		// 由于这两个状态没有算u上方的贡献,故要讨论u==1,下同理
		chkmax(ans,dp[u][0]+dp[v][3]-(u==1));
		chkmax(ans,dp[u][1]+dp[v][2]);
		// 不用加u上方的贡献了,因为dp[u][1]已经包含了
		chkmax(ans,dp[u][1]+dp[v][1]-1);
		// 必须减,u上方被算了两次
		chkmax(ans,dp[u][2]+dp[v][1]-(u==1));
		chkmax(ans,dp[u][2]+dp[v][2]-(u==1));
		
		chkmax(dp[u][1],dp[v][1]);
		chkmax(dp[u][1],dp[v][2]+1);
		// 过根->不过根,联通块+1
		chkmax(dp[u][3],dp[u][0]+dp[v][2]-1);
		// u多算了v这个联通块,减去,下同理
		chkmax(dp[u][3],dp[u][2]+dp[v][0]-1);
		chkmax(dp[u][3],dp[v][3]+in[u]-1);
		chkmax(dp[u][3],dp[v][0]+in[u]+res-2);
		// 比较复杂,但总之是减去算重的块
		chkmax(dp[u][3],dp[u][0]+dp[v][1]-1);
		chkmax(dp[u][2],dp[u][0]+dp[v][0]-1);
		chkmax(dp[u][0],dp[v][0]+in[u]-1);
		
		chkmax(dp[u][2],dp[u][0]);
		chkmax(dp[u][3],dp[u][2]); 
		// 状态的包含关系
		
		chkmax(res,dp[v][1]); chkmax(res,dp[v][2]);
		// 更新res
	}
} // 注意dp顺序,不要自己把自己算了

int main()
{
	T=read(); opt=read(); int u,v;
	while(T--) {
		n=read(); cnt=ans=0;
		for(int i=1;i<=opt*2;++i) u=read();
		memset(in,0,sizeof(in));
		memset(hd,0,sizeof(hd));
		memset(vis,0,sizeof(vis));
		for(int i=2;i<=n;++i) {
			u=read(); v=read(); 
			add(u,v); add(v,u); --in[i]; // 减去父亲贡献的度数
		}
		dfs(1); printf("%d\n",ans);
	}
    return 0;
}
`cpp

posted @ 2019-03-23 08:58  cloud_9  阅读(243)  评论(0)    收藏  举报