题解 UVA323 【Jury Compromise】
ysj1173886760 · · 题解
这道题很不错,所以写一篇题解吧。
楼上的dalao也说过了,这是一道01背包拔河问题,实际上并不简单的只是01背包拔河。
这道题中物品的属性也有变化,属性不仅仅是要用于拔河的权值,还有另一个属性,就是这道题中的人,每一个人看做一个物品,其p和d值就是用于拔河的权值,而人本身还有一个属性就是体积,一个人就是1,为了满足恰好选m个的要求。
所以正常来说我们的背包应该设成3维,dp[i][j][k]表示前i个人,选了j个人,k=
我们可以优化成滚动数组。
这些细节就不在多说了,想说的就是这道题可以在网上看到很多的题解。 但是有些题解并不是正确的。
有些做法是先枚举m,以选择的人划分阶段。然后在dp内部判断是否选择了重复的。再继续dp
大概是这样的。
for(int j=0;j<m;j++){
for(int k=0;k<=800;k++)
if(f[j][k]>=0){
for(int i=1;i<=n;i++)
(judge.....)
}
这样的做法是错误的,因为如果以人数作为阶段划分,每次枚举这一阶段要选那个人进去,首先就需要确定这个人之前没有出现过,第一想法肯定是状压,但是网上的做法并不是这样的。而是写了一个find函数,顺着之前走到过的最优解的路径进行查找,看之前有没有选过这个人。从而更新状态。
但是我们可以发现,如果这样强行把需要记录那个人选没选的信息压掉,就出现了后效性,也就是说,如果达到dp[i][j]这个状态,根据上面的方式转移,其背后还隐藏着路径这个状态。
如果存在相同值也可以到达dp[i][j]这个状态,在上面的方案中是不取的。但是问题来了,如果之后的最优解需要当前选择方案中的一个怎么办呢??
形象点说,当前选了a b c可以到达dp[i][j],同时如果选d e f也可以到达这个状态,如果最终答案是a d e f,但如果程序选的a b c,那么之后的决策中就没有a,也就不会达到a d e f这个状态。
还有一个很相似的例子就是noip2017 的宝藏,很多人的状态都是错的,因为需要考虑路径的长度,但是数据水就过去了。
这道题一样,是需要考虑已经选择的集合的。 如果根据划分人数为状态,就不需要考虑是否选了重复的。
还有一点就是记路径了。如果用滚动数组的方法记路径会发现出现重复了。但是最终的值是对的,但是方案不对。
这是因为倒叙更新状态的话,dp是不会受到影响的,但是路径数组可能会被自己更新,emm
举个例子,比如当前枚举到3,dp[3][50]是由第三个数转移来的。 我们记录rec[3][50]=3,假设3的权值为5,我们找路径就会先找3,然后找dp[2][45],假设rec[2][45]=2.
如果很巧合的我们用3同样更新了dp[2][45]的话,那么rec[2][45]就变成了3,结果最后找路径就出现了重复。
所以考虑用链表存储路径,状态与状态的转移直接把表头继承过去就行了,然后加上当前点。
总之还是需要多体会吧。dp的后效性还是很坑人的。
对了注意一点,要输出两个换行
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxx=430;
const int maxn=2e2+10;
int n,m,tot;
int dp[30][maxx*2],p[maxn],d[maxn],rec[30][maxx*2];
struct line
{
int to,next;
}edge[maxn*4*maxx];
void add1(int a1,int a2,int b)
{
edge[++tot].to=b;
edge[tot].next=rec[a1][a2];
rec[a1][a2]=tot;
}
void print(int now)
{
if(!now)return;
print(edge[now].next);
cout<<" "<<edge[now].to;
}
int main()
{
int cnt=0;
cin>>n>>m;
while(n!=0&&m!=0)
{
cnt++;
for(int i=1;i<=n;i++)cin>>p[i]>>d[i];
memset(dp,-0x3f,sizeof(dp));
memset(rec,0,sizeof(rec));
dp[0][maxx]=0;
for(int i=1;i<=n;i++)
for(int j=m;j>=1;j--)
for(int k=-400;k<=400;k++)
{
if(dp[j-1][k-(p[i]-d[i])+maxx]<0)continue;
if(dp[j-1][k-(p[i]-d[i])+maxx]+p[i]+d[i]>dp[j][k+maxx])
{
dp[j][k+maxx]=dp[j-1][k-(p[i]-d[i])+maxx]+p[i]+d[i];
rec[j][k+maxx]=rec[j-1][k-(p[i]-d[i])+maxx];
add1(j,k+maxx,i);
}
}
int ans=0,id=0,pp=0,dd=0;
for(int i=0;i<=400;i++)
{
ans=dp[m][maxx+i],id=i;
if(dp[m][maxx-i]>ans)id=-i,ans=dp[m][maxx-i];
if(ans>=0)break;
}
pp=(ans+id)/2;
dd=(ans-id)/2;
cout<<"Jury #"<<cnt<<endl;
cout<<"Best jury has value "<<pp<<" for prosecution and value "<<dd<<" for defence:"<<endl;
print(rec[m][id+maxx]);
cout<<endl;
cout<<endl;
cin>>n>>m;
}
return 0;
}