题目大意:有 2n 个长度为n的01串,两个01串之间有边当且仅当这两个01串只有一位不同,现在从这 2n 个串中拿掉k个,问指定两个串之间能否到达
吐槽:
先给这题100个差评,这题无论是POI官网还是BZOJ都特别差
先说POI官网,给的题解直接来了个定理也没证,然后说了一句这个定理在opisu中已经给出了,我拿百度翻译翻译了一下发现opisu是description 的意思…可是description 里没给啊,然后我就翻了整个POI2013的波兰文题解也没找到在哪,那就只能把这个定理当成已知了
再说BZOJ,这题总时限开50s,官网上单点都有120s好吗!各种卡常数,最后照着Claris的代码一点一点改,结果把循环变量i开成全局变量就A了…..卡了一上午测评差评!
题解:
先说那个POI官方给的题解里的定理吧,对于一个单位超立方体,如果我们把所有顶点划分成两个集合,这两个点集之间的边的数量至少是两个点集中较小的那一个的个数,也就是
|(u,v):u∈S,v∈V−S|≥min(|S|,|V−S|).
这个我也不会证,不过既然官方说这个题目里告知了,我们就把他当成定理吧
接着我们来证明对于一个单位超立方体,假如挖掉k个节点,剩下的结点个数大于nk的连通块至多有一个
用反证法
假设有两个这样的连通块,另
S
为其中一个连通块的点集,
则由定理可知S与V-S这两个集合之间的边至少为nk+1条
接着因为一共删除了k个点,而每个点都向外连了n条边,所以最多最多只能删掉这nk+1条中的nk条边,也就是说S和V-S一定还有边相连,这就与之前假设的S是一个独立的连通块矛盾了
所以剩下的结点个数大于nk的连通块至多有一个
所以我们只需要从一个节点开始BFS,限制最多扩展nk步,如果两个都扩展了nk步还没搜完,那就说明他俩在同一个大块里
各种卡常数啊,unordered_map貌似都不行,只能手写hash链表
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 5001000
#define M 9999991
using namespace std;
int i,head[M],nxt[N],cnt;
long long v[N];
inline void add(long long x)
{
int T=x%M,i;
cnt++;nxt[cnt]=head[T];
head[T]=cnt;v[cnt]=x;
}
long long q[N],h,t;
inline void checkandadd(long long x)
{
int T=x%M,i;
for(int i=head[T];i;i=nxt[i])
if(v[i]==x) return;
cnt++;nxt[cnt]=head[T];
head[T]=cnt;v[cnt]=x;
t++;q[t]=x;
}
int n,k;
char s[101];
inline long long dec()
{
scanf("%s",s);
long long ret=0,i;
for(int i=0;i<n;i++)
ret=ret*2+s[i]-48;
return ret;
}
long long S,E;
long long a[N];
int main()
{
scanf("%d%d",&n,&k);
int nk=n*k+5;
long long x,y;
S=dec();
E=dec();
for(i=1;i<=k;i++)
{
a[i]=dec();
add(a[i]);
}
h=t=1;q[1]=S;
add(S);
while(h<=t&&t<=nk)
{
x=q[h];h++;
if(x==E) {puts("TAK");return 0;}
for(i=0;i<n;i++)
checkandadd(x^(1LL<<i));
}
if(t<=nk) {puts("NIE");return 0;}
h=t=1;q[1]=E;
cnt=0;
for(i=0;i<M;i++)
head[i]=0;
add(E);
for(i=1;i<=k;i++)
add(a[i]);
while(h<=t&&t<=nk)
{
x=q[h];h++;
if(x==S) {puts("TAK");return 0;}
for(i=0;i<n;i++)
checkandadd(x^(1LL<<i));
}
if(t<=nk) puts("NIE");
else puts("TAK");
}