20220729 dp训练

时间安排

8:00—8:10 读题:T1应该是区间dp,但是小细节比较多;T2标题提示的很明显了,是个背包问题(感觉比较简单准备放到最后再做);T3看到的第一眼没什么思路,先做一道题如果还有时间再想想
8:10—8:40 码T1,结果发现普通的dp套路做不来,写了个废掉的代码
8:40—9:00 又理解了一下T3,简单码了一下发现输出一直是-1.放弃准备磕T1
9:00—9:30 想到了T1一种新的状态,感觉可行,疯狂码ing
9:30—9:50 手造了两组数据,调T1
9:50 突然说要延时20分钟
9:50—10:10 终于过了手造数据
10:10 发现来不及码T2了只好匆忙写了个T3的骗分,暴力也没写完
最终得分:100+0+2=102

题目分析

T1 格斗俱乐部

题目描述
格斗俱乐部是格斗爱好者的一个组织,在这里,格斗者们能通过与别的成员进行格斗来释放自己的压力
与轻松自己的情绪。
最近俱乐部举行了一场比赛,该比赛有N位选手参加,他们将围成一个圆圈,每一场比赛圈内任意的两
位相邻的选手均可进行相互的格斗,胜利者将留在圈内进入下轮比赛而失败者则直接被送往医院(没有
平局)。比赛是残酷的,最后圈内将只剩下一位选手,他将是总冠军。
我们做个奇怪的假设,两位选手进行格斗,他们比赛的结果总是确定的。虽然俱乐部的成员们都很喜欢
格斗,但是他们仍然很希望能获得总冠军。
现在你通过统计已经知道了任意两位选手格斗的结果,你有责任告诉每位选手,如果赛程合适安排的
话,他是否可能成为总冠军。
输入格式
数据第一行是一个整数 N,((1<=N<=40)),表示比赛的选手数量。
接下来给出一个 N*N的“ 0”、“1 ”矩阵 A(行内用空格隔开),第i行第j列为 1表示选手i能战胜选手j,
否则选手j能战胜选手i。
你可以假定 A i j Aij Aij A j i Aji Aji(i≠j)均是不同的且 A i i Aii Aii=0。比赛开始时所有选手按顺时针方向由编号 到编
号 站成一个圈,初始时编号 与编号 的选手是相邻的。
输出格式
输出包含 行,每行为一个整数“0”或“1”,“1”表示第i号选手有可能成为冠军,“0”表示不可能。
input
3
0 1 1
0 0 1
0 0 0
output
1
0
0
分析:判断x是否能成为冠军,开一个链,x点拆成两个,那编号为x的人能成为冠军的条件是他能和自己相邻。这样,在连续几个人的链中,只须考虑头尾两个人能否相邻,中间的则不予考虑。设f[i,j]记录i和j能否相邻,能则为1,则问题转化为了找到一个k,使得i和k,k和j均能相邻,而i或者j能打败k。

#include<bits/stdc++.h>
using namespace std;
#define xyg(i,j,k) for(int i=j;i<=k;i++)
const int maxn=1010;
int n;
int s[maxn];
int f[maxn][2*maxn],g[maxn][maxn];
int main()
{
	freopen("data.in","r",stdin);
	freopen("data.out","w",stdout);
	memset(f,0,sizeof(f));
	memset(g,0,sizeof(g));
	scanf("%d",&n);
	xyg(i,1,n)
		xyg(j,1,n)
			scanf("%d",&g[i][j]);
	xyg(i,1,n*2)
	{
		f[i][i+1]=1; //第i个人和第i+1个人开始一定是相邻的 
	 	s[i]=i-(i>n)*n;
	}
	xyg(x,1,n)
	{
		for(int i=1;x+i<=n*2;i++)
		{
			int j=x+i;
			xyg(k,i+1,j-1)
			{
				if(f[i][k]&&f[k][j])
				{
					if(g[s[i]][s[k]]||g[s[j]][s[k]])  //i能打败k或者j能打败k都能使i,j相邻 
					{
						f[i][j]=1;
						break;
					}	
				}
			}
		}
	}
	xyg(i,1,n) cout<<f[i][i+n]<<endl;
	return 0;
}

T2 背包问题

Description
从T组物品中选出一些物品,放入背包中,求剩余空间的最小值。
限制条件:从每组物品中挑选物品必须要选取连续的一段。就是说,如果这组物品共有n个: 物品1、物品2、物品3、…、物品n,那么只能选取物品i、物品i+1、…、物品j,其中1<=i<=j<=n,或者不选。
Input
第一行为两个用空格隔开的正整数v和T。表示背包的空间和物品的组数。接下来有T行,每行先是一个正整数ni,表示这组物品有ni个,然后ni个正整数,表示每个物品的大小。
Output
仅一个数,表示剩余空间的最小值。
Sample Input
100 3
3 7 6 8
2 80 70
4 101 108 103 150
Sample Output
6
【样例说明】
第1组选6、8,第2组选80,第3组不选。
【限制】
60%的数据满足:1 <= ni <= 10
100%的数据满足:1 <= ni <= 100,1<=v<=5000,1<=T<=10

分析:
每组物品,只能选连续的一段且只能选一次,所以我们可以想到DP.
b [ i , j ] b[i,j] b[i,j]表示第i组物品,是否出现连续一段总体积为j的情况。
f [ i , j ] f[i,j] f[i,j]表示前T组物品,用了j体积装物品,能装载的物品的最大值。
用一个前缀和去统计,然后暴力去枚举判断就好了。
然后状态转移方程:
f [ i , j ] : = m a x ( f [ i , j − 1 ] , m a x ( f [ i − 1 , j − k ] + k ) ) f[i,j]:=max(f[i,j-1],max(f[i-1,j-k]+k)) f[i,j]:=max(f[i,j1],max(f[i1,jk]+k));
最后在这里面找出一个最大值,然后用 v − m a x v-max vmax

#include<bits/stdc++.h>
using namespace std;
const int maxn=110;
#define xyg(i,j,k) for(int i=j;i<=k;i++)
int v,t;
int a[maxn][maxn],sum[maxn][maxn],r[maxn][100010];
int f[5000],tot[maxn],n[maxn];
int main()
{
	freopen("bag.in","r",stdin);
	freopen("bag.out","w",stdout);
	cin>>v>>t;
	memset(f,-10,sizeof(f));
	f[0]=0;
	xyg(i,1,t)
	{
		scanf("%d",&n[i]);
		xyg(j,1,n[i])
			scanf("%d",&a[i][j]);
	}
	xyg(i,1,t)
		xyg(j,1,n[i])
			sum[i][j]=sum[i][j-1]+a[i][j];
	xyg(i,1,t)
	{
		xyg(j,1,n[i]) 
			xyg(k,1,j)
				r[i][++tot[i]]=sum[i][j]-sum[i][k-1]; 
		r[i][++tot[i]]=0;
	}
	xyg(i,1,t)
		for(int j=v;j>=0;j--)
			xyg(k,1,tot[i])
				if(j>=r[i][k])
					f[j]=max(f[j],f[j-r[i][k]]+r[i][k]);
	for(int i=v;i>=0;i--)
	{
		if(f[i]>0)
		{
			cout<<v-f[i];
			break;
		}
	} 
	return 0;
}

T3 【atcoder abc142E】Get Everything

Description:
有n个野怪,给定m个武器的价钱和它能打的野怪,求打完所有野怪要的最少花费
输入格式
第一行输入野怪的个数 n 和武器的个数 m
接下来共有m组,第i组描述第i把武器的信息:
对于每一组武器:
第一行输入一个a和b, 分别代表这种武器的价钱和这把武器能打死几个野怪
第二行输入b个编号,代表这个技能能打死野怪的编号
输出格式
输出最小的花费打死所有的野怪. 如果不能全部打死,输出-1
样例数据
input
2 3
10 1
1
15 1
2
30 2
1 2
output
25
购买第一把武器和第二把武器可以打死所有的野怪,共花费最小消耗25

分析
宝箱的数量较小,可以用2进制状态压缩来表示每个宝箱持有的状态。
例如1号宝箱就是00000001,2号就是00000010…依次往后。
现在假设你当前钥匙能开的宝箱的二进制是now,当前的钥匙为i ii号钥匙
定义 d p ( b i t ) dp(bit) dp(bit)为该二进制下解开这些宝箱所需要的最少钱数
那么此时 d p ( b i t ∣ n o w ) = m i n ( d p ( b i t ∣ n o w , d p ( b i t ) + a [ i ] ) dp(bit|now)=min(dp(bit|now,dp(bit)+a[i]) dp(bitnow)=min(dp(bitnow,dp(bit)+a[i])
初始状态 d p ( 0 ) = 0 dp(0)=0 dp(0)=0其他的状态初始化为inf,最终答案就是 d p ( ( 1 < < N ) − 1 ) dp ( ( 1 < < N ) − 1 ) dp((1<<N)1) 的值

#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
int n,m;
int a[1111], b[1111];
int c[1111][20];
vector<int> dp(5050, inf);
int main() 
{
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
    cin>>n>>m;
    for (int i=1;i<=m;i++) 
	{
        cin>>a[i]>>b[i];
        for(int j=1;j<=b[i];j++) cin>>c[i][j];
    }
    dp[0]=0;
    for (int i=1;i<=m;i++)
    {
        int now=0;
        for(int j=1;j<=b[i];j++)
            now|=(1<<(c[i][j]-1));
        for(int bit=0;bit<(1<<n);bit++) 
        	dp[bit|now]=min(dp[bit|now],dp[bit]+a[i]);
    }
    if(dp[(1<<n)-1]==inf) cout<<-1<<endl;
	else cout<<dp[(1<<n)-1]<<endl;
    return 0;
}

总结&反思

下次先把简单的做了,不至于到最后磕一道题导致没时间打暴力

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值