点这里看题目
考场上也没想到这道 t 3 t3 t3竟是整场比赛最可做的一道题。但是拿了全场最高的部分分,这是好的。
唉,学了插头 d p dp dp但是却无法将其运用,我真傻,真的。
部分分已经很好的提示了这道题的正解是状压。先考虑 d > 8 d>8 d>8,也就是我考场上没想出来的部分。
不妨将原问题做细微的变动,令 d ← 2 d d\gets 2d d←2d,那么 x x x可以移动到 x x x或 x + 2 d x+2d x+2d两个位置,这样做的好处在于所有点都是朝一个方向运动。
以往我们对坐标的认识都是很大而不好处理的,但是这道题非常特殊,注意到 x i ≤ 150 x_i\le 150 xi≤150,因此可以直接看成是大小 ≤ 150 \le 150 ≤150的棋盘,特别的,我们将 x , x + d , x + 2 d , . . . x,x+d,x+2d,... x,x+d,x+2d,...排成一个横排,这样得到了一个宽 ≤ ⌊ x i d ⌋ \le \lfloor\frac{x_i}{d}\rfloor ≤⌊dxi⌋的矩阵。
注意到 ⌊ x i d ⌋ ≤ 8 \lfloor\frac{x_i}{d}\rfloor\le 8 ⌊dxi⌋≤8,这提示我们按行进行状压。但是同一行的坐标在数轴上不是连续的,注意到我们在插头 d p dp dp中处理的路径也不是连续的,但是我们可以将它拼接起来,那么类似的,我们也可以看成是数轴上有若干个端点朝左右方向扩展,新建一个端点的代价是 a a a,扩展一个单位的代价是 b b b。甚至这道题目的情况也远没有插头 D P DP DP复杂,只是这个想法比较隐晦而已。
观察一下这个表格,同一列在坐标轴上的位置是固定的,因此可以只用记录两个端点是否被线段覆盖。一个棋子也只能移动到同一行相邻的位置,因此情况并不复杂。那么这部分就做完了,复杂度 O ( max ( x i ) 2 16 ) O(\max(x_i)2^{16}) O(max(xi)216)。
最后再来补充一下 d ≤ 8 d\le 8 d≤8的部分。不得不说这个部分分设置得非常好。考场上想了一个巨麻烦的方法,但是其实没有必要,就直接记录后 d d d个位置那些点有棋子,以及前一个位置有没有被覆盖即可,这其实就是我考场的做法,但是考场上好像写复杂了。复杂度同样是 O ( max ( x i ) 2 16 ) O(\max(x_i)2^{16}) O(max(xi)216)。
代码比想象中的要长一点。注意数组不要开小了。
#include
#define ll long long
#define fi first
#define se second
#define pb push_back
#define db double
#define inf 0x3f3f3f3f
using namespace std;
int n,d,a,b,X[155],now[2][1<<18],nxt[2][1<<18],vispoints[305];
int h,w,now2[1<<9],nxt2[1<<9],points[305][20],res=0x3f3f3f3f;
void chmin(int &x,int y){x=min(x,y);}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>d>>a>>b,d<<=1;
for(int i=0;i<n;i++)cin>>X[i];
sort(X,X+n);
n=unique(X,X+n)-X;for(int i=n-1;i>=0;i--)X[i]-=X[0];
if(d<=18){
memset(now,0x3f,sizeof now);
now[0][0]=0;
for(int i=0;i<n;i++)vispoints[X[i]]=1;
for(int i=0;i<=X[n-1]+d;i++){
memset(nxt,0x3f,sizeof nxt);
if(!vispoints[i]){
for(int j=0;j<1<<d;j++){
if(j&1){
chmin(nxt[1][j>>1],now[0][j]+a);
chmin(nxt[1][j>>1],now[1][j]+min(a,b));
}
else{
chmin(nxt[1][j>>1],now[0][j]+a);
chmin(nxt[1][j>>1],now[1][j]+min(a,b));
chmin(nxt[0][j>>1],now[0][j]);
chmin(nxt[0][j>>1],now[1][j]);
}
}
}
else{
for(int j=0;j<1<<d;j++){
if(j&1){
chmin(nxt[1][j>>1],now[0][j]+a);
chmin(nxt[1][j>>1],now[1][j]+min(a,b));
}
else{
chmin(nxt[1][j>>1],now[0][j]+a);
chmin(nxt[1][j>>1],now[1][j]+min(a,b));
chmin(nxt[0][(j>>1)^(1<<d-1)],now[0][j]);
chmin(nxt[0][(j>>1)^(1<<d-1)],now[1][j]);
}
}
}
memcpy(now,nxt,sizeof nxt);
}
cout<<now[0][0];
}
else{
h=d,w=X[n-1]/d+2;
assert(2*w<=18);
for(int i=0;i<n;i++){
points[X[i]%d][X[i]/d]=1;
assert(X[i]/d<w-1);
assert(X[i]%d<h);
}
for(int s=0;s<1<<w;s++){
int ok=1;
for(int i=0;i<w-1;i++){
if(points[0][i]&&!((s>>i&1)|(s>>i+1&1))){
ok=0;
break;
}
}
if(!ok)continue;
int tmp=__builtin_popcount(s);
memset(now2,0x3f,sizeof now2),now2[s]=tmp*a;
for(int i=1;i<h;i++){
for(int j=0;j<w;j++){
memset(nxt2,0x3f,sizeof nxt2);
for(int k=0;k<1<<w;k++){
if(j&&points[i][j-1]&&!(k>>j-1&1)){
if(k>>j&1){
chmin(nxt2[k],now2[k]+min(a,b));
}
else{
chmin(nxt2[k^(1<<j)],now2[k]+a);
}
}
else{
if(k>>j&1){
chmin(nxt2[k],now2[k]+min(a,b));
chmin(nxt2[k^(1<<j)],now2[k]);
}
else{
chmin(nxt2[k^(1<<j)],now2[k]+a);
chmin(nxt2[k],now2[k]);
}
}
}
memcpy(now2,nxt2,sizeof nxt2);
}
}
for(int i=0;i<1<<w;i++){
for(int j=0;j<w-1;j++){
if((i>>j&1)&&(s>>j+1&1)&&a>=b){
now2[i]-=a,now2[i]+=b;
}
}
chmin(res,now2[i]);
}
}
cout<<res;
}
}