一般图的点的三元问题转化为二分图最大独立集:ABC461G

本文搬运自本人高中时期CSDN博客,若图片加载不出来,可到原文查看:https://blog.csdn.net/zhangtingxiqwq/article/details/162103270

https://atcoder.jp/contests/abc461/tasks/abc461_g

一种错误做法

我刚开始的做法:

首先每个点肯定是0、1013、2026,即0、1、2的。

考虑到每个点双内,它的最大值必然不会超过所有点选1。

于是建立圆方树,然后树上dp。

一个点若为2,则需同一点双内所有点均为0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include<bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stdout, ##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
#define Z(x) (x)*(x)
#define pb push_back
#define fi first
#define se second
//#define M
//#define mo
#define N 200010
int n, m, i, j, k, T;
int dp[N][3], dfn[N], low[N], vis[N];
int u, v, ans;
int tot, num;
stack<int>z;
vector<int>G[N];

namespace Tree {
vector<int>G[N];
void cun(int x, int y) {
G[x].pb(y); G[y].pb(x);
}
void dfs(int x, int fa, int op) {
vis[x] = 1;
for(int y : G[x]) {
if(y == fa) continue ;
dfs(y, x, op ^ 1);
if(op == 0) {
dp[x][0] += max({dp[y][0], dp[y][1], dp[y][2]});
dp[x][1] += max(dp[y][0], dp[y][1]);
dp[x][2] += dp[y][0];
}
if(op == 1) {
dp[x][2] = max(dp[x][2] + dp[y][0], dp[x][0] + dp[y][2]);
dp[x][0] += dp[y][0];
dp[x][1] += max(dp[y][0], dp[y][1]);
}
}
if(op == 0) dp[x][1]++, dp[x][2] += 2;
}
}

void dfs(int x) {
dfn[x] = low[x] = ++tot; z.push(x);
for(int y : G[x]) {
if(!dfn[y]) {
dfs(y);
low[x] = min(low[x], low[y]);
if(low[y] == dfn[x]) {
Tree :: cun(x, ++num);
while(z.top() != y) {
Tree :: cun(z.top(), num);
z.pop();
}
Tree :: cun(y, num); z.pop();
}
}
else low[x] = min(low[x], dfn[y]);
}
}

signed main()
{
#ifdef LOCAL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
// srand(time(NULL));
// T = read();
// while(T--) {
//
// }
n = num = read(); m = read();
for(i = 1; i <= m; ++i) {
u = read(); v = read();
G[u].pb(v); G[v].pb(u);
}
for(i = 1; i <= n; ++i)
if(!dfn[i]) dfs(i);
for(i = 1; i <= n; ++i)
if(!vis[i]) {
Tree :: dfs(i, 0, 0);
ans += max({dp[i][0], dp[i][1], dp[i][2]});
}
printf("%lld", ans * 1013);
return 0;
}


但这这样子有什么问题呢?请看反例:

在这里插入图片描述

如果用我的做法,构造出来的是这样子的:

在这里插入图片描述

但实际上,这样子更优:

在这里插入图片描述

于是我的做法就不行了。


正确做法

我们考虑构造二分图,

如果原图 u,vu,v 间有连边,我们就在二分图上,连接 aubva_u、b_vavbua_v、b_u

然后我们求这个二分图的最大独立集即可。


根据结论:

二分图最大独立集 = 总点数 - 最大匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
n = read(); m = read(); 
S = ++tot; T = ++tot;
for(i = 1; i <= n; ++i) a[i] = ++tot;
for(i = 1; i <= n; ++i) b[i] = ++tot;
mf_graph<int> G(tot + 5);
for(i = 1; i <= n; ++i) G.add_edge(S, a[i], 1);
for(i = 1; i <= n; ++i) G.add_edge(b[i], T, 1);
for(i = 1; i <= m; ++i) {
u = read(); v = read();
G.add_edge(a[u], b[v], 1);
G.add_edge(a[v], b[u], 1);
}
k = G.flow(S, T);
printf("%lld", (2 * n - k) * 1013);