Solution -「JOISC 2020」「UOJ 509」迷路的貓
\(\mathcal{Decription}\)
Link.
這是一道通訊題。
給定一個 \(n\) 個點 \(m\) 條邊的連通無向圖與兩個限制 \(A,B\)。
程式 Anthony
需要用 \(0\sim A-1\) 共 \(A\) 中顏色為無向圖的每條邊染色。
程式 Catherine
需要幫助一隻貓行走:已知貓所在結點鄰接每種顏色的邊的數量,你需要告訴貓走哪種顏色的邊(但不能讓它走特定某條),並保證貓從起點 \(s\) 到 \(0\) 所走的距離不超過兩點最短距離 \(+B\)。
\(n,m\le2\times10^4\),\(A,B\) 滿足 \(A\ge 3,B\ge0\)
\(\mathcal{Solution}\)
就**離譜好嗎 qwq!
\(\mathcal{Anthony}\)
嘛……不難看出兩種情況是割裂的,分類討論咯。
\(\mathcal{Part~1}\)
\(A=3,B=0\),相當於要求我們必須走最短路。
考慮從 \(0\) 出發,用 BFS 將圖分層,設結點到 \(0\) 的距離為 \(dist_i\) 的點都在第 \(dist_i\) 層。注意貓是要從終點向回走,所以我們應當把連線上一層的邊和連線同層或連線下一層的邊區分開來。一種方法是令 \(color(u,v)=\min\{dist_u,dist_v\}\bmod3\)
\(\mathcal{Part~1-Code}\)
namespace Task1 { bool vis[MAXN + 5]; inline vecint main ( vecint U, vecint V ) { std::queue<int> que; que.push ( 0 ), vis[0] = true; while ( ! que.empty () ) { int u = que.front (); que.pop (); for ( pii v: graph[u] ) if ( ! vis[v.first] ) { dist[v.first] = dist[u] + 1; que.push ( v.first ), vis[v.first] = true; } } vecint ret ( m ); for ( int i = 0; i < m; ++ i ) ret[i] = std::min ( dist[U[i]], dist[V[i]] ) % 3; return ret; } } // namespace RybyA::Task1.
\(\mathcal{Part~2}\)
\(A=2,B=6\),一棵樹。如果以 \(0\) 為根,無腦往上走就好啦。問題在於如何將一個結點的父親與兒子們區分開來。
若 \(d_u>2\),即 \(u\) 有多於一個兒子,那麼令向兒子們的顏色相同,向父親的顏色與之區分即可。
若 \(d_u=2\),即鏈。怎麼區分上下呢……我們考慮用一串迴圈顏色構造一個“通行方向箭頭”,這樣能讓貓在走了一定的遠路之後,可以認出箭頭的指向繼而調轉方向。可見,“箭頭”需要滿足:所有由其迴圈同構串作為迴圈節的串不存在長度 \(\ge\lfloor\frac{B}2\rfloor+2\) 的迴文子串。說人話呢,算上起點,貓最多用長度為 \(\lfloor\frac{B}2\rfloor+1\) 的鏈“認路”,那麼貓最多看到 \(\lfloor\frac{B}2\rfloor+2\) 條邊的顏色。如果顏色是迴文,還是認不出路,就失敗啦!
一種可行的“箭頭”是 0 0 1 1 0 1
,恰好滿足條件(0 1 1 0
為最長迴文,長度為 \(4\))。
\(\mathcal{Part~2-Code}\)
namespace Task2 {
const int dir[] { 0, 0, 1, 1, 0, 1 };
vecint ans;
inline void direct ( const int u, const int f, const int clen, const int curc ) {
// clen為鏈長(不在鏈上則為0),curc是若u不在鏈上時需要為向兒子的邊染的顏色。
if ( ! ~ f ) {
for ( pii v: graph[u] ) {
ans[v.second] = 0;
direct ( v.first, u, ( int ) graph[u].size () == 1, 1 );
}
return ;
}
if ( ( int ) graph[u].size () == 2 ) {
for ( pii v: graph[u] ) if ( v.first ^ f ) {
ans[v.second] = dir[clen % 6];
direct ( v.first, u, clen + 1, ans[v.second] ^ 1 );
}
return ;
}
for ( pii v: graph[u] ) if ( v.first ^ f ) {
ans[v.second] = curc;
direct ( v.first, u, 0, ans[v.second] ^ 1 );
}
}
inline vecint main () {
ans.resize ( m );
direct ( 0, -1, 0, 0 );
return ans;
}
} // namespace RybyA::Task2.
\(\mathcal{Catherine}\)
大 模 擬 !
\(\mathcal{Part~1}\)
顯。(因為下一部分太複雜 qwq……
\(\mathcal{Part~1-Code}\)
namespace Task1 {
inline int main ( vecint Y ) {
// last 為上一次走的顏色,Part2同理。
int cnt = 0;
if ( ~ last ) ++ Y[last];
for ( int y: Y ) cnt += !! y;
if ( cnt == 1 ) last = Y[2] ? 2 : !! Y[1];
else {
for ( int i = 0; i < 3; ++ i ) {
if ( ! Y[i] ) {
last = ( i + 1 ) % 3;
break;
}
}
}
return last;
}
} // namespace RybyC::Task1.
\(\mathcal{Part~2}\)
首先,記錄是否已經定好方向。若已定向,無腦走即可。若未定向且當前點仍在鏈上,則全域性用一個 std::vector
記下經過的箭頭顏色。當 std::vector
的 size()
為 \(5\),就一定可以定向了,打表判一判即可。
反正……億點細節,見程式碼吧(絕望 qwq。
\(\mathcal{Part~2-Code}\)
namespace Task2 {
const int down[6][5] {
{ 0, 0, 1, 1, 0 },
{ 0, 1, 1, 0, 1 },
{ 1, 1, 0, 1, 0 },
{ 1, 0, 1, 0, 0 },
{ 0, 1, 0, 0, 1 },
{ 1, 0, 0, 1, 1 }
};
vecint chain;
bool directed;
inline int main ( vecint Y ) { // 注意需要在外部用返回值更新last。
if ( ! ~ last ) {
if ( Y[0] + Y[1] == 2 ) {
if ( ! Y[1] ) {
chain.push_back ( 0 ), chain.push_back ( 0 );
return 0;
} else if ( ! Y[0] ) {
chain.push_back ( 1 ), chain.push_back ( 1 );
return 1;
} else {
chain.push_back ( 1 ), chain.push_back ( 0 );
return 0;
}
}
if ( ! Y[0] ) return directed = true, 1;
if ( ! Y[1] ) return directed = true, 0;
return directed = true, Y[1] == 1;
}
if ( ! Y[0] && ! Y[1] ) return directed = true, -1;
if ( ! directed ) {
if ( Y[0] + Y[1] > 1 ) {
directed = true;
if ( ! Y[0] || ! Y[1] ) return -1;
return ! last;
}
chain.push_back ( Y[1] );
if ( chain.size () == 5 ) {
bool downing = false; directed = true;
for ( int i = 0; ! downing && i < 6; ++ i ) {
int j = 0;
for ( ; j < 5; ++ j ) if ( down[i][j] ^ chain[j] ) break;
if ( j == 5 ) downing = true;
}
if ( downing ) return -1;
}
return chain.back ();
} else {
if ( ! Y[0] ) return 1;
if ( ! Y[1] ) return 0;
++ Y[last];
return Y[1] == 1;
}
}
} // namespace RybyC::Task2.
} // namespace RybyC.
\(\mathcal{Code}\)
// Anthony.cpp
#include <queue>
#include <vector>
#include <iostream>
#include "Anthony.h"
#ifndef vecint
#define vecint std::vector<int>
#endif
#ifndef pii
#define pii std::pair<int, int>
#endif
namespace RybyA {
const int MAXN = 2e4;
int n, m, dist[MAXN + 5];
std::vector<pii> graph[MAXN + 5];
namespace Task1 {
bool vis[MAXN + 5];
inline vecint main ( vecint U, vecint V ) {
std::queue<int> que;
que.push ( 0 ), vis[0] = true;
while ( ! que.empty () ) {
int u = que.front (); que.pop ();
for ( pii v: graph[u] ) if ( ! vis[v.first] ) {
dist[v.first] = dist[u] + 1;
que.push ( v.first ), vis[v.first] = true;
}
}
vecint ret ( m );
for ( int i = 0; i < m; ++ i ) ret[i] = std::min ( dist[U[i]], dist[V[i]] ) % 3;
return ret;
}
} // namespace RybyA::Task1.
namespace Task2 {
const int dir[] { 0, 0, 1, 1, 0, 1 };
vecint ans;
inline void direct ( const int u, const int f, const int clen, const int curc ) {
if ( ! ~ f ) {
for ( pii v: graph[u] ) {
ans[v.second] = 0;
direct ( v.first, u, ( int ) graph[u].size () == 1, 1 );
}
return ;
}
if ( ( int ) graph[u].size () == 2 ) {
for ( pii v: graph[u] ) if ( v.first ^ f ) {
ans[v.second] = dir[clen % 6];
direct ( v.first, u, clen + 1, ans[v.second] ^ 1 );
}
return ;
}
for ( pii v: graph[u] ) if ( v.first ^ f ) {
ans[v.second] = curc;
direct ( v.first, u, 0, ans[v.second] ^ 1 );
}
}
inline vecint main () {
ans.resize ( m );
direct ( 0, -1, 0, 0 );
return ans;
}
} // namespace RybyA::Task2.
} // namespace RybyA.
vecint Mark ( const int N, const int M, const int A, const int B, vecint U, vecint V ) {
RybyA::n = N, RybyA::m = M;
for ( int i = 0; i < M; ++ i ) {
RybyA::graph[U[i]].push_back ( { V[i], i } );
RybyA::graph[V[i]].push_back ( { U[i], i } );
}
if ( A > 2 ) return RybyA::Task1::main ( U, V );
return RybyA::Task2::main ();
}
// main () {}
// Catherine.cpp
#include <vector>
#include <assert.h>
#include "Catherine.h"
#ifndef vecint
#define vecint std::vector<int>
#endif
#ifndef pii
#define pii std::pair<int, int>
#endif
namespace RybyC {
bool type;
int last;
namespace Task1 {
inline int main ( vecint Y ) {
int cnt = 0;
if ( ~ last ) ++ Y[last];
for ( int y: Y ) cnt += !! y;
if ( cnt == 1 ) last = Y[2] ? 2 : !! Y[1];
else {
for ( int i = 0; i < 3; ++ i ) {
if ( ! Y[i] ) {
last = ( i + 1 ) % 3;
break;
}
}
}
return last;
}
} // namespace RybyC::Task1.
namespace Task2 {
const int down[6][5] {
{ 0, 0, 1, 1, 0 },
{ 0, 1, 1, 0, 1 },
{ 1, 1, 0, 1, 0 },
{ 1, 0, 1, 0, 0 },
{ 0, 1, 0, 0, 1 },
{ 1, 0, 0, 1, 1 }
};
vecint chain;
bool directed;
inline int main ( vecint Y ) {
if ( ! ~ last ) {
if ( Y[0] + Y[1] == 2 ) {
if ( ! Y[1] ) {
chain.push_back ( 0 ), chain.push_back ( 0 );
return 0;
} else if ( ! Y[0] ) {
chain.push_back ( 1 ), chain.push_back ( 1 );
return 1;
} else {
chain.push_back ( 1 ), chain.push_back ( 0 );
return 0;
}
}
if ( ! Y[0] ) return directed = true, 1;
if ( ! Y[1] ) return directed = true, 0;
return directed = true, Y[1] == 1;
}
if ( ! Y[0] && ! Y[1] ) return directed = true, -1;
if ( ! directed ) {
if ( Y[0] + Y[1] > 1 ) {
directed = true;
if ( ! Y[0] || ! Y[1] ) return -1;
return ! last;
}
chain.push_back ( Y[1] );
if ( chain.size () == 5 ) {
bool downing = false; directed = true;
for ( int i = 0; ! downing && i < 6; ++ i ) {
int j = 0;
for ( ; j < 5; ++ j ) if ( down[i][j] ^ chain[j] ) break;
if ( j == 5 ) downing = true;
}
if ( downing ) return -1;
}
return chain.back ();
} else {
if ( ! Y[0] ) return 1;
if ( ! Y[1] ) return 0;
++ Y[last];
return Y[1] == 1;
}
}
} // namespace RybyC::Task2.
} // namespace RybyC.
void Init ( int A, int B ) { RybyC::last = -1, RybyC::type = A > 2; }
int Move ( vecint Y ) {
if ( RybyC::type ) return RybyC::Task1::main ( Y );
else {
int t = RybyC::Task2::main ( Y );
if ( ~ t ) RybyC::last = t;
return t;
}
}
// main () {}