[数据丢失]剑客的治疗守卫
壹、题目描述 ¶
有一个 \(n\times n\) 的网格图,初始有 \(m\) 个点存在标记,而你也可以通过以下规则给某些点打上标记:
- 如果 \((a,b)\) 有标记,\((b,c)\) 有标记,那么 \((c,a)\) 也可以打上标记;
在经过尽可能多的操作之后,该图中最多可以存在多少个点有标记。
数据范围满足 \(1\le n,m\le 10^5\).
贰、关于题解 ¶
不妨先将一个点 \((a,b)\) 看成是从 \(a\to b\) 的有向边,那么,原题就转化为:
若 \(a\to b,b\to c\),那么可以添加一条边 \(c\to a\).
现在我们的目的是求出这个图中最后会有多少条边。
不难发现,每个连通块是独立的,于是对于每个连通块分开考虑。先从每个连通块中随便抓一个点出来,将这个点等级设为 \(0\),然后依次对于每个点在 \(\%3\) 意义下染色。然后我们可以分析这个图的性质。
断言,若存在一个自环,那么该连通块可以整体变成完全图。
而这个自环是怎么形成的呢?我们可以看到以下几种情况都可以造一个自环出来:
实际上两种方法本质是一样的,都是存在右边的那种东西,然后出现自环,然后整个图变成完全图...
如果将该两种方法 \(\%3\) 编号,你会发现这样两种情况分别是:
- 左图,同一层的点之间存在连边(或者你看成一条边跨越了两层);
- 右图,某一层存在点向上一层连边;
综合来说,如果一个连通块无法被 完美地 三染色,那么该部分最后一定会变成一个完全图。
剩下的两个问题之一 —— 如果一个连通块可以被完美三染色?不妨先画个图:
不难发现,邻层的点都可以连满(指从 \(0\to 1,1\to 2,2\to 0\))至于这是为什么?不妨先假设我们从一个 \(0\) 层点开始染色,那么,该 \(0\) 点可以走到所有 \(2\) 层点,因此所有 \(2\) 层点会向 \(0\) 层这个点连边,此时,\(0\to 1,2\to 0\) 都连满了,我们再证明 \(1\to 2\) 可以连满即可,事实上很简单,我们从 \(2\) 层某个点开始走到 \(0\),而 \(0\) 可以走到任意一个 \(1\) 层点,因此该 \(2\) 层点可以被所有 \(1\) 层点连接到,因此所有 \(1\) 层点都可以连接到 \(2\) 层点.
最后一个问题 —— 如果这个图不足三层怎么办?事实上,你什么也做不了。
然后这个题就做完了,一个 \(\rm dfs\) 统计每一连通块的情况即可,时间复杂度 \(\mathcal O(n)\),数据范围只是拿来唬人的。
叁、参考代码 ¶
#include <bits/stdc++.h>
using namespace std;
// #define USING_STDIN
// #define NDEBUG
// #define NCHECK
#include <cassert>
namespace Elaina {
#define rep(i, l, r) for(int i = (l), i##_end_ = (r); i <= i##_end_; ++i)
#define drep(i, l, r) for(int i = (l), i##_end_ = (r); i >= i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define whole(v) ((v).begin()), ((v).end())
#define bitcnt(s) (__builtin_popcount(s))
#define y0 FUCK_UP
#define y1 MOTHER_FUCKER
#ifdef NCHECK
# define iputs(Content) ((void)0)
# define iprintf(Content, argvs...) ((void)0)
#else
# define iputs(Content) fprintf(stderr, Content)
# define iprintf(Content, argvs...) fprintf(stderr, Content, argvs)
#endif
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
template<class T> inline T fab(T x) { return x < 0 ? -x : x; }
template<class T> inline void getmin(T& x, const T rhs) { x = min(x, rhs); }
template<class T> inline void getmax(T& x, const T rhs) { x = max(x, rhs); }
#ifndef USING_STDIN
inline char qkgetc() {
# define BUFFERSIZE 1 << 17
static char BUF[BUFFERSIZE], *p1 = BUF, *p2 = BUF;
return p1 == p2 && (p2 = (p1 = BUF) + fread(BUF, 1, BUFFERSIZE, stdin), p1 == p2) ? EOF : *p1++;
# undef BUFFERSIZE
}
# define CHARRECEI qkgetc()
#else
# define CHARRECEI getchar()
#endif
template<class T> inline T readret(T x) {
x=0; int f = 0; char c;
while((c = CHARRECEI) < '0' || '9' < c) if(c == '-') f = 1;
for(x = (c ^ 48); '0' <= (c = CHARRECEI) && c <= '9'; x = (x << 1) + (x << 3) + (c ^ 48));
return f ? -x : x;
}
template<class T> inline void readin(T& x) {
x=0; int f = 0; char c;
while((c = CHARRECEI) < '0' || '9' < c) if(c == '-') f = 1;
for(x = (c ^ 48); '0' <= (c = CHARRECEI) && c <= '9'; x = (x << 1) + (x << 3) + (c ^ 48));
if(f) x = -x;
}
template<class T, class... Args> inline void readin(T& x, Args&... args) {
readin(x), readin(args...);
}
// default enter
template<class T> inline void writln(T x, char s = '\n') {
static int fwri_sta[55], fwri_ed = 0;
if(x < 0) putchar('-'), x = -x;
do fwri_sta[++fwri_ed] = x % 10, x /= 10; while(x);
while(putchar(fwri_sta[fwri_ed--] ^ 48), fwri_ed);
putchar(s);
}
} using namespace Elaina;
const int maxn = 1e5;
int n, m;
struct edge { int to, nxt; } e[maxn * 2 + 5];
int tail[maxn + 5], ecnt;
inline void add_edge(int u, int v) {
e[ecnt] = edge{v, tail[u]}; tail[u] = ecnt++;
e[ecnt] = edge{u, tail[v]}; tail[v] = ecnt++;
}
inline void input() {
readin(n, m);
int u, v;
memset(tail + 1, -1, n << 2);
rep(i, 1, m) {
readin(u, v);
add_edge(u, v);
}
}
bool vis[maxn + 5];
int lev[maxn + 5];
int cnt[3];
ll cnt_posiEdge, cnt_node;
bool isWhole;
void dfs(int u) {
vis[u] = true, ++cnt_node;
++cnt[lev[u]];
for(int i = tail[u], v; ~i; i = e[i].nxt) {
if(!(i & 1)) ++cnt_posiEdge; // the counter of positive edge
if(!vis[v = e[i].to]) {
if(i & 1) lev[v] = (lev[u] + 2) % 3;
else lev[v] = (lev[u] + 1) % 3;
dfs(v);
}
else {
if(i & 1) {
if(lev[v] != (lev[u] + 2) % 3)
isWhole = true;
}
else if(lev[v] != (lev[u] + 1) % 3)
isWhole = true;
}
}
}
signed main() {
input();
ll ans = 0;
rep(i, 1, n) if(!vis[i]) {
cnt_posiEdge = cnt_node = 0, isWhole = false;
memset(cnt, 0, sizeof cnt);
dfs(i);
if(isWhole) ans += 1ll * cnt_node * cnt_node;
else if(cnt[0] && cnt[1] && cnt[2]) {
rep(j, 0, 2) ans += 1ll * cnt[j] * cnt[(j + 1) % 3];
}
else ans += cnt_posiEdge;
}
writln(ans);
return 0;
}