DP练习总结

P5322 排兵布阵

1.对于玩家a而言,攻占第i个城堡,需要至少派出2∗a[i]+1的兵力

   如果攻占下一个玩家的城堡,那么出兵更弱的玩家自然也会被攻占

2.可以先对每个城堡的玩家兵力排序,于是就可以简化成:有i个城堡,每个城堡有s个玩家,攻占一个玩家需要派出2∗i[s]+1的兵力,获得i∗s的分数。求在有m个兵力的情况下,对于每个城堡要攻占哪一个玩家使得总分最大。

3.所以可以联想到分组背包,把每一个城堡看成一个组,每个玩家是一个组内的物品。物品的价值即为所获分数i∗s,体积为攻打所需兵力2∗i[s]+1,而背包体积就是兵力m。

代码也就很好写了:

#include <bits/stdc++.h>
using namespace std;
int s,n,m;
int C[110][110],W[110][110];
int F[20010];
void package(int V,int K,int GMC){
    for(int k=1;k<=K;k++){
    	for(int v=V;v>0;v--){
    		for(int i=1;i<=GMC;i++){
    			if(v-C[k][i]>=0&&F[v-C[k][i]]+k*i>F[v]){
    				F[v]=F[v-C[k][i]]+k*i;	
				}
			}
		}
	}
}
int main(){
    cin>>s>>n>>m;
    for(int i=1;i<=s;i++){
    	for(int j=1;j<=n;j++){
    		cin>>C[j][i];
		}
	}
    for(int i=1;i<=n;++i){
        sort(C[i]+1,C[i]+1+s);
        for(int j=1;j<=s;j++){
        	C[i][j]=C[i][j]*2+1;
		}
    }
    package(m,n,s);
    cout<<F[m]<<endl;
    return 0;
}

P6359 Cloud computing

1.按照f[i]从大到小排序,如果f[i]相等,则按照v[i] 从小到大排序(贪心),比较大的f[i]可以满足更多的需求,而当f[i]相等,自然要选择比较小的v[i]。

2.定义dp[i]​表示使用i个内核可以获得的最大利润,而该题涉及两个操作,即买入和卖出,买入的价格为了和卖出统一,就变成负数。

注意排序要将 n+m个信息进行操作,用tot来存储当前的核心数,方便进行背包处理,处理是最基础的背包问题(01 背包)。

最后枚举0~tot,求dp最大值

代码如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=4010,M=2e5+10;
int n,m,dp[M],tot,ans;
struct node{
	int c,f,v;
}a[N];
inline bool cmp(node x,node y){
	return x.f==y.f?x.v<y.v:x.f>y.f;
}
inline int max(int x,int y){
	return x>y?x:y;
}
inline int min(int x,int y){
	return x<y?x:y;
}
signed main(){
	memset(dp,128,sizeof(dp));
	dp[0]=0;
	scanf("%lld",&n);
	for(int i=1;i<=n;i++){
		scanf("%lld %lld %lld",&a[i].c,&a[i].f,&a[i].v);
		a[i].v=-a[i].v;
	}
	scanf("%lld",&m);
	for(int i=n+1;i<=n+m;i++){
		scanf("%lld %lld %lld",&a[i].c,&a[i].f,&a[i].v);
	}
	stable_sort(a+1,a+1+n+m,cmp);
	for(int i=1;i<=n+m;i++){
		if(a[i].v<0){
			for(int j=tot;j+1;j--){
				dp[j+a[i].c]=max(dp[j+a[i].c],dp[j]+a[i].v);
			}
		tot+=a[i].c;
		}
		else{
			for(int j=0;j<=tot;j++){
				dp[j]=max(dp[j],dp[j+a[i].c]+a[i].v);
			}
		}
	}
	for(int i=0;i<=tot;i++){
		ans=max(ans,dp[i]);
	}
	printf("%lld\n",ans);
	return 0;
}

P6280 Exercise G

1.可以发现如果循环了几次可以回到原地,当且仅当形成了一个环,假如设每个环的长度是 a[i],那么可以发现 k=lcm ⁡a[i]​,又知道 lcm⁡ 其实就是将那几个数分解质因数,然后取每个质数的最高次幂乘起来,所以可以枚举素数来做dp。

2.设 f(i,j)表示前 i 个素数总和为 j 的所有 k 的总和,枚举第 i 个素数的幂进行转移,因为之前并没有用过第 i 个素数,所以应把上一个状态乘上 p[i][k]​,所以直接有方程 f(i,j)=∑f(i−1,j−p[i][k]​)×p[i][k]​

3.利用滚动数组压缩并倒叙枚举,f(0)=1,最后答案是 ∑f(i)

最后呈上代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e4+10;
bool vis[N];
vector<int> p;
ll f[N]={1},m;
int n;
int main(){
	p.push_back(0);
	cin>>n>>m;
	for(int i=2;i<=n;i++){
		if(!vis[i]) p.push_back(i);
		for(int j=i*i;j<=n;j+=i){
			vis[j]=1;
		}
	}
	for(int i=1;i<p.size();i++){
		for(int j=n;j>=p[i];j--){
			int tmp=p[i];
			while(tmp<=j){
				f[j]=(f[j]+f[j-tmp]*tmp%m)%m;
				tmp*=p[i];
			}		
		}
	}
	ll ans=0;
	for(int i=0;i<=n;i++){
		ans=(ans+f[i])%m;
	}
	printf("%lld\n",ans);
	return 0;
}

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值