TIOJ-2038

金氏運動公司打算舉辦一場馬拉松比賽,為了締造亮眼的完成比賽時間,金氏運動公司打算選擇性地邀請選手參賽。分析過往的數據資料,金氏運動公司觀察到以下二個現象:
(a) 對於任何一位選手,如果愈多他的朋友參賽,則他就能跑得愈快,所以傾向於找一群選手使得彼此互相認識的情況很多。因為認識是雙向的,如果$P$認識$Q$,則$Q$認識$P$。所以當我們說$P$認識$Q$時,等同於表示$P$、$Q$兩位互相認識。

(b) 如果參賽的選手當中,存在兩位選手$P$和$Q$彼此不認識,而且在參賽的選手當中無法找到$t$位選手$S_1,S_2,…S_t$($t$為任意大於0的整數),使得$P$認識$S_1$,$S_2$認識$S_3$,…,$S_{t-1}$認識$S_t$,$S_t$認識$Q$,則比賽將會有嚴重的惡性競爭,所以需要避免這樣的狀況。

現在金氏運動公司手上有一份$N$位選手的名單以及一份顯示這$N$位選手彼此之間是否認識的表單,現在的任務是從這$N$位選手找出選手的子集合$S = {P_1, P_2, \ldots, P_{\lvert S \rvert} }$,使得$S$沒有惡性競爭的狀況,而且讓以下影響因子$F(S)$得到最大化,這影響因子的設計除了讓每位選手都認識夠多的參賽者,也兼顧了不讓參賽人數過少。

$$F(S)=|S|\min_{1\le i\le |S|}D_i$$

其中$D_i$表示選手$P_i$所認識的人當中,有多少人在子集合$S$裡面。在以下這個4位選手的例子中,選$S = {2, 3, 4}$比其他的選法有更高的$F(S)$。

Select way

$N\le 5000, M\le \frac{N\times (N-1)}{2}$


這題簡單來說就是選取一個連通子集,且她們的最小度數*子集大小最大。
考慮枚舉最小的度數$D$由小到大,不斷將度數小於最小度數的點都拔掉,同時檢查她周遭的點是否會因為這個點被拔而跟著被拔。可以用bfs配合求拓樸排序那種手法處理,然而在找連通塊大小時得做一次dfs,總複雜度會退化成$O(N(N+M))$(當然,可以直接砸可反悔的dsu)

既然沒辦法好好維護連通塊大小,那就改成枚舉大到小,不斷把度數大於等於最小度數的都加進圖,直接用dsu維護最大連通大小,不過會遇到一個問題:該怎麼判斷加點的順序。
這就是這題有趣之處:事實上,直接將拔點順序反著插入會是好的。
詳細證明我也不會,大概就是拔點都是拔整個會影響的連通塊,插入也自然是插入整個連通塊。
所以就把拔點順序加進一個stack就好了
複雜度$O(N^2+M)$

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
/*
-------------- | /
| | /
| | /
| * |/ | | ------ *
| | | | / \
| | |\ | | | |\ |
\ | | | \ | | | | \ |
\ | | | \ | | \ / \ |
V | | \ \__/| ----- \ |
*/
#include <bits/stdc++.h>
using namespace std;

#define EmiliaMyWife ios::sync_with_stdio(0); cin.tie(NULL);
/*-----------------------------------------------------------------------------------------------------*/

const int N = 1e4;
int pa[N], sz[N];
int fnd(int x) { return pa[x] == x ? pa[x] : pa[x] = fnd(pa[x]); }
void uni(int a, int b) {
if((a = fnd(a)) == (b = fnd(b)))
return;
if(sz[a] > sz[b])
swap(a, b);
sz[b] += sz[a];
pa[a] = b;
}

signed main() {
EmiliaMyWife

int n, m, ans = 0;
cin >> n >> m;
for(int i = 1; i <= n; i++)
pa[i] = i, sz[i] = 1;

vector<vector<int>> edge(n + 1);
vector<int> cnt(n + 1), rcnt(n + 1), has(n + 1);
for(int i = 0, a, b; i < m; i++)
cin >> a >> b, cnt[a]++, cnt[b]++, edge[a].push_back(b), edge[b].push_back(a);

stack<int> owo;
for(int i = 0; i <= n; i++) {
queue<int> cc;
for(int j = 1; j <= n; j++) {
if(has[j])
continue;
if(cnt[j] <= i) {
cc.push(j);
has[j] = 1;
}
}
while(!cc.empty()) {
int v = cc.front();
owo.push(v);
cc.pop();
for(int u : edge[v]) {
if(has[u])
continue;
cnt[u]--;
if(cnt[u] <= i) {
cc.push(u);
has[u] = 1;
}
}
}
}
while(!owo.empty()) {
int v = owo.top();
owo.pop();
has[v] = 0;
for(int u : edge[v]) {
if(has[u])
continue;
uni(u, v);
rcnt[v]++;
}
ans = max(ans, rcnt[v] * sz[fnd(v)]);
}
cout << ans << '\n';

return 0;
}