Luogu8595 solution

Problem

给定一个 \(n\)\(m\) 边的森林,可以进行若干次如下两种操作的一种:

  1. 连接一条边 \((u,v)\)
  2. 把所有与点 \(u\) 直接相连的点 \(v\) 之间的边 \((u,v)\) 全部断掉。

求至少进行多少次操作可以使该森林变成一条链。

Solution

首先约定:对于森林中的每一棵树的根节点为该树中编号最小的节点,并以 \(R\) 表示所有根的集合,以 \(S_u\) 表示 \(u\) 的儿子集合。

显然是一道树型 DP。

首先对于任意一个点 \(u\) 至多会对其进行 \(1\) 次操作二。原因显然:如果断掉的边有且仅有原图上的边,则断一次就可以全部断掉;如果断掉的边中有新建的边,则还不如不连这条边。

所以赛时第一反应是定义状态 \(f_{u,0/1}\) 表示将 \(u\) 及其子树变成一条以 \(u\) 为根的链且会(不会)对点 \(u\) 进行操作二的最少步数。则答案就是 \((\sum\limits_{u\in R} \min\{f_{u,0},f_{u,1}\}+1)-1\)?(把每条链头尾相接)。

然而这样少了一种情况。不妨看下图:

显然如果按上述说,则答案为 \(2\)(断掉边 \((1,2)\),连上边 \((2,3)\)),但是,如果我们把这棵树以 \(3\)(或 \(2\))为根节点,则可以看到该树本身就是一条链。

或许有人认为解决这种情况的方法是令 \(2\)(或 \(3\))为树的根节点。然而这十分难操作,这里讲一下我的方法:显然这样子的情况大抵可以概括成以点 \(u\) 为根的子树中,\(u\) 的左子树是一条链,\(u\) 的右子树也是一条链。则我们不妨再定义状态 \(f_{u,2}\) 表示刚才所述的状态。

转移方程可见赛时代码。

Code

点击查看代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define INFLL 0x3f3f3f3f3f3f3f3f
//#define int long long
#define PII pair<int,int>
#define rep(k,l,r) for(int k=l;k<=r;++k)
#define per(k,r,l) for(int k=r;k>=l;--k)
#define cl(f,x) memset(f,x,sizeof(f))
using namespace std;
const int N=2e6+5;
int head[N],len;
struct node {
	int to,nxt;
}; node edge[N<<1];
void add_edge(int u,int v) {
	edge[++len]={v,head[u]}; head[u]=len;
}
int f[N][3],ans;
bool used[N];
void dfs(int u,int from) {
	used[u]=true;
	//solve f[u][0] - 叉掉 u
	int tot=0,minn1=INF,minn2=INF,cnt=0;
	f[u][0]=1; f[u][2]=INF;
	for(int i=head[u];i;i=edge[i].nxt) {
		int v=edge[i].to;
		if(v!=from) {
			dfs(v,u); f[u][0]+=min(f[v][0],min(f[v][1],f[v][2]))+1; tot+=f[v][0]+1; ++cnt;
			int val=f[v][1]-(f[v][0]+1);
			if(val<minn1)
				minn2=minn1,minn1=val;
			else if(val<minn2)
				minn2=val;
		}
	}
	//solve f[u][1] - 不叉掉 u,但叉掉所有的儿子
	f[u][1]=tot;
	//solve f[u][1] - 不叉掉 u,保留一条主链
	f[u][1]=min(f[u][1],tot+minn1);
	//solve f[u][2] - 不叉掉 u,保留两条链
	if(cnt>=2)
		f[u][2]=min(f[u][2],tot+minn1+minn2);
//	printf("(%d,%d) %d %d %d\n",u,from,f[u][0],f[u][1],f[u][2]);
}
signed main() {
	int n,m;
	scanf("%d%d",&n,&m);
	rep(i,1,m) {
		int u,v;
		scanf("%d%d",&u,&v);
		add_edge(u,v);
		add_edge(v,u);
	}
	rep(i,1,n) {
		if(!used[i]) {
			dfs(i,0); ans+=min(f[i][0],min(f[i][1],f[i][2]))+1;
		}
	}
	printf("%d\n",ans-1);
	return 0;
}
posted @ 2022-10-23 20:35  lsj2009  阅读(40)  评论(0)    收藏  举报