【洛谷1489】猫狗大战&【BZOJ1899】【ZJOI2004】Lunch午餐

题目描述

新一年度的猫狗大战通过SC(星际争霸)这款经典的游戏来较量,野猫和飞狗这对冤家为此已经准备好久了,为了使战争更有难度和戏剧性,双方约定只能选择Terran(人族)并且只能造机枪兵。

比赛开始了,很快,野猫已经攒足几队机枪兵,试探性的发动进攻;然而,飞狗的机枪兵个数也已经不少了。野猫和飞狗的兵在飞狗的家门口相遇了,于是,便有一场腥风血雨和阵阵惨叫声。由于是在飞狗的家门口,飞狗的兵补给会很快,野猫看敌不过,决定撤退。这时飞狗的兵力也不足够多,所以没追出来。

由于不允许造医生,机枪兵没办法补血。受伤的兵只好忍了。555-

现在,野猫又攒足了足够的兵力,决定发起第二次进攻。为了使这次进攻给狗狗造成更大的打击,野猫决定把现有的兵分成两部分,从两路进攻。由于有些兵在第一次战斗中受伤了,为了使两部分的兵实力平均些,分的规则是这样的:1)两部分兵的个数最多只能差一个;2)每部分兵的血值总和必须要尽可能接近。现在请你编写一个程序,给定野猫现在有的兵的个数以及每个兵的血格值,求出野猫按上述规则分成两部分后每部分兵的血值总和。

输入输出格式

输入格式:

第一行为一个整数n(1<=n<=200),表示野猫现在有的机枪兵的个数。以下的n行每行一个整数,表示每个机枪兵的血格(1<=ai<=40)。

输出格式:

一行,为两个整数,表示分成两部分后每部分兵的血值总和

输入输出样例

输入样例#1:
3
35
20
32
输出样例#1:
35 52

说明

TO 狗狗:这道题的数据范围我已经尽量按星际的游戏规则来了,如果你再固执于由于机枪兵的攻击力一定使不能达到某些血格值或者游戏中一定要造农民不能使机枪兵的人数达到200的话,我只能决定将那场猫狗大战的录像公开于世人了!!!

题解

先说正解,我们用f[i][j][k]表示选了前i个兵中j个兵是否可以构成血量为k,之后就转化为一个背包问题的变形,优化一维成为f[i][j]表示选i个兵能否构成j的血量 转移方程 f[i][j]=f[i][j]|f[i-1][j-a[i];边界条件 f[0][0]=1 即选取0个人构成0的血量是可行的。最后统计答案是从sum(所以兵血量和)除以2开始枚举,发现的第一个满足的方案时就输出解。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define inf 100000000
using namespace std;
const int maxn=300;
int a[maxn],sum,dp[maxn][50010];
int main(){
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		sum+=a[i];
	}
	
	dp[0][0]=1;
	
	for(int i=1;i<=n;i++)
	for(int j=i;j>=1;j--)
	for(int k=sum;k>=a[i];k--)--)//枚举构成的血量,至于为什么这两行要倒叙枚举
	//就像01背包优化为一维时,就是倒叙枚举,因为物品只有一个,本题的人也各只有一个,所以为避免重复计算,故倒叙枚举; 
	dp[j][k]|=dp[j-1][k-a[i]];//这行就是前面有一个状态可以这个状态就可以

	int i;
	for(i=sum/2;i>=0;i--)
	if(dp[n/2][i])
	break;
	
	printf("%d %d\n",i,sum-i);
	return 0;
}

现在说一下我开始错误的想法。写在代码上了。

//dp[i][j][k]表示前i个人第一组有j个的第一组的数和第二个的数。
//但这个方程并不对,因为并不是有局部最优推出全局最优(dp性质),可能当前状态这是最优的,但是假设再加上另一个很大的数的时候就不是最优的了
//它可能不是由上一个最优的解转移过来,而是由不是最优的转移过来,加上这个数之后就是最优的了,这不满足dp的性质,所以不行。这和午餐这题很相似。 
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define inf 100000000
using namespace std;
const int maxn=300;
int a[maxn],dp[maxn][maxn][5],ans=inf,ansx,ansy;
int main(){
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]);
	sort(a+1,a+n+1);
	
	for(int i=1;i<=n;i++)
	for(int j=0;j<=i;j++){
		if(abs(i-j*2)>1) continue;
		else if(abs(i-j*2-1)>1) dp[i][j][1]=dp[i-1][j-1][1]+a[i],dp[i][j][2]=dp[i-1][j-1][2];
		else if(abs(i-j*2+1)>1)  dp[i][j][2]=dp[i-1][j][2]+a[i],dp[i][j][1]=dp[i-1][j][1]; 
		else if(abs(dp[i-1][j][1]-dp[i-1][j][2]-a[i])<abs(dp[i-1][j-1][1]+a[i]-dp[i-1][j-1][2])){
			dp[i][j][2]=dp[i-1][j][2]+a[i];
			dp[i][j][1]=dp[i-1][j][1]; 
		}
		else dp[i][j][1]=dp[i-1][j-1][1]+a[i],dp[i][j][2]=dp[i-1][j-1][2];
    }
	for(int i=0;i<=n;i++)
	if(abs(n-i*2)<=1){
		if(ans>abs(dp[n][i][1]-dp[n][i][2])){
			ans=min(ans,dp[n][i][1]-dp[n][i][2]);
			ansx=dp[n][i][1];
			ansy=dp[n][i][2];
		}
	}
	printf("%d\n%d\n",ansx,ansy);

	return 0;
}

Description

上午的训练结束了,THU ACM小组集体去吃午餐,他们一行N人来到了著名的十食堂。这里有两个打饭的窗口,每个窗口同一时刻只能给一个人打饭。由于每个人的口味(以及胃口)不同,所以他们要吃的菜各有不同,打饭所要花费的时间是因人而异的。另外每个人吃饭的速度也不尽相同,所以吃饭花费的时间也是可能有所不同的。 THU ACM小组的吃饭计划是这样的:先把所有的人分成两队,并安排好每队中各人的排列顺序,然后一号队伍到一号窗口去排队打饭,二号队伍到二号窗口去排队打饭。每个人打完饭后立刻开始吃,所有人都吃完饭后立刻集合去六教地下室进行下午的训练。 现在给定了每个人的打饭时间和吃饭时间,要求安排一种最佳的分队和排队方案使得所有人都吃完饭的时间尽量早。 假设THU ACM小组在时刻0到达十食堂,而且食堂里面没有其他吃饭的同学(只有打饭的师傅)。每个人必须而且只能被分在一个队伍里。两个窗口是并行操作互不影响的,而且每个人打饭的时间是和窗口无关的,打完饭之后立刻就开始吃饭,中间没有延迟。 现在给定N个人各自的打饭时间和吃饭时间,要求输出最佳方案下所有人吃完饭的时刻。

Input

第一行一个整数N,代表总共有N个人。 以下N行,每行两个整数 Ai,Bi。依次代表第i个人的打饭时间和吃饭时间。

Output

一个整数T,代表所有人吃完饭的最早时刻。

Sample Input

5
2 2
7 7
1 3
6 4
8 5

Sample Output

17

HINT

方案如下:

窗口1: 窗口2:
7 7 1 3
6 4 8 5
2 2

【限制】
所有输入数据均为不超过200的正整数。

现在说说和这题类似的午餐问题。错误的想法是dp[i][j]前i个人中,j个放在第一组。错误原因和上面一样。上一题枚举血量,所以这一题枚举时间。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=500100;
int n,sum[maxn],sum1[maxn];
int dp[maxn];
struct node{
    int u,v;
}a[maxn];
bool cmp(node a,node b){
    return a.v>b.v;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d%d",&a[i].u,&a[i].v);
    }
    sort(a+1,a+n+1,cmp);
    
    for(int i=1;i<=n;i++)
    sum[i]=sum[i-1]+a[i].u;
    memset(dp,127,sizeof(dp));
    
    dp[0]=0;
    for(int i=1;i<=n;i++){
        for(int j=sum[i];j>=0;j--){
            if(j>=a[i].u)
            dp[j]=min(max(dp[j-a[i].u],j+a[i].v),max(dp[j],sum[i]-j+a[i].v));
            else
            dp[j]=max(dp[j],sum[i]-j+a[i].v);
        }
    }
    int ans=2147483647;
    for(int i=1;i<=sum[n];i++)
    ans=min(ans,dp[i]); 
    printf("%d\n",ans);

    return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值