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
N−M后缀等于
N
−
M
N-M
N−M前缀。
s
[
i
]
s[i]
s[i]为字符串第
i
i
i个字符。则:
s
[
i
]
=
s
[
i
+
m
]
,
0
≤
i
<
N
−
M
s[i] = s[i+m],0\leq i< N-M
s[i]=s[i+m],0≤i<N−M
那么显然,
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
<
=
300
,
N
<
18
N<=300, \sqrt{N}<18
N<=300,N<18.
当 M < = N M<=\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=0mini−1(dp[i−t−1]+(t+1)M−(cost[i]−cost[i−t−1])+2,(t+1)M−cost[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[i−1]+bitcnt[A[i]xorD]]dp[0]=min(cost[0],M−cost[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[i−1]直接计算得到。这样可以进一步提高效率,不过现在已经足够了。
当 M > N M>\sqrt{N} M>N时。即: ⌊ N M ⌋ < N \Big\lfloor\frac{N}{M}\Big\rfloor<\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;
}