dp练习总结(2)

CF295C Greg and Friends

1.令船最多的载重(即题目中的 k)设为 m,50 千克的人的个数为 x,100 千克的人的个数为 y。考虑最劣情况,发现就是一个人把另一个拉过去后又自己回来然后又拉下一个人,以此类推,因此最多又 2n 个往返次数,即 4n 个过程(指船从一边到另一边的过程,后文均延用此定义),所以当过程数大于 4n 时,就说明无解。

2.设 f(i,j,k) 表示现在是第 i 个过程,这个过程结束后,还有 j 个 50 千克,k 个 100 千克的人没有过河的方案数,很明显一开始没有人渡河,所以 f(0,x,y)=1

   (1)如果 i 是奇数,说明现在是到达对岸的过程,我们枚举这一次渡河的 50 千克的人数 a 与 100 千克的人数 b,则 f(i,j,k)​ 将要增加的贡献为 f(i-1,j+a,k+b)×C(j+a,a)×C(k+b,b)​。

   (2)如果 i 是偶数,说明现在是从对岸回来的过程,我们再次枚举这一次渡河的 50 千克的人数 a 与 100 千克的人数 b,则 f(i,j,k)​ 将要增加的贡献为 f(i-1,j-a,k-b)×C(x−j+a,a)×C(y−k+b,b)。

3.最后,每当进行 1 步骤的转移时,我们判断 f(i,0,0)​ 是否不为 0,如果不为 0,则输出答案,最后如果 i>4n 说明无解。

代码如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=60,mod=1e9+7;
int f[4*N][N][N],C[N][N];
int x,y,n,m;
inline int read(){
	char c=getchar();
	int f=1,ans=0;
	while(c<48||c>57) f=(c==45?f=-1:1),c=getchar();
	while(c>=48&&c<=57) ans=(ans<<1)+(ans<<3)+(c^48),c=getchar();
	return ans*f;
}
signed main(){
	n=read(),m=read();
	for (int i=1;i<=n;i++){
		int xx=read();
		if (xx==50) x++;
		else y++;
	}
	C[0][0]=1;
	for (int i=1;i<=n;i++){
		C[i][0]=1;
		for (int j=1;j<=n;j++)
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
	}
	f[0][x][y]=1;
	for (int i=1;i<=4*n;i++)
		for (int j=0;j<=x;j++)
			for (int k=0;k<=min(y,n-j);k++){
				if (i&1){
					for (int a=0;a<=x-j;a++)
						for (int b=0;b<=y-k;b++)
							if (!(a==0&&b==0)&&50*a+100*b<=m)
								f[i][j][k]=(f[i][j][k]+f[i-1][j+a][k+b]*C[j+a][a]%mod*C[k+b][b]%mod)%mod;
					if (f[i][0][0]){
						cout <<i<<endl<<f[i][0][0];
						return 0;
					}
				}
				else{
					for (int a=0;a<=j;a++)
						for (int b=0;b<=k;b++)
							if (!(a==0&&b==0)&&50*a+100*b<=m)
								f[i][j][k]=(f[i][j][k]+f[i-1][j-a][k-b]*C[x-j+a][a]%mod*C[y-k+b][b]%mod)%mod;
				}
			}
	puts("-1\n0");
    return 0;
}

P5424 snakes G

1.设f[i][j]表示前ii条蛇,改变j次大小所得到的最小剩余空间

g[i][j]表示区间[i,j]的空余空间

那么可以得到如下转移方程:f[i][j]=min(f[i][j],f[k][j−1]+g[k+1][i])

k为枚举的一个断点,表示[k+1,i]这段区间用同一大小的网。

2.根据定义,一段区间的空余空间一定是用区间中的最大值作为网的大小,减去其他数字得到的和。即g[i][j]=maxn∗(j−i+1)−sum(i,j),其中区间和可以用前缀和O(1)处理。由于我们枚举断点的时候会从i−1i−1向下逐一枚举,那么我们可以在枚举断点的时候同时更新最大值。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int MAX=410;
int n,m;
int a[MAX],f[MAX][MAX],s[MAX];
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        s[i]=s[i-1]+a[i];
    }
    m++;
    memset(f,0x3f,sizeof(f));
    f[0][0]=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=min(m,i);j++){
            int mx=a[i];
            for(int k=i-1;k>=0;k--){
                f[i][j]=min(f[i][j],f[k][j-1]+mx*(i-k)-(s[i]-s[k]));
                mx=max(mx,a[k]);
            }
        }
    }
    int ans=0x3f3f3f3f;
    for(int i=0;i<=m;i++){
        ans=min(ans,f[n][i]);
    }
    cout<<ans<<endl;
    return 0;
}
  • 9
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值