ARC126D Pure Straight

该问题要求计算将一个包含1到k的整数序列调整为包含1到k的连续子串所需的最小操作次数。解决方案是首先将1到k的数移到序列的一段,然后使用动态规划方法在这一段内进行排序优化。关键在于确定最优的交换策略,通过计算不同状态的贡献并进行转移。最终的答案可以通过预处理数组和动态规划状态转移得到,时间复杂度为O(n*2^k)。
摘要由CSDN通过智能技术生成

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满足下列条件:

  • A A A包含 ( 1 , 2 , … , k ) (1,2,\dots,k) (1,2,,k)这个子串

2 ≤ k ≤ 16 , k ≤ n ≤ 200 2\leq k\leq 16,k\leq n\leq 200 2k16,kn200


题解

我们可以先将在 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=1mid1(cmidmid+i)ci)+(i=mid+1kci(cmid+imid))

我们发现这个式子中的许多地方可以抵消,最后式子可变为

( ∑ 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+1kci)(i=1mid1ci)cmid×(n%2==0)+mid×(n%2==0)+(i=1mid1i)(i=mid+1ki)

后面 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=1mid1i)(i=mid+1ki)是可以 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<<a_i-1)}= \left\{\begin{matrix} f_s-i+p_{s,a_i} \qquad\qquad\qquad\qquad \ \ hv_s+1<mid \\ f_s-i\times(k\%2==0)+p_{s,a_i} \qquad hv_s+1=mid\\ f_s+i+p_{s,a_i} \qquad\qquad\qquad\qquad \ \ hv_s+1>mid \end{matrix}\right. fi,s(1<<ai1)= fsi+ps,ai  hvs+1<midfsi×(k%2==0)+ps,aihvs+1=midfs+i+ps,ai  hvs+1>mid

其中 f i , s ∣ ( 1 < < a i − 1 ) f_{i,s|(1<<a_i-1)} fi,s(1<<ai1)与后面的部分取 max ⁡ \max max

下面来解释一下 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=1mid1i)(i=mid+1ki)即为答案。

时间复杂度为 O ( n ⋅ 2 k ) O(n\cdot2^k) O(n2k)

code

#include<bits/stdc++.h>
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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值