一、题目
二、解法
虽然没有做出这道结论题,但是由于在没见过兰道定理的情况下直接把它当结论推出来了,还是很开心的。如果我比兰道早出生那么这个定理就改名了。
Theorem one(Landau's Theorem):一个竞赛图强连通的充要条件是把把所有点按入度排序之后,对于任意 (kin [1,n)) 都不满足 (sum_{i=1}^k d_i={kchoose2})
Lamma:一个竞赛图强连通的充要条件是对于任意集合 (Ssubsetneq V),都存在 (v otin S) 有一条 (v) 连向 (S) 内部点的边。
必要性显然,充分性证明可以考虑用强连通图的耳分解,考虑现在得到了一个强连通点集 (S),设不在强连通块内的点集为 (T),设集合 (G) 里面包含所有 (vin S) 满足 (v) 有边连向 (S),应用上面的条件可以找到一点 (u) 使得 (u) 有边连向 (G),无论 (u) 是否属于集合 (S),我们一定找到一条路径扩展,那么最后可以得到一张强连通图。
根据引理我们只需要判断有没有"入度"为 (0) 的点集即可,这等价于 (sum_{iin S}d_i={|S|choose 2}),也就是只有集合内部有入边,而我们又知道存在不等式 (sum_{iin S}d_igeq {|S|choose 2}),依据贪心原理排序之后判断即可,就得到了兰道定理。
Theorem two:如果 (ngeq 4),那么 (n) 阶强连通竞赛图存在 (n-1) 阶强连通子图。
Proof:考虑一个 (n-1) 阶的竞赛图,缩点之后按拓扑序的顶点序列 ({a}={a_1,a_2...a_k}),然后向这个图里加入点 (n) 及其对应的边,得到 (n) 阶强连通图,分类讨论如下几种情况:
- (k=1),直接得到解。
- (kgeq 3),我们从 (a_m(1<m<k)) 中选一个点删掉,因为一定存在边 ((a_n,n),(n,a_1)),那么剩下任意点对 ((u,v)) 一定存在 ({u...a_k,n,a_1...v}) 的一条路径。
- (k=2),(max(a_1,a_2)geq 2),假设是 (a_2geq 2),找到有连 (n) 的边的点 (v),考虑一点 (u) 如果到 (v) 必须经过点 (x),就连一条 ((u,x)) 的有向边,那么在得到的拓扑图里选一个入度为 (0) 的点删去即可。
deduction:如果 (ngeq 4) 的强连通竞赛图,总可以翻转恰好一个点使得它还是强连通的。
根据定理二,翻转那个多出来的点即可。
Theorem three:(n>6) 时总可以通过翻转至多一个点使得原图变成强连通的。
proof:设原图缩点之后按拓扑序的顶点序列 ({a}={a_1,a_2...a_k}),分类讨论:
- (k=1),不需要翻转。
- (kgeq 3),选 (a_m(1<m<k)) 中任意一个点 (x) 翻转,任意点对 ((u,v)) 都有路径 ({u...a_k,x,a_1...v})
- (k=2),由于 (n>6) 则 (max(a_1,a_2)geq 4),假设是 (a_2geq 4),那么选一个点翻转后 (a_2) 仍然强连通,而且翻转之后存在环,所以整个图强连通。
所有结论证完了(体力活),如果 (nleq 6) 我们暴力枚举翻转点,如果 (n>6) 我们只需要枚举一个翻转点,然后用兰道定理判一下即可,时间复杂度 (O(n^2log n))
#include <cstdio>
#include <algorithm>
using namespace std;
const int M = 2005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,num,ans,v[M],d[M],t[M],a[M][M];
void reverse(int x)
{
d[x]=n-d[x]-1;
for(int i=1;i<=n;i++)
{
d[i]-=a[i][x];
a[x][i]^=1;
a[i][x]^=1;
d[i]+=a[i][x];
}
}
int check()
{
for(int i=1;i<=n;i++) t[i]=d[i];
sort(t+1,t+1+n);
for(int i=1,s=0;i<n;i++)
{
s+=t[i];
if(s==i*(i-1)/2) return 0;
}
return 1;
}
void dfs(int x,int y)
{
if(check())
{
if(y<num) num=y,ans=0;
if(num==y) ans++;
}
for(int i=1;i<=n;i++)
if(!v[i])
{
v[i]=1;reverse(i);
dfs(x+1,y+1);
v[i]=0;reverse(i);
}
}
signed main()
{
n=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
scanf("%1d",&a[i][j]);
if(a[i][j]) d[i]++;
}
if(n<=6)
{
num=n,dfs(1,0);
if(num==n) puts("-1");
else printf("%d %d
",num,ans);
}
else
{
if(check())
{
puts("0 1");
return 0;
}
for(int i=1;i<=n;i++)
{
reverse(i);
ans+=check();
reverse(i);
}
printf("1 %d
",ans);
}
}