Living-Dream 系列笔记 第78期

Posted on 2024-09-11 22:17  _XOFqwq  阅读(3)  评论(0编辑  收藏  举报

常用 dp 状态:\(dp_i\) 表示以 \(i\) 结尾的 XXX / 前 \(i\) 个元素的 XXX。

涉及的类型(由易到难):线性 dp,背包,区间 dp,树形 dp(换根 dp),状压 dp,dp 的各类优化(数据结构优化、斜率优化、四边形不等式优化......)。

背包问题:01 背包、完全背包、分组背包、多重背包、树上背包。

P1455

并查集维护配套,按照所有套餐进行 01 背包即可。

总结:本题是并查集与 01 背包的结合,具有综合性。

code
#include<bits/stdc++.h>
using namespace std;

const int N=1e4+5;
int n,m,w;
int c[N],d[N];
int p[N],fa[N],dp[N];

int fnd(int x){
	return (fa[x]==x?x:fa[x]=fnd(fa[x]));
}
void uni(int x,int y){
	x=fnd(x),y=fnd(y);
	if(x!=y){
		fa[x]=y;
		c[y]+=c[x];
		d[y]+=d[x];
	}
}

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>m>>w;
	for(int i=1;i<=n;i++)
		cin>>c[i]>>d[i],fa[i]=i;
	for(int i=1,u,v;i<=m;i++)
		cin>>u>>v,uni(u,v);
	int cnt=0;
	for(int i=1;i<=n;i++){
		if(fa[i]==i)
			p[++cnt]=i;
    }
	for(int i=1;i<=cnt;i++)
		for(int j=w;j>=c[p[i]];j--)
			dp[j]=max(dp[j],dp[j-c[p[i]]]+d[p[i]]);
	cout<<dp[w];
	return 0;
}

CF507A

定义 \(path_{i,j}\) 记录背包容量为 \(j\) 时物品 \(i\) 是否转移成功,每次转移时标记即可。

最后递归地寻找答案,具体见代码。

总结:本题是记录决策点的 01 背包,以后要求记录决策点的 dp 都可以仿照着这样写。

code
#include<bits/stdc++.h>
using namespace std;

const int N=1e2+5,K=1e4+5;
int n,k,cnt;
int c[N],dp[K],ans[N];
bool path[N][K];

void getans(int cur,int l){
    if(!cur)
        return;
    if(path[cur][l]){
        ans[++cnt]=cur;
        getans(cur-1,l-c[cur]);
    }
    else
        getans(cur-1,l);
}

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>k;
	for(int i=1;i<=n;i++)
		cin>>c[i];
	for(int i=1;i<=n;i++){
		for(int j=k;j>=c[i];j--){
			if(dp[j]<dp[j-c[i]]+1){
				dp[j]=dp[j-c[i]]+1;
                path[i][j]=1;
			}
		}
	}
	getans(n,k);
    cout<<cnt<<'\n';
    for(int i=1;i<=cnt;i++)
        cout<<ans[i]<<' ';
	return 0;
}

AT_abc054_d

既然有两种类型的物品,我们就设两维,令 \(dp_{i,j}\) 表示选重量为 \(i\) 的 A 元素且选重量为 \(j\) 的 B 元素所能获得的最大价值。仍然像 01 背包一样转移即可。

最后,我们枚举所有状态,对于所有满足要求的状态取最小值即可。

总结:本题是具有多个维度的 01 背包。谨记:当存在多个物品时,考虑增设维度。

code
#include<bits/stdc++.h>
using namespace std;

const int N=5e2+5;
int n,ma,mb,sa,sb;
int a[N],b[N],c[N];
int dp[N][N];

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>ma>>mb;
	for(int i=1;i<=n;i++){
		cin>>a[i]>>b[i]>>c[i];
		sa+=a[i],sb+=b[i];
	}
	memset(dp,0x3f,sizeof dp);
	dp[0][0]=0;
	for(int i=1;i<=n;i++)
		for(int j=sa;j>=a[i];j--)
			for(int k=sb;k>=b[i];k--)
				dp[j][k]=min(dp[j][k],dp[j-a[i]][k-b[i]]+c[i]);
	int ans=1e9;
	for(int i=1;i<=sa;i++)
		for(int j=1;j<=sb;j++)
			if(j*ma==i*mb)
				ans=min(ans,dp[i][j]);
	cout<<(ans==1e9?-1:ans);
	return 0;
}

AT_abc118_d

首先这显然是个完全背包,我们先按照完全背包的方式做,令 \(dp_i\) 表示用 \(i\) 根火柴能拼成的最大位数。

显然,位数越大越好,位数相同的情况下数码越大越好,所以我们将 \(a\) 先排个序。

然后从 \(n\) 开始(这样保证了位数最大),每次在 \(m\) 个数码中选取能拼成 \(dp_n\) 这么多位且当前这一位为 \(a_i\) 的最大的那个 \(a_i\),记录下它即可。

总结:本题是完全背包与贪心的结合,这启发我们可以在 dp 之后贪心选取最优方案,这两个东西并不是水火不容。

code
#include<bits/stdc++.h>
using namespace std;

const int N=1e4+5;
const int num[]={0,2,5,5,4,5,6,3,7,6};
int n,m,tot;
int a[N],dp[N],ans[N];

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=m;i++)
		cin>>a[i];
    memset(dp,0xcf,sizeof dp);
    dp[0]=0;
	for(int i=1;i<=m;i++)
		for(int j=num[a[i]];j<=n;j++)
			dp[j]=max(dp[j],dp[j-num[a[i]]]+1);
	//int cur=dp[n];
	sort(a+1,a+m+1);
	while(n){
        //cout<<cur<<'\n';
		int p=0;
		for(int i=1;i<=m;i++)
			if(n>=num[a[i]]&&dp[n-num[a[i]]]+1==dp[n])
				p=i;
		n-=num[a[p]];
		ans[++tot]=a[p];
	}
	for(int i=1;i<=tot;i++)
		cout<<ans[i];
	return 0;
}

CF632E

正解是 FFT / NTT,但是朴素完全背包也可以过。

这题并不要求最大价值,于是我们换一种状态定义:令 \(dp_i\) 表示凑成价值为 \(i\) 需要的最少商品数,转移 \(dp_i=\min(dp_i,dp_{i-a_j}+1)\),这样方便统计答案。

但是题目需要恰好取 \(k\) 件商品,于是我们可以将每个物品的价值全都减去最小的那个商品的价值,这样任意商品数 \(\le k\) 的方案都可以通过不断加上最小的那个商品从而凑成 \(k\) 个,之后在加回去就可以了。

总结:本题是完全背包的变种,且用到了一个小技巧。我们必须要依据题目关心的东西来设计状态。

code
#include<bits/stdc++.h>
using namespace std;

const int N=1e6+5;
int n,k,mx,mn=1e9;
int a[N],dp[N];

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		mn=min(mn,a[i]);
		mx=max(mx,a[i]);
	}
	mx-=mn;
	for(int i=1;i<=n;i++)
		a[i]-=mn;
	memset(dp,0x3f,sizeof dp);
	dp[0]=0;
	for(int i=0;i<=k*mx;i++)
		for(int j=1;j<=n;j++)
			if(i>=a[j])
				dp[i]=min(dp[i],dp[i-a[j]]+1);
	for(int i=0;i<=k*mx;i++)
		if(dp[i]<=k)
			cout<<i+k*mn<<' ';
	return 0;
}