这道题是给你n个任务,每个任务都有 任务名称,任务时限,所需时间,然后当一个任务的完成时间 t 超过该任务的任务时限 end 时,就会扣除 t-end 的分数,然后求完成所有任务所扣除的分数最小,把任务安排打印出来,如果有多种方案,输出字典序最小的。
这题用可以用n位的二进制表示选取情况,在二进制的第i位上,可以用1或0表示第i个物品已选取或者尚未选取,这样子我们要求的答案就是这n位全是1的状态。至于方案输出就是记录路径,可以在状态转移的过程中记录。
我最开始的想法是将状态定义成:选第i个任务的时候选的是编号为j的任务的状态。也就是dp[i][k],k表示的是当前的选取状态,然后当满足(k&(1<<j)==1)&&(k里面的1的个数为i)时,也就是当前选择第i个任务 选择的是编号为j的任务时,可以通过选择了i-1个任务,状态是k^(1<<j)的情况推过来,那么状态转移公式就是 dp[i][k]=min(dp[i][k],dp[i-1][k^(1<<j)]+k^(1<<j)状态选取第j个任务扣除的分数)。
但是仔细想想,如果不限制当前选择的是第几个任务,而是通过当前状态去判断是否可选取编号为j的任务,那么是否可行呢?答案是可行的,我们会发现,知道了当前状态k(0<k<(1<<n)),那么就知道了选取的任务个数,我们可以枚举j,表示最后一个选的任务是编号为j的任务。那么我们需要知道的就是在没选它的时候的状态的最优解,也就是dp[k]=min(dp[k],dp[k^(1<<j)]+k^(1<<j)状态选取第j个任务扣除的分数)
第一种思路:
#include <cstring>
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <cmath>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <math.h>
#include <set>
#include <time.h>
using namespace std;
char s[20][110];
int End[20];
int need[20];
int gs[1<<15];
int dp[17][1<<15][4];
int ans[17];
int main()
{
for(int i=0; i<(1<<15); ++i)//统计所有的状态二进制1的个数
{
int count=0;
int temp=i;
while(temp)
{
count+=temp&1;
temp>>=1;
}
gs[i]=count;
}
int t;
scanf("%d",&t);
while(t--)
{
memset(dp,0x3f,sizeof(dp));
int n;
scanf("%d",&n);
for(int i=0; i<n; ++i)
scanf("%s%d%d",s[i],End+i,need+i);
int ma=1<<n;
memset(dp[0],0,sizeof(dp[0]));
for(int i=1; i<=n; ++i)
{
for(int j=n-1;~j; --j)
for(int k=0; k<ma; ++k)
{
int now=k&(1<<(j));
if(now&&gs[k]==i)//如果第i位是1,并且k的二进制表示1的个数为i
{
int temp=dp[i-1][k^(1<<(j))][1]+need[j]-End[j];//需要扣掉的分数
if(temp<0)//不扣分
temp=0;
if(dp[i][k][0]>dp[i-1][k^(1<<(j))][0]+temp)
{
dp[i][k][0]=dp[i-1][k^(1<<(j))][0]+temp;
dp[i][k][1]=dp[i-1][k^(1<<(j))][1]+need[j];//当前选取状况所需的时间
dp[i][k][2]=j;//当前选的是编号为j的任务
dp[i][k][3]=k^(1<<(j));//从k^(1<<(j))来
}
}
}
}
printf("%d\n",dp[n][(1<<(n))-1][0]);
int now=(1<<(n))-1;
for(int i=n; i; --i)
{
ans[i]=dp[i][now][2];
now=dp[i][now][3];
}
for(int i=1; i<=n; ++i)
printf("%s\n",s[ans[i]]);
}
return 0;
}
第二种思路:
#include <cstring>
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <cmath>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <math.h>
#include <set>
#include <time.h>
using namespace std;
char s[20][110];
int End[20];
int need[20];
int dp[1<<16][2];
int ans[1<<16];
void print(int x)//打印路径
{
if(!x)
return ;
print(x^(1<<ans[x]));
printf("%s\n",s[ans[x]]);
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n;
scanf("%d",&n);
for(int i=0; i<n; ++i)
scanf("%s%d%d",s[i],End+i,need+i);
int ma=1<<n;
dp[0][0]=0;//状态的初始值
dp[0][1]=0;
for(int i=1; i<ma; ++i)
{
dp[i][0]=0x3f3f3f3f;
for(int j=n-1; ~j; --j)
{
if(i&(1<<j))
{
int cj=dp[i^(1<<j)][1]+need[j]-End[j];
if(cj<0)
cj=0;
if(dp[i][0]>dp[i^(1<<j)][0]+cj)
{
dp[i][0]=dp[i^(1<<j)][0]+cj;
dp[i][1]=dp[i^(1<<j)][1]+need[j];
ans[i]=j;
}
}
}
}
printf("%d\n",dp[(1<<n)-1][0]);
print((1<<n)-1);
}
return 0;
}