【题解】 CF1481D AB Graph 构造+图论
Legend
给定一张有向完全图(比竞赛图还多一倍的边),每条边有 \(a\) 或 \(b\) 字样,问图中是否存在一条长 \(m\) 的回文路径。
Link \(\textrm{to Codeforces}\)。
Editorial
已经发现一个更简单做法,如不想看麻烦做法,可以跳到 Editorial 2。
很容易想到,如果存在一条边,它与它的反边相同,那就一直走这两条就行了。
- 现在图变成正边和反边权值不同了!
我们只看为 \(a\) 的边,很容易想到,如果成环,一直沿着环走就行了。
- 所以现在图中没有环了!(因为 \(a\) 有环,其反图 \(b\) 必然有环;\(a\) 没环,\(b\) 也必然没环)
- 也就是现在图中是一个 DAG!
接下来我们再来看比较 trivial 的部分,\(m\) 为奇数的是时候怎么做?
- 显然随便 \(1\to 2\to 1 \to 2 \to \cdots \to 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 ," \n"[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()] ," \n"[j == m + 1]);
}
return ;
}
}
// 没找到环啊,那就是个 DAG 呗
if(m & 1){
puts("YES");
for(int i = 1 ; i <= m + 1 ; ++i)
printf("%d%c" ,(i & 1) + 1 , " \n"[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\)
可以证明,如果排除掉之前的两种情况看,并且 \(n\ge 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) ," \n"[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 ," \n"[j == rep]);
}
return;
}
}
assert(0);
}
int main(){
int T = read();
for(int i = 1 ; i <= T ; ++i){
solve();
}
return 0;
}