題目描述
新一年度的貓狗大戰通過SC(星際爭霸)這款經典的遊戲來較量,野貓和飛狗這對冤家爲此已經準備好久了,爲了使戰爭更有難度和戲劇性,雙方約定只能選擇Terran(人族)並且只能造機槍兵。
比賽開始了,很快,野貓已經攢足幾隊機槍兵,試探性的發動進攻;然而,飛狗的機槍兵個數也已經不少了。野貓和飛狗的兵在飛狗的家門口相遇了,於是,便有一場腥風血雨和陣陣慘叫聲。由於是在飛狗的家門口,飛狗的兵補給會很快,野貓看敵不過,決定撤退。這時飛狗的兵力也不足夠多,所以沒追出來。
由於不允許造醫生,機槍兵沒辦法補血。受傷的兵只好忍了。555-
現在,野貓又攢足了足夠的兵力,決定發起第二次進攻。爲了使這次進攻給狗狗造成更大的打擊,野貓決定把現有的兵分成兩部分,從兩路進攻。由於有些兵在第一次戰鬥中受傷了,爲了使兩部分的兵實力平均些,分的規則是這樣的:1)兩部分兵的個數最多隻能差一個;2)每部分兵的血值總和必須要儘可能接近。現在請你編寫一個程序,給定野貓現在有的兵的個數以及每個兵的血格值,求出野貓按上述規則分成兩部分後每部分兵的血值總和。
輸入輸出格式
輸入格式:第一行爲一個整數n(1<=n<=200),表示野貓現在有的機槍兵的個數。以下的n行每行一個整數,表示每個機槍兵的血格(1<=ai<=40)。
輸出格式:一行,爲兩個整數,表示分成兩部分後每部分兵的血值總和
輸入輸出樣例
3 35 20 32
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
Input
Output
Sample Input
2 2
7 7
1 3
6 4
8 5
Sample Output
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;
}