【CF1469E】A Bit Similar
题目
题目链接:https://codeforces.com/problemset/problem/1469/E
定义两个长度均为 \(k\) 的 01 串 \(s,t\) 是相似的,当且仅当存在一个 \(i\in[1,k]\),使得 \(s_i=t_i\)。
给定一个长度为 \(n\) 的 01 串 \(s\),求一个长度为 \(k\) 的 01 串 \(t\),使得 \(t\) 与 \(s\) 所有长度为 \(k\) 的子串都是相似的。如果有多个解,输出字典序最小的。
多测。
\(\sum n\leq 10^6\),\(k\leq 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\times \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\n"; return; }
cout<<"YES\n";
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<<"\n";
}
int main()
{
memset(v,-1,sizeof(v));
scanf("%d",&T); tot=T+1;
while (T--) Work();
return 0;
}