51nod 2214

51nod 2214

http://www.51nod.com/Challenge/Problem.html#!#problemId=2214
现在给定一个长度为 N 的 01串,同时给定参数 M,可以执行以下两种操作:

  • 选择串的任意一个位置取反
  • 指定整数 K>= 1,将串的前 K * M 位全部取反。
    用最少的操作次数,使得这个串的N-M前缀和N-M后缀完全相同,你只需要输出最少的操作次数。

思路也不是很明确。不过看了一下答案,感觉有了思路。答案给出了一个暴力的方案。
因为 N − M N-M NM后缀等于 N − M N-M NM前缀。 s [ i ] s[i] s[i]为字符串第 i i i个字符。则: s [ i ] = s [ i + m ] , 0 ≤ i &lt; N − M s[i] = s[i+m],0\leq i&lt; N-M s[i]=s[i+m],0i<NM
那么显然, s s s是一个以 M M M为循环节的字符串。
则 : m i n ( M , ⌊ N M ⌋ ) ≤ N min(M,\Big\lfloor\frac{N}{M}\Big\rfloor)\leq\sqrt{N} min(M,MN)N
N &lt; = 300 , N &lt; 18 N&lt;=300, \sqrt{N}&lt;18 N<=300,N <18.

M &lt; = N M&lt;=\sqrt{N} M<=N 时。暴力枚举循环节。枚举到的情况则可以动态规划计算小耗费。

首先。因为所有操作时可以交换测序的。且每一个循环节只需要考虑是否整体取反。先预处理出来 b i t c n t bitcnt bitcnt.
b i t c n t [ i ] bitcnt[i] bitcnt[i]表示 i i i的二级制中 1 1 1的个数
d p [ i ] dp[i] dp[i]表示前 i i i个循环节转移到当前枚举时需要的最小耗费。 c o s t [ i ] cost[i] cost[i]表示前 i i i个循环节中,枚举与原始串不同的字符个数。
d p [ i ] = m i n ( min ⁡ t = 0 i − 1 ( d p [ i − t − 1 ] + ( t + 1 ) M − ( c o s t [ i ] − c o s t [ i − t − 1 ] ) + 2 , ( t + 1 ) M − c o s t [ i ] + 1 ) dp[i] = min(\min_{t=0}^{i-1}(dp[i-t-1]+(t+1)M-(cost[i]-cost[i-t-1])+2,(t+1)M-cost[i]+1) dp[i]=min(t=0mini1(dp[it1]+(t+1)M(cost[i]cost[it1])+2,(t+1)Mcost[i]+1)
其中: c o s t [ i ] = c o s t [ i − 1 ] + b i t c n t [ A [ i ] x o r D ] ] d p [ 0 ] = m i n ( c o s t [ 0 ] , M − c o s t [ 0 ] + 1 ) cost[i]=cost[i-1]+bitcnt[A[i]xorD]]\\dp[0]=min(cost[0],M-cost[0]+1) cost[i]=cost[i1]+bitcnt[A[i]xorD]]dp[0]=min(cost[0],Mcost[0]+1)
A[i]是原始串中,第 i i i个循环节的位压。 D D D为枚举的循环节。
这个效率已经足够,不过显然 d p [ i ] dp[i] dp[i]可以由 d p [ i − 1 ] dp[i-1] dp[i1]直接计算得到。这样可以进一步提高效率,不过现在已经足够了。

M &gt; N M&gt;\sqrt{N} M>N 时。即: ⌊ N M ⌋ &lt; N \Big\lfloor\frac{N}{M}\Big\rfloor&lt;\sqrt{N} MN<N

暴力枚举操作2.也就是操作2的位置。然后根据枚举计算最小耗费。只要操作2确定。操作已也确定了。

#include<algorithm>
#include <stdio.h>
#include <cmath>
#define MAXN 303

using namespace std;
const int  bsize = 1 << 18;
char sptr[MAXN];
int bitcnt[bsize];
int A[MAXN];
int C[MAXN];
int D[MAXN];
int dp[MAXN];
int cost[MAXN];
int strToA(int N, int M)
{
	int deep = -1;
	memset(A, 0, sizeof A);
	for (int i = 0;i<N;i++)
	{
		if (i%M == 0)deep++;
		A[deep] = (A[deep] << 1) + (sptr[i] == '1');
	}
	return deep;
}

int main()
{
	for (int i = 1;i < bsize; i++) bitcnt[i] = bitcnt[i >> 1] + (i & 1);
	int T, N, M;
	scanf("%d", &T);
	while (T--)
	{
		scanf("%d %d", &N, &M);
		scanf("%s", sptr);
		int B = sqrt(N) + 1;
		int ans = MAXN;
		if (M < B)
		{
			int size = 1 << M;
			int cnt = strToA(N, M) + (N%M == 0);
			memset(C, 0, sizeof C);
			memset(D, 0, sizeof D);
			memset(cost, 0, sizeof cost);
			for (int i = 0;i < size;i++)
			{
				int tmpans = bitcnt[A[cnt] ^ (i >> ((M - (N%M)) % M))];
				if (N%M == 0)
				{
					tmpans = 0;
				}
				cost[0] = bitcnt[i^A[0]];
				dp[0] = min(cost[0], M - cost[0] + 1);
				for (int k = 1; k< cnt;k++)
				{
					int a = bitcnt[i^A[k]];
					int tmp = a + dp[k - 1];
					cost[k] = cost[k - 1] + a;
					for (int t = 0;t<k;t++)
					{
						a = dp[k - t - 1] + (t + 1)*M - (cost[k] - cost[k - t - 1]) + 2;
						if (a < tmp)tmp = a;
					}
					a = (k + 1)*M - cost[k] + 1;
					if (a < tmp) tmp = a;
					dp[k] = tmp;
				}
				if (tmpans + dp[cnt - 1] < ans)ans = tmpans + dp[cnt - 1];
			}
		}
		else
		{
			int bb = N / M;
			int size = (1 << bb);
			for (int i = 0;i<size;i++)
			{
				int k = 1, tmp = i, deep = 0, tmpans = bitcnt[i];
				while (k <= bb)
				{
					if (bitcnt[tmp] & 1)
					{
						int sz = k*M;
						while (deep < sz)
						{
							if (sptr[deep] == '1')C[deep] = 0;
							else C[deep] = 1;
							++deep;
						}
					}
					else
					{
						int sz = k*M;
						while (deep < sz)
						{
							if (sptr[deep] == '1')C[deep] = 1;
							else C[deep] = 0;
							++deep;
						}
					}
					tmp >>= 1;
					++k;
				}
				while (deep < N)
				{
					if (sptr[deep] == '1')C[deep] = 1;
					else C[deep] = 0;
					++deep;
				}
				for (int k = 0; k<M;k++)
				{
					int a = 0, b = 0;
					for (int u = k;u< N;u += M)
					{
						if (C[u]) a++;
						else b++;
					}
					tmpans += min(a, b);
				}
				if (tmpans < ans)ans = tmpans;
			}
		}

		printf("%d\n", ans);
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值