@description@
我们称一个有向图G是传递的,当且仅当对任意三个不同的顶点a,,若G中有一条边从a到b且有一条边从b到c ,则G中同样有一条边从a到c。
现在,给你两个有向图P = (V,Ep)和Q = (V,Ee),满足 Ep与Ee没有公共边且 (V,Ep⋃Ee)是一个竞赛图(有向完全图)。
你的任务是:判定是否P,Q同时为传递的。
Input
包含至多20组测试数据。
第一行有一个正整数,表示数据的组数。
对于每组数据,第一行有一个正整数n。接下来n行,每行为连续的n个字符,每 个字符只可能是’-’,’P’,’Q’中的一种。
∙如果第i行的第j个字符为’P’,表示有向图P中有一条边从i到j;
∙如果第i行的第j个字符为’Q’,表示有向图Q中有一条边从i到j;
∙否则表示两个图中均没有边从i到j。
保证1 <= n <= 2016,一个测试点中的多组数据中的n的和不超过16000。保证输入的图一定满足给出的限制条件。
Output
对每个数据,你需要输出一行。如果P 与 Q都是传递的,那么请输出’T’。否则, 请输出’N’ (均不包括引号)。
Sample Input
4
4
-PPP
--PQ
---Q
----
4
-P-P
--PQ
P--Q
----
4
-PPP
--QQ
----
--Q-
4
-PPP
--PQ
----
--Q-
Sample Output
T
N
T
N
@solution@
感觉正解还是挺巧妙的,虽然我在网上看到的题解大多是用乱搞水过去的。。。
考虑如果不传递,那么一定存在 a, b, c 使得 a->b, b->c 但不存在 a->c。
不存在 a->c 有几种情况呢?一种是存在 c->a,一种是在另一个图中存在 a->c/c->a。
假如存在 c->a,则形成一个三元环。所以其中一个不合法的判定是找图中是否有环。
那如果 a, c 之间的边在另一边呢?假如另一边也是 c->a,则将两个图拼在一起后又形成了三元环。
那么是否拼在一起后原本不会出环的地方变出环来呢?答案是不会。考虑竞赛图有一个性质:如果有环,则必然有三元环(这个性质还是很容易证的吧)。枚举一下三元环中边的所属情况,发现无论如何都会落入上面的不合法情况。
所以,我们可以通过将图 P 和图 Q 拼起来找环,解决“存在 c->a”,“另一个图中存在 c->a” 这两种不合法情况。
那么“另一个图中存在 a->c”怎么办呢?我们可以考虑将图 Q 的边反向过后在和图 P 拼起来再找环,一样可以证明这样是不会讲合法误判成不合法。
至于找环 tarjan 啊拓扑排序啊都可以找的。
@accepted code@
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 2016;
int G[MAXN + 5][MAXN + 5], n;
int tid[MAXN + 5], low[MAXN + 5], dcnt, tot;
int stk[MAXN + 5], tp; bool vis[MAXN + 5];
char str[MAXN + 5];
int func(char ch) {
if( ch == 'P' ) return 1;
else if( ch == 'Q' ) return -1;
else return 0;
}
void dfs(int x) {
tid[x] = low[x] = (++dcnt);
vis[x] = true; stk[++tp] = x;
for(int i=1;i<=n;i++) {
if( !G[x][i] ) continue;
if( !tid[i] )
dfs(i), low[x] = min(low[x], low[i]);
else if( vis[i] )
low[x] = min(low[x], tid[i]);
}
if( low[x] >= tid[x] ) {
tot++;
while( tp && tid[stk[tp]] >= tid[x] )
vis[stk[tp]] = false, tp--;
}
}
void solve() {
scanf("%d", &n);
for(int i=1;i<=n;i++) {
scanf("%s", str + 1);
for(int j=1;j<=n;j++)
G[i][j] = func(str[j]);
}
dcnt = tot = 0;
for(int i=1;i<=n;i++) tid[i] = 0;
for(int i=1;i<=n;i++)
if( !tid[i] ) dfs(i);
if( tot != n ) {
puts("N");
return ;
}
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
if( G[i][j] == -1 || G[j][i] == -1 )
swap(G[i][j], G[j][i]);
dcnt = tot = 0;
for(int i=1;i<=n;i++) tid[i] = 0;
for(int i=1;i<=n;i++)
if( !tid[i] ) dfs(i);
if( tot != n ) {
puts("N");
return ;
}
puts("T");
}
int main() {
int T; scanf("%d", &T);
for(int i=1;i<=T;i++) solve();
}
@details@
感觉这么巧妙的正解(看起来有问题但实际上严格正确),为什么网上都是用乱搞过的。。。
以及我为什么要用 tarjan 找环而不直接写个拓扑排序就好了。。。