题目
题目链接:https://codeforces.com/problemset/problem/1469/E
定义两个长度均为 (k) 的 01 串 (s,t) 是相似的,当且仅当存在一个 (iin[1,k]),使得 (s_i=t_i)。
给定一个长度为 (n) 的 01 串 (s),求一个长度为 (k) 的 01 串 (t),使得 (t) 与 (s) 所有长度为 (k) 的子串都是相似的。如果有多个解,输出字典序最小的。
多测。
(sum nleq 10^6),(kleq n)。
思路
等价于把 (s) 按位取反,然后求一个字典序最小的长度为 (k) 的 01 串 (t),使得 (t) 不为 (s) 的子串。
因为要字典序最小,那么从前往后依次考虑每一位能不能取 (0)。假设已经确定了前 (i-1) 位,那么第 (i) 位能取 (0) 当且仅当:设 01 串 (t'=) 前 (i-1) 位的 01 串 + 0
,对于 (t') 在 (s) 所有位置出现的子串,其后面 (k-i) 位,所形成的 01 串集合,大小小于 (2^{k-i})。否则如果这一位选 (0),后面无论怎么选都肯定是 (s) 的子串了。
而 (s) 长度为 (k) 的子串数量有 (n-k+1) 个,也就是当 (i>log n) 的时候,这一位一定可以选 (0)。
那么只需要对最后 (20) 位分别处理即可。时间复杂度 (O(n imes max(k,log n)))。
代码
#include <bits/stdc++.h>
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const int N=(1<<20)+50;
int n,m,T,a[N],c[N],b[N];
ll tot,v[N];
bool check()
{
if (m>20) return 1;
int cnt=0;
for (int i=1;i<=n-m+1;i++)
if (v[b[i]]!=T) v[b[i]]=T,cnt++;
if (cnt==(1<<m)) return 0;
return 1;
}
void Work()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
{
scanf("%1d",&a[i]);
c[i]=c[i-1]+a[i]; b[i]=0;
}
for (int i=1;i<=n;i++)
for (int j=1;j<=min(m,20);j++)
b[i]=(b[i]<<1)|a[i+j-1];
if (!check()) { cout<<"NO
"; return; }
cout<<"YES
";
for (int i=21;i<=m;i++) putchar(48);
int k=max(0,m-20),s=0;
m=min(m,20);
for (int i=m;i>=1;i--)
{
int cnt=0; tot++;
for (int j=k+1;j<=n-m+1;j++)
{
if (c[j-1]-c[j-k-1]!=k) continue;
if ((b[j]>>(i-1))!=(s<<1)+1) continue;
int val=(b[j]&((1<<i-1)-1));
if (v[val]!=tot) v[val]=tot,cnt++;
}
if (cnt==(1<<i-1)) s=(s<<1),putchar(49);
else s=(s<<1)|1,putchar(48);
}
cout<<"
";
}
int main()
{
memset(v,-1,sizeof(v));
scanf("%d",&T); tot=T+1;
while (T--) Work();
return 0;
}