【洛谷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;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章