原题: https://www.luogu.org/problemnew/show/P2157
题意:
n个人按顺序排队,每个人有一个服务时间T,忍耐度D,允许排在他后面的D个人进行插队。
第一个人的实际时间为0,其他人的时间为T异或前一个被服务的人的T,问总服务时间的最小值。
解析:
dp数组的维度很容易想,dp[i][j][k]
代表到了第i个人,状态为j,前一个服务的人为k的最小值。
因为D最大只有7,所以j只需要表示i及后面7个人的状态即可。
注意前一个服务的人可能是i-8~i+7(前面第8个人让前面1至7个人先服务)。
在遍历人进行服务的时候,注意维护可以服务的范围。
而对于一个状态,不需要判断是否合理(有的人没被服务但是忍耐度之外的人被服务),因为我们在选择人进行服务的时候,一定会考虑到这一点。所以,只要dp值不是inf,就合理。
有一个剪枝,到了第i个人的时候,假设有一个状态已经服务了i时,可以continue了。因为当前继续的所有操作在i+1时都会再次进行。
数组开成2^7WA了好久。。。
A掉后去看别人的题解发现写的好短,但是回头看了一下时间就欣慰了
AC代码:
#include<bits/stdc++.h>
using namespace std;
int t[1009],c[1009];
int dp[1009][330][20];
int maxc;
inline int id(int i,int idx){
return i+maxc-idx;
}
int main(){
int T;scanf("%d",&T);
while(T--){
int n;scanf("%d",&n);
memset(dp,0x3f,(sizeof dp[0])*(n+2));
maxc=0;
for(int i=1;i<=n;i++){
scanf("%d%d",t+i,c+i);
maxc=max(maxc,c[i]);
}
maxc++;
maxc=min(maxc,n);
int maxsta=(1<<maxc)-1;
// i: c[i]+1 bit and more(maxc-c[i]-1) 0
// 第一个人0费用
int last=maxc-c[1];
for(int i=maxc;i>=last;i--){
if(id(1,i)>n)break;
dp[1][1<<i-1][i]=0;
last=max(last,i-c[id(1,i)]);
}
for(int i=1;i<=n;i++){
int cmp=(1<<maxc-c[i]-1)-1 ;
for(int sta=0;sta<=maxsta ;sta++){
//存在即合理
bool f=0;
for(int j=2*maxc;j>=1;j--)if(dp[i][sta][j]<0x3f3f3f3f)f=1;
if(!f)continue;
if(sta&(1<<maxc-1))continue;//剪枝
// 遍历这个sta的可填的位置
last=maxc-c[i];
for(int j=maxc;j>=last;j--){
if(id(i,j)>n)break;
if(!(sta&(1<<j-1))){
//填
for(int k=2*maxc+1;k>=1;k--)//遍历前一个填的位置
if(k>maxc||(sta&(1<<k-1)))
dp[i][sta|(1<<j-1)][j]=
min(dp[i][sta|(1<<j-1)][j],dp[i][sta][k]+(t[id(i,k)]^t[id(i,j)]));
last=max(last,j-c[id(i,j)]);
}
}
}
// 状态转移
for(int sta=0;sta<=maxsta;sta++){
if(sta&(1<<maxc-1)){
for(int j=2*maxc+1;j>=1;j--){
if(dp[i][sta][j]<0x3f3f3f3f)
dp[i+1][(sta<<1)&maxsta][j+1]=
min(dp[i+1][(sta<<1)&maxsta][j+1],dp[i][sta][j]);
}
}
}
}
int ans=0x3f3f3f3f;
for(int j=2*maxc+1;j>=1;j--){
ans=min(ans,dp[n][1<<maxc-1][j]);
}
printf("%d\n",ans);
}
}