ARC126D Pure Straight
给一个长度为 n n n的整数序列 A = ( a 1 , a 2 , … , a n ) A=(a_1,a_2,\dots,a_n) A=(a1,a2,…,an),其中 a i ∈ [ 1 , k ] a_i\in [1,k] ai∈[1,k]。
你可以做如下操作任意次:
求最小的操作次数,使得序列 A A A满足下列条件:
2 ≤ k ≤ 16 , k ≤ n ≤ 200 2\leq k\leq 16,k\leq n\leq 200 2≤k≤16,k≤n≤200
我们可以先将在 1 1 1到 k k k内的数移到一段,然后在这一段区间内排序。
令构成子串的元素的下标从小到大依次为 c 1 , c 2 , … , c n c_1,c_2,\dots,c_n c1,c2,…,cn,中点位置为 m i d = ⌊ k 2 ⌋ mid=\lfloor\dfrac k2\rfloor mid=⌊2k⌋,则显然让所有 a c i a_{c_i} aci向 a c m i d a_{c_mid} acmid移动是最优的,总步数为
( ∑ i = 1 m i d − 1 ( c m i d − m i d + i ) − c i ) + ( ∑ i = m i d + 1 k c i − ( c m i d + i − m i d ) ) (\sum\limits_{i=1}^{mid-1}(c_{mid}-mid+i)-c_i)+(\sum\limits_{i=mid+1}^kc_i-(c_{mid}+i-mid)) (i=1∑mid−1(cmid−mid+i)−ci)+(i=mid+1∑kci−(cmid+i−mid))
我们发现这个式子中的许多地方可以抵消,最后式子可变为
( ∑ i = m i d + 1 k c i ) − ( ∑ i = 1 m i d − 1 c i ) − c m i d × ( n % 2 = = 0 ) + m i d × ( n % 2 = = 0 ) + ( ∑ i = 1 m i d − 1 i ) − ( ∑ i = m i d + 1 k i ) (\sum\limits_{i=mid+1}^kc_i)-(\sum\limits_{i=1}^{mid-1}c_i)-c_{mid}\times (n\%2==0)+mid\times(n\%2==0)+(\sum\limits_{i=1}^{mid-1}i)-(\sum\limits_{i=mid+1}^ki) (i=mid+1∑kci)−(i=1∑mid−1ci)−cmid×(n%2==0)+mid×(n%2==0)+(i=1∑mid−1i)−(i=mid+1∑ki)
后面 m i d × ( n % 2 = = 0 ) + ( ∑ i = 1 m i d − 1 i ) − ( ∑ i = m i d + 1 k i ) mid\times(n\%2==0)+(\sum\limits_{i=1}^{mid-1}i)-(\sum\limits_{i=mid+1}^ki) mid×(n%2==0)+(i=1∑mid−1i)−(i=mid+1∑ki)是可以 O ( 1 ) O(1) O(1)求出的,我们来看看如何求前面的部分。
可以用状压DP,设 f i , s f_{i,s} fi,s表示枚举到 A A A的第 i i i位时状态为 s s s, s s s的二进制位 1 1 1表示已取过这个数字, 0 0 0表示没取过这个数字。我们需要预处理数组 h v s hv_s hvs,表示 s s s的二进制位中有多少个 1 1 1。
状态转移式如下
f i , s ∣ ( 1 < < a i − 1 ) = { f s − i + p s , a i h v s + 1 < m i d f s − i × ( k % 2 = = 0 ) + p s , a i h v s + 1 = m i d f s + i + p s , a i h v s + 1 > m i d f_{i,s|(1<
其中 f i , s ∣ ( 1 < < a i − 1 ) f_{i,s|(1<
下面来解释一下 p p p是什么。因为在将 1 1 1到 k k k内的数移到一段后,内部还要调整。根据冒泡排序的原理,若要用最少的操作次数排好序,每个数对操作次数的贡献为在它之前比他大的数的个数。 p s , i p_{s,i} ps,i表示在 s s s中二进制位数大于 i i i且该位为 1 1 1的数量,在转移式中表示加入这个元素的贡献。
求出 f f f后,加上 m i d × ( n % 2 = = 0 ) + ( ∑ i = 1 m i d − 1 i ) − ( ∑ i = m i d + 1 k i ) mid\times(n\%2==0)+(\sum\limits_{i=1}^{mid-1}i)-(\sum\limits_{i=mid+1}^ki) mid×(n%2==0)+(i=1∑mid−1i)−(i=mid+1∑ki)即为答案。
时间复杂度为 O ( n ⋅ 2 k ) O(n\cdot2^k) O(n⋅2k)。
#include
using namespace std;
int n,k,mid,ans,a[205],v[20],hv[1<<16],p[1<<16][20],f[1<<16];
void pd(int now){
f[now]=1000000000;
int s=0;
for(int i=k;i>=1;i--){
p[now][i]=s;
s+=v[i];
}
hv[now]=s;
}
void dfs(int t,int now){
if(t<k) dfs(t+1,now);
else pd(now);
now+=(1<<t-1);v[t]=1;
if(t<k) dfs(t+1,now);
else pd(now);
v[t]=0;
}
int main()
{
scanf("%d%d",&n,&k);
mid=(k+1)/2;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
dfs(1,0);
f[0]=0;
for(int i=1;i<=n;i++){
for(int s=(1<<k)-1;s>=0;s--){
if(s&(1<<a[i]-1)) continue;
int t=s|(1<<a[i]-1);
if(hv[s]+1<mid) f[s|t]=min(f[s|t],f[s]-i+p[s][a[i]]);
else if(hv[s]+1==mid) f[s|t]=min(f[s|t],f[s]-i*(k%2==0)+p[s][a[i]]);
else f[s|t]=min(f[s|t],f[s]+i+p[s][a[i]]);
}
}
ans=f[(1<<k)-1];
if(k%2==0) ans+=mid;
ans=ans+(mid)*(mid-1)/2-(k-mid)*(k+mid+1)/2;
printf("%d",ans);
return 0;
}