【题解】 CF1481D AB Graph 构造+图论

Legend

给定一张有向完全图(比竞赛图还多一倍的边),每条边有 (a)(b) 字样,问图中是否存在一条长 (m) 的回文路径。

Link ( extrm{to Codeforces})

Editorial

已经发现一个更简单做法,如不想看麻烦做法,可以跳到 Editorial 2

很容易想到,如果存在一条边,它与它的反边相同,那就一直走这两条就行了。

  • 现在图变成正边和反边权值不同了!

我们只看为 (a) 的边,很容易想到,如果成环,一直沿着环走就行了。

  • 所以现在图中没有环了!(因为 (a) 有环,其反图 (b) 必然有环;(a) 没环,(b) 也必然没环)
  • 也就是现在图中是一个 DAG!

接下来我们再来看比较 trivial 的部分,(m) 为奇数的是时候怎么做?

  • 显然随便 (1 o 2 o 1 o 2 o cdots o 2) 就做完了!

那么 (m) 为偶数呢?

看看我们现在能做什么?

  • 找到最长链,我们就能走出至多 (n-1) 个连续的 (a)(b)

所以大概可以构造一个形如 (ababab|bababa) 的东西,转化成答案序列就是 (1n1n1n2|1n1n1n) 这样的。

或者是 (ababa|ababa),也就是 (1n1n1(n-1)|n1n1n)

那么什么时候这样子的构造不合法呢?

  • (n=2)!因为这个时候我们只能走出最长 (1)(a)(b)

于是我们做完啦!

Code

虽然感觉我这个分析题目的方式挺正常的,但是这个代码长度为啥就这么长了呢。。。。。

讲几个实现细节,我是怎么找到最长链的——

考虑图是一个竞赛图而且是 DAG,所以我们就按出度排序,就是拓扑序了。

#include <bits/stdc++.h>

#define debug(...) fprintf(stderr ,__VA_ARGS__)
#define __FILE(x)
	freopen(#x".in" ,"r" ,stdin);
	freopen(#x".out" ,"w" ,stdout)
#define LL long long

const int MX = 1000 + 23;
const LL MOD = 998244353;

int read(){
	char k = getchar(); int x = 0;
	while(k < '0' || k > '9') k = getchar();
	while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
	return x;
}

int head[MX] ,tot = 1;
struct edge{
	int node ,next;
}h[MX * MX];
void addedge(int u ,int v){
	h[++tot] = (edge){v ,head[u]} ,head[u] = tot;
}

char E[MX][MX];
int vis[MX] ,inq[MX] ,stk[MX] ,dep;
int D[MX];

std::vector<int> cyc;
int getcycle(int x){
	if(vis[x]) return 0;
	stk[++dep] = x;
	inq[x] = vis[x] = 1;
	for(int i = head[x] ,d ; i ; i = h[i].next){
		d = h[i].node;
		if(inq[d]){
			cyc.push_back(d);
			while(stk[dep] != d){
				cyc.push_back(stk[dep--]);
			}
			return 1;
		}
		if(getcycle(d)) return 1;
	}
	--dep;
	inq[x] = 0;
	return 0;
}

bool cmp(int a ,int b){return D[a] > D[b];}
int T ,test;
void solve(){
	cyc.clear();
	tot = 0;
	memset(vis ,0 ,sizeof vis);
	memset(inq ,0 ,sizeof inq);
	dep = 0;
	memset(D ,0 ,sizeof D);
	memset(head ,0 ,sizeof head);
	int n = read() ,m = read();
	for(int i = 1 ; i <= n ; ++i){
		scanf("%s" ,E[i] + 1);
	}

	for(int i = 1 ; i <= n ; ++i){
		for(int j = i + 1 ; j <= n ; ++j){
			if(E[i][j] == E[j][i]){
				puts("YES");
				for(int k = 1 ; k <= m + 1 ; ++k)
					printf("%d%c" ,k & 1 ? i : j ," 
"[k == m + 1]);
				return ;
			}
		}
	}
	for(int i = 1 ; i <= n ; ++i)
		for(int j = 1 ; j <= n ; ++j)
			if(E[i][j] == 'a'){
				++D[i];
				addedge(i ,j);
			}
	for(int i = 1 ; i <= n ; ++i){
		if(getcycle(i)){
			puts("YES");
			for(int j = 1 ; j <= m + 1 ; ++j){
				printf("%d%c" ,cyc[j % cyc.size()] ," 
"[j == m + 1]);
			}
			return ;
		}
	}
	// 没找到环啊,那就是个 DAG 呗
	if(m & 1){
		puts("YES");
		for(int i = 1 ; i <= m + 1 ; ++i)
			printf("%d%c" ,(i & 1) + 1 , " 
"[i == m + 1]);
	}
	else if(n >= 3){
		puts("YES");
		std::vector<int> orz;
		for(int i = 1 ; i <= n ; ++i) orz.push_back(i);
		std::sort(orz.begin() ,orz.end() ,cmp);

		int last = orz[0];
		printf("%d " ,last);
		for(int i = 1 ; i <= m / 2 ; ++i){
			int cur = 0;
			if(i + 1 > m / 2){
				if(i & 1){ // is a
					cur = orz[n - 2];
				}
				else{ // is b
					cur = orz[1];
				}
			}
			else{
				if(i & 1) cur = orz[n - 1];
				else cur = orz[0];
			}	
			printf("%d " ,cur);
		}
		for(int i = m / 2 ,cur ; i >= 1 ; --i){
			if(i & 1) cur = orz[n - 1];
			else cur = orz[0];
			printf("%d " ,cur);
		}
		puts("");
	}
	else{
		puts("NO");
	}
}

int main(){
	int t = read(); test = t;
	while(t--) ++T ,solve();
	return 0;
}

Editorial 2

很容易想到,如果存在一条边,它与它的反边相同,那就一直走这两条就行了。

很容易想到如果 (m) 为奇数,也可以搞。

然后如果存在如下的一张子图,那也可以搞:

1----->2
|  a
|
|b
V
3

即一个点同时有 (a)(b) 的出边。

  • (frac{m}{2}) 为奇数时,可以走:(21213131)
  • (frac{m}{2}) 为偶数时,可以走:(121213131)

可以证明,如果排除掉之前的两种情况看,并且 (nge 3),那么图中一定会有这样的一个子图。

于是就有了这份更简单的代码:

#include <bits/stdc++.h>

#define debug(...) fprintf(stderr ,__VA_ARGS__)
#define __FILE(x)
	freopen(#x".in" ,"r" ,stdin);
	freopen(#x".out" ,"w" ,stdout)
#define LL long long

const int MX = 1000 + 233;
const LL MOD = 998244353;

int read(){
	char k = getchar(); int x = 0;
	while(k < '0' || k > '9') k = getchar();
	while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
	return x;
}

char s[MX][MX];
void solve(){
	int n = read() ,m = read();
	for(int i = 1 ; i <= n ; ++i) scanf("%s" ,s[i] + 1);
	for(int i = 1 ; i <= n ; ++i)
		for(int j = i + 1 ; j <= n ; ++j)
			if(s[i][j] == s[j][i] || (m & 1)){
				puts("YES");
				for(int k = 0 ; k <= m ; ++k) printf("%d%c" ,(k & 1 ? i : j) ," 
"[k == m]);
				return ;
			}
	if(n == 2){
		puts("NO");
		return ;
	}
	for(int i = 1 ; i <= n ; ++i){
		int o[2] = {0 ,0};
		for(int j = 1 ; j <= n ; ++j){
			if(j == i) continue;
			if(s[i][j] == 'a') o[0] = j;
			else o[1] = j;
		}
		if(o[0] && o[1]){
			puts("YES");
			int rep = m / 2 ,tr = i ^ o[0] ,st = 0;
			if(rep & 1) st = o[0];
			else st = i;
			for(int j = 0 ; j <= rep ; ++j){
				printf("%d " ,st);
				st ^= tr;
			}
			st ^= tr;
			tr = i ^ o[1];
			for(int j = 1 ; j <= rep ; ++j){
				st ^= tr;
				printf("%d%c" ,st ," 
"[j == rep]);
			}
			return;
		}
	}
	assert(0);
}

int main(){
	int T = read();
	for(int i = 1 ; i <= T ; ++i){
		solve();
	}
	return 0;
}
原文地址:https://www.cnblogs.com/imakf/p/14460710.html