3 Split - The 8th Hebei Collegiate Programming Contest, Problem F
给定n个点的竞赛图,给出一个方案把n个点分为A,B,C三个非空集合,使得所有的边要么连接着相同集合的点,要么从A指向B,要么从B指向C,要么从C指向A。
考虑钦定点 1 属于 B 集合,考察 1 的所有边。以 1 为起点的边,其终点必然属于 B 或 C 中的一个;同理,以 1 为终点的边,其起点必然属于 B 或 A 中的一个。并且,竞赛图保证了所有点都与 1 有边,即上述讨论可以覆盖到所有点。那么除 1 外的每个点都有两种选择,可以转化为 2-SAT 问题求解,简单分析可以知道每一条与 1 无关的边都可以转换为 2-SAT 上的一个限制。
于是问题可以转换为 2-SAT 上求每一个点对中拓扑序较小的那个点是否合法,通过 floyd 或 n 次 dfs 计算连通性是 \(O(n^3)\) 的。不清楚是否可以优化成平方级别。
换个思路,其实竞赛图+非空集合这两个限制叠加在一起是非常紧的。我们可以通过考察三个点的两两关系证明如果存在方案,那么方案在轮换等价的意义上是唯一的。
依然考虑钦定 1 属于 B,并假设存在一个合法方案。想要构造一个新方案,不失一般性,假设至少有一个原本属于 C 的点变得不再属于 C。任意选取集合 A 中的一个点 a,集合 C 中的一个点 c,则一定有 \(a\to 1\),\(1\to c\) 和 \(c\to a\)。如果 c 在新方案中不再属于 C,因为有 \(1\to c\),那么它只能属于 B,由于存在 \(c\to a\),那么 a 也必须改为属于 B。由于 a 的任意性,原 A 集合内的所有点都必须改为 B。类似的,现在 a 被改为了 B,那么集合 C 中的所有点也必须改为 B。也就是说,要想这个新方案合法,原 B 集合必须有一些点改为 A 和 C,但这也是不可能的。因为现在 a 和 c 都属于了 B,如果有另一个属于 B 的 b(要求 |B|>1) 变成了属于 A,则不符合 \(a\to b\),同理这个 b 也不能属于 C。综上,要构造一个新方案,至少有一个原本属于 A 或 C 的点会变为属于 B,进一步会推出 A 和 C 集合必须为空,故不存在合法新方案。
于是可以钦定 \(1\in B\),枚举 \(x\in A\),\(y\in C\) 满足是一个三元环,然后将剩下的点一个个加入进去。如果存在一个合法方案且这个方案中 \(1\in B\),\(x\in A\),\(y\in C\),那么其所有顶点包含 1,x 和 y 的子图也都存在合法方案且唯一,所以每次加入也最多有一种合法方式,一旦无法放入,说明最终的合法方案中 \((x,1,y)\) 不是分属于 A B C,而是同属于 B。那么就换一对 \((x,y)\) 重复上述步骤。这样做复杂度是 \(O(n^4)\)。
为了找到正确的三元组 \((x,1,y)\),在没有合法方案的最坏情况下需要枚举所有 \((x,y)\),这一步花了太多时间。我们考虑优化这一点。假设我们找到了第一个 \((x,1,y)\),此时如果存在合法方案并且 \((x,1,y)\) 在最终方案中并非分属于 A B C(即同属于 B),那么说明存在一个 \((x',y')\),使得 \(1,x,y\in B\), \(x'\in A\),\(y'\in C\) 。所以我们不断地去找有没有可以把之前枚举的所有点放在 B 里的新的 \(x'\) 和 \(y'\),一旦找不到了,就说明在存在合法方案的情况下,最后枚举到的 \(x'\) 和 \(y'\) 一定就在方案中分别属于 A 和 C,而之前枚举的所有点包括 1 都属于 B,然后再一个个把剩下的点加入即可,复杂度可以做到 \(O(n^2)\)。
题解里写到用 random-shuffle 做到 n 方,不过没怎么搞清楚题解的意思。
再给一个不需要任何性质的简单做法。仍然考虑钦定 1 为 B,将 1 连向的所有点拿出来做 tarjan scc,由于竞赛图,肯定有且仅有一个汇点 scc。如果存在解,那么这个 scc 中必然全为 C,因此至少可以确定一个在 C 中的点。有了一个 B 点和一个 C 点,我们可以确定所有的 A 点,进而确定所有 B 和 C 点。这个做法也能证明解的唯一性,也是 \(O(n^2)\)。
这里给出第二种做法的代码
#include<bits/stdc++.h>
using ll=long long;
using namespace std;
void solve();
int main(){
int t=1;
// cin>>t;
while(t--)solve();
return 0;
}
const int N=503;
int a[N][N];
int st[N];
void solve(){
int n;
cin>>n;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++){
cin>>a[i][j];
a[j][i]=a[i][j]^1;
}
for(int i=2;i<=n;i++){
if(a[i][1])st[i]=1;
else st[i]=2;
}
auto add=[&](int u){
st[u]=0;
for(int i=1;i<=n;i++)if(st[i]){
if(st[i]==1&&a[u][i])st[i]=3;
if(st[i]==2&&a[i][u])st[i]=3;
}
};
int x=-1,y=-1; // x->1->y
for(int i=2;i<=n;i++)if(st[i]==1){
for(int j=2;j<=n;j++){
if(a[j][i]&&st[j]==2){
add(i);
add(j);
x=i;y=j;
break;
}
}
}
if(x==-1){
cout<<"0 0 0\n";
return;
}
st[x]=-1;//A
st[y]=-2;//C
vector<int>A,B,C;
A.push_back(x);
C.push_back(y);
for(int i=1;i<=n;i++)if(st[i]==0)B.push_back(i);
for(int i=1;i<=n;i++){
if(st[i]>0){
int wb=a[i][1],wa=a[i][x],wc=a[i][y];
for(auto j:A)if(wa!=-1&&(wa^a[i][j])){
wa=-1;break;
}
for(auto j:B)if(wb!=-1&&(wb^a[i][j])){
wb=-1;break;
}
for(auto j:C)if(wc!=-1&&(wc^a[i][j])){
wc=-1;break;
}
if(wb==1&&wc==0)A.push_back(i);
else if(wc==1&&wa==0)B.push_back(i);
else if(wa==1&&wb==0)C.push_back(i);
else{
// cerr<<"!\n";
cout<<"0 0 0\n";
return;
}
}
}
cout<<A.size()<<" "<<B.size()<<" "<<C.size()<<endl;
for(auto j:A)cout<<j<<" ";cout<<endl;
for(auto j:B)cout<<j<<" ";cout<<endl;
for(auto j:C)cout<<j<<" ";cout<<endl;
}
浙公网安备 33010602011771号