AT_abc328_g Cut and Reorder 题解
AT_abc328_g Cut and Reorder
非常好的状压 DP 题目!
思路
手推一下,发现第一个操作只会进行一次,进行多次不会比进行一次更优。
假设我们已经进行过第一个操作了,那么代价就是第一个操作的代价加上 $\sum_{i=1}^{N} \left | A_{i}-B_{i} \right | $。现在关键是想怎么进行第一个操作。
数据范围不简单,显然是状压的范围。
我们可以考虑把 AAA 的元素分成一段一段放到目标位置上,设 dpidp_{i}dpi 表示 iii 状态下的最小代价,其中 iii 是一个二进制数,每一位 111 表示已经放上元素了,被占用了,000 表示还没有放上元素。如果 iii 中有 sisisi 个 111,那么就表示把 (A1,A2,…,Asi)(A_{1},A_{2},\dots ,A_{si})(A1,A2,…,Asi) 加入到被占用的位置上,不一定要按顺序。
我们用刷表法更新,即用已知去更新未知。
我们可以枚举 iii 中未被占用的位置,假设新的一段从这里插入,那么我们顺次枚举插入多少元素更新新的状态。
memset(dp,127,sizeof(dp));
dp[0]=0;
for(int i=0;i<(1<<n);i++)
{int si=0;//i中1的个数for(int j=0;j<n;j++)if(i&(1<<j)) si++;for(int j=0;j<n;j++)if(!(i&(1<<j))){long long sum=c,now=si+1;int f=i;for(int k=j;k<n;k++)if(!(i&(1<<k))){sum+=abs(a[now++]-b[k+1]);f|=(1<<k);dp[f]=min(dp[f],dp[i]+sum);}else break;//否则就加不进去了}
}
时间复杂度 O(2NN)O(2^NN)O(2NN),空间复杂度 O(2N)O(2^N)O(2N)。
时间复杂度的证明
看似是 O(2NN2)O(2^NN^2)O(2NN2),实则不然。
我们发现内层的 jjj 与 kkk 实际上是在枚举子段,假设子段长度为 lll,则其出现 2N−l(N−l+1)2^{N-l}(N-l+1)2N−l(N−l+1) 次,则总复杂度为 ∑l=1N2N−l(N−l+1)≈∑l=1N2N−lN=(2N−1)N\sum_{l=1}^{N}2^{N-l}(N-l+1) \approx \sum_{l=1}^{N}2^{N-l}N=(2^N-1)N∑l=1N2N−l(N−l+1)≈∑l=1N2N−lN=(2N−1)N。
故时间复杂度为 O(2NN)O(2^NN)O(2NN)。
代码
#include<bits/stdc++.h>
using namespace std;
int n;
long long dp[1<<22];
long long a[23],b[23],c;
int main()
{ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);cin>>n>>c;for(int i=1;i<=n;i++)cin>>a[i];for(int i=1;i<=n;i++)cin>>b[i];memset(dp,127,sizeof(dp));dp[0]=0;for(int i=0;i<(1<<n);i++){int si=0;//i中1的个数for(int j=0;j<n;j++)if(i&(1<<j)) si++;for(int j=0;j<n;j++)if(!(i&(1<<j))){long long sum=c,now=si+1;int f=i;for(int k=j;k<n;k++)if(!(i&(1<<k))){sum+=abs(a[now++]-b[k+1]);f|=(1<<k);dp[f]=min(dp[f],dp[i]+sum);}else break;//否则就加不进去了}}cout<<dp[(1<<n)-1]-c;//实际上i段需要(i-1)个c,所以我们多算了一个creturn 0;
}