很早很早以前就以为是一个麻烦的题目也感受很简单不想写……结果今天写的时候发现以前的想法假了(因此不要口胡题目不写代码)c++
\(\text{Solution:}\)ide
看到 \(b[i]\leq 7\) 的数据范围先想到状压。spa
考虑状压一我的后面的人的打饭状况,那么能够设 \(f[i][S]\) 表示前 \(i\) 我的已经打完饭,\(i+1\to i+b[i]\) 的打饭状况是 \(S\) 的最小时间。设计
可是会发现一件很棘手的事情:当咱们去用 \(S\) 转移的时候,把一我的放到前面影响的不单单是 \(i\) 一个位置,更有 \(i\to pos\) 之间的全部位置。code
也就是说,若是我想要把一个位置提早,那么它必须知足全部尚未打饭的人的容忍度,这个东西是单调递减的。ci
因此咱们能够考虑在 \(dp\) 的时候来维护一个范围,若是当前枚举的人已经超过合法范围了,那咱们就直接 \(pass.\)get
继续考虑,若是咱们让一我的 \(j\) 到前面去,代价应该是什么呢?it
因为这个状态的设计,咱们发现咱们没有办法得到上一次打饭的人的编号。因而,这个编号应该也做为 \(dp\) 里面的一个维度。io
发现空间很大怎么办,考虑到这我的和 \(i\) 的差异不超过 \(8\) 因而咱们能够考虑用一个 “位移长度” \(k\) ,以 \(i+k\) 表明上一次打饭的人的编号。这样就解决了空间的问题。
继续考虑,发现若是这样设计状态 对于第一个位置 \(1\) 来讲,\(i\) 已经打完饭的最优解其实是不是很好肯定的。由于第一道菜不须要时间。因而咱们稍微改一下方程:
\(f[i][j][k]\) 表示前 \(i-1\) 我的全都打完饭了,当前人们的打饭状态为 \(j,\) 上一次打饭的人的编号是 \(i+k\) 的最小时间。
发现这个 \(k\) 其实是能够到 \(-8\) 的,由于能够把一个编号靠后的人拿到前面去,这样上一次打饭的人和 \(i\) 这一层就有可能差出 \(8.\)
为了解决这个问题,考虑把数组总体平移一下,加上一个 \(8\) 就好了。
那么,考虑转移:若是当前 \(i\) 已经打饭了,观察一下这个状态 显然对于 \(f[i+1][j>>1][k+7]\) 这个状态,\(f[i][j][k+8]\) 是和它等价的。
因而直接转移便可。
另外一种转移是考虑枚举一我的去打饭,这个时候咱们须要同时维护一下一个容忍度的上界,转移的时候还须要特别注意是否是第一我的。
关于初始化 \(f[1][0][7]=0\) 是指上一个打饭的人是 \(1\) 前面的一我的,且 \(0\) 后面的人都没有打饭。
转移就是:
这里是异或的缘由是\(\text{a or b-a and b=a xor b}\)
最后统计答案要统计 \(n+1\) 的,由于状态设计的是 \(i-1\) 所有填满。
#include<bits/stdc++.h> using namespace std; const int dyx=0x3f3f3f3f; const int MAXN=2e3+10; int f[MAXN][1<<9][20]; int n,t[MAXN],b[MAXN]; inline int Min(int x,int y){return x<y?x:y;} void solve(){ scanf("%d",&n); for(int i=1;i<=n;++i)scanf("%d%d",&t[i],&b[i]); memset(f,dyx,sizeof f);f[1][0][7]=0; for(int i=1;i<=n+1;++i) for(int j=0;j<(1<<8);++j) for(int k=-8;k<8;++k){ if(f[i][j][k+8]!=dyx){ if(j&1)f[i+1][j>>1][k+7]=Min(f[i+1][j>>1][i+7],f[i][j][k+8]); else{ int R=dyx; for(int p=0;p<8;++p){ if(j>>p&1)continue; if(i+p>R)break; R=Min(R,i+p+b[i+p]); f[i][j|(1<<p)][p+8]=Min(f[i][j|(1<<p)][p+8],f[i][j][k+8]+(k+i>0?(t[i+k]^t[i+p]):0)); } } } } int ans=dyx; for(int i=0;i<=15;++i)ans=Min(ans,f[n+1][0][i]); cout<<ans<<endl; for(int i=1;i<=n;++i)t[i]=b[i]=0; } int main(){ int T; cin>>T; while(T--){ solve(); } return 0; }