专项训练 dp

作者在做题的时候深感自己dp水平的低下(几近为零),于是尝试逼迫自己搞懂每道题并写一点做题记录,本质上是为了避免自己成为只会抄题解的机器。。

把所有dp题单总结放一起,留给考前复习用。

2024.10 提高组专题dp4

1.[PA2021] Od deski do deski

首先,对于一个合法的序列f,若f+x为合法序列,那么f+x+x必然也为合法序列。

其次状态设计,设 \(f_{i,j,0/1}\) 表示序列的前 \(i\) 个,在后面放 \(j\) 种数可以变成合法的(注意这一位的设计会比较诡异,但是为了方便转移),0/1表示当前这个序列是否合法。

考虑转移,对于第 \(i+1\) 位进行分讨,如果当前序列是合法的,则有
若下一位合法:$$f_{i+1,j,1}+=f_{i+1,j,1}+f_{i,j,1}*j$$

若下一位不合法:$$f_{i+1,j+1,0}+=f_{i,j,1}*(m-j)$$


2025.1 提高dp

1.Min-Fund Prison (Medium)

卡了将近4h,死活不会写转移......

容易发现,为了使整张图联通,我们需要添加的边数是一定的,为图上联通块的个数 \(-1\)

所以原式中 \(k*c\) 的值是不变的,我们只需要使 \(x^2+y^2\) 的值最小。有 \(x^2+y^2=(x+y)*(x-y)\)\(x+y\) 不变,为图的大小,所以我们只需要最小化 \(x\)\(y\) 的差即可。

因为最终要分成两个联通块,我们删掉得便一定是割边或我们加上的边。

考虑对原图进行边双缩点,缩完点后得到的图一定是一个森林,记 \(x\) 所在的联通块大小为 \(siz_x\) ,断开割边后两部分值一定是 \(siz_x\) 和 图的大小减去 \(siz_x\)

考虑可行性dp。设 \(dp_{i,j,0/1}\) 表示第¥\(i\) 个联通块,其中一个联通块大小为 \(j\) ,是否断边。dp 值为 \(1\) 表示可行,\(0\) 为不可行。初始时 \(dp_{0,0,0}=1\)

接下来状态转移,如果没有断开,则直接连到对应联通块即可。

若连向第二个联通块,则

\[dp_{i,j,0}\mid=dp_{i-1,j,0} \]

\[dp_{i,j,1}\mid=dp_{i-1,j,1} \]

若连向第一个联通块,则

\[dp_{i,j,0}\mid=dp_{i-1,j-siz_{rt},0} \]

\[dp_{i,j,1}\mid=dp_{i-1,j-siz_{rt},1} \]

若断边,则必然会分成两部分,一部分,则有

\[dp_{i,j,0}\mid=dp_{i-1,j-(siz_{rt}-siz_{x}),0} \]

\[dp_{i,j,1}\mid=dp_{i-1,j-siz_{x},1} \]

注意这里使用或进行转移。

点击查看代码
/*注意到加边使其联通的边数其实是不变的,只需要最小化x^2+y^2即可
当原图是一个边双时无解
边双缩点,然后进行可行性dp
设dp[i][j][k]表示 前i个树 删/没删完边 后能否达到k
注意到边数很小,所以不需要优化吧......*/
#include<bits/stdc++.h> 
#define int long long 
using namespace std;
const int maxn=305; 
int T,n,m,c;
struct edge{
	int to,nxt;
}e[2*maxn];
int head[maxn],edgenum=1;
void add_edge(int u,int v){
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	head[u]=edgenum;
}
int dp[maxn][maxn][3];

int dfn[maxn],low[maxn],tim,sum,siz[maxn],col[maxn];
stack<int> s;
void tarjan(int u,int fa){//边双缩点 	
	dfn[u]=low[u]=++tim;
	s.push(u);
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(!dfn[v]){
			tarjan(v,i);
			low[u]=min(low[u],low[v]); 
		}
		else if(i!=(fa^1)) low[u]=min(low[u],dfn[v]); 
	}
	if(low[u]==dfn[u]){
		sum++;
		int y;
		do{
			y=s.top();
			s.pop();
			siz[sum]++;
			col[y]=sum;
		}while(y!=u);
	}
}
vector<int> vec[maxn];
int rt[maxn],st[maxn];
bool vis[maxn];
void dfs(int u,int root){
	vis[u]=1;
	rt[u]=root;
	for(int v:vec[u]){
		if(vis[v]) continue;
		dfs(v,root);
		siz[u]+=siz[v];
	}
}
void clear(){
	memset(e,0,sizeof(e));
	memset(head,0,sizeof(head));
	memset(dfn,0,sizeof(dfn));
	memset(dp,0,sizeof(dp));
	memset(low,0,sizeof(low));
	memset(col,0,sizeof(col));
	memset(siz,0,sizeof(siz));
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++){
		for(int j=0;j<vec[i].size();j++) vec[i][j]=0;
	}
	tim=0,sum=0;
	edgenum=1;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>T;
	while(T--){
		cin>>n>>m>>c;
		clear();
		for(int i=1,x,y;i<=m;i++){
			cin>>x>>y;
			add_edge(x,y);
			add_edge(y,x);
		}
		for(int i=1;i<=n;i++){
			if(!dfn[i]) tarjan(i,0);
		}
		if(sum==1){
			cout<<-1<<endl;
			continue;
		}
		for(int i=1;i<=n;i++){
			for(int j=head[i];j;j=e[j].nxt){
				if(col[e[j].to]!=col[i]){
					vec[col[e[j].to]].push_back(col[i]);
					vec[col[i]].push_back(col[e[j].to]);
				}
			}
		}
		int cnt=0;
		for(int i=1;i<=sum;i++){
			if(!vis[i]){
				dfs(i,i);
				st[++cnt]=i;//共有cnt棵树 
			}
		}
		dp[0][0][0]=1;
		c=(cnt-1)*c;
		for(int i=1;i<=cnt;i++){
			int tot=siz[st[i]];
			for(int j=0;j<=n;j++){//枚举大小 
				if(j>=tot){
					dp[i][j][0]|=dp[i-1][j-tot][0];
					dp[i][j][1]|=dp[i-1][j-tot][1];
				}
				dp[i][j][0]|=dp[i-1][j][0];
				dp[i][j][1]|=dp[i-1][j][1];
				for(int k=1;k<=sum;k++){
					//k不属于第i块 
					if(rt[k]!=st[i] || k==st[i]) continue;
					if(j>=tot-siz[k]) dp[i][j][1]|=dp[i-1][j-tot+siz[k]][0];
					if(j>=siz[k]) dp[i][j][1]|=dp[i-1][j-siz[k]][0];
				}
			}
		}
		long long ans=1e17;
		for(int i=0;i<=n;i++){
			if(dp[cnt][i][0] || dp[cnt][i][1]){
				ans=min(ans,1ll*i*i+1ll*(n-i)*(n-i));
			}
		}
		cout<<ans+c<<endl;
	}
}
posted @ 2024-10-16 21:28  Aapwp  阅读(16)  评论(0编辑  收藏  举报
我给你一个没有信仰的人的忠诚