题意
- 【Go Make It Complete | Gym 102001G】
给定一个 n n n 个点 m m m 条边的图。
每次选择两个没有边直接相邻的点,把 d e g [ i ] + d e g [ j ] deg[i]+deg[j] deg[i]+deg[j] 即两个节点的度的值放入集合 S S S 中,然后连接这两个点。
直到这个图变成一个完全图。
求最大化集合中的最小值。 -
2
≤
n
≤
500
2\le n\le 500
2≤n≤500
0 ≤ m < n ( n − 1 ) 2 0\le m<\frac{n(n-1)}{2} 0≤m<2n(n−1)
思路
- 考虑贪心,每次拉出来最大的
d
e
g
[
i
]
+
d
e
g
[
j
]
deg[i]+deg[j]
deg[i]+deg[j] 的两个点
(
i
,
j
)
(i,j)
(i,j),然后连边。
然后更新, d e g [ i ] + + , d e g [ j ] + + deg[i]++,deg[j]++ deg[i]++,deg[j]++
然后加新的点对,放进优先队列跑。
但如果一开始是一个空图,每次优先队列里都要放 O ( n ) O(n) O(n) 个点对,光内存就 O ( n 3 ) O(n^3) O(n3) 了,时间复杂度大约 O ( n 3 log n ) O(n^3\log n) O(n3logn) - 考虑优化。我们记一个 i n [ x ] [ y ] in[x][y] in[x][y] 表示在优先队列中,我们目前的最优 d e g [ x ] + d e g [ y ] deg[x]+deg[y] deg[x]+deg[y] 的值。如果更新到某个时间,发现 d e g [ x ] + d e g [ y ] > i n [ x ] [ y ] deg[x]+deg[y]>in[x][y] deg[x]+deg[y]>in[x][y],我们再让点对 ( x , y ) (x,y) (x,y) 进优先队列。但是复杂度对于空图来说没有本质变化,对于满图的复杂度倒是优化了不少。
- 考虑优化。我们记
a
n
s
ans
ans 为目前的
min
{
d
e
g
[
i
]
+
d
e
g
[
j
]
}
\min\{deg[i]+deg[j]\}
min{deg[i]+deg[j]},容易发现如果
d
e
g
[
x
]
+
d
e
g
[
y
]
≥
a
n
s
deg[x]+deg[y]\ge ans
deg[x]+deg[y]≥ans,我们就直接连边吧,没必要放进优先队列里去了。我们直接连边,然后增加他们的度,就不放到优先队列里去了。但是只这样处理是错误的。 因为我们增加了
y
y
y 的度,相应有可能某个
z
z
z 满足
d
e
g
[
y
]
+
d
e
g
[
z
]
<
a
n
s
deg[y]+deg[z]<ans
deg[y]+deg[z]<ans,但是
d
e
g
[
y
]
+
d
e
g
[
z
]
>
deg[y]+deg[z]>
deg[y]+deg[z]> 优先队列中的其他值。
所以我们需要一个队列,存储这一趟我们直接连边的那些点的下标。如果队列非空,我们继续找是否有 z z z 满足可以直接连边,或者更优与 i n [ z ] [ n o w ] in[z][now] in[z][now] 然后放入优先队列中。 - 可以感性地想,如果图较满,我们进队列的次数不会很多,大多数都是优先队列的复杂度。
如果图较空,则 a n s ans ans 较小,大多数点对之前都能满足度和大于等于 a n s ans ans,大多数都是队列的复杂度,没有 l o g log log。
代码
- 综合复杂度:
O
(
n
2
l
o
g
+
n
3
)
O(n^2log+n^3)
O(n2log+n3)
T i m e s : 78 / 1000 ( M s ) Times:78/1000(Ms) Times:78/1000(Ms)
M e m o : 5.6 ( M b ) Memo:5.6(Mb) Memo:5.6(Mb)
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x << " ] , ";show(args...);}
const int MAX = 505;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-7;
int deg[MAX];
bool mp[MAX][MAX];
int in[MAX][MAX];
bool vis[MAX];
int main()
{
int n,m;cin >> n >> m;
for(int i = 1;i <= m;++i){
int ta,tb;cin >> ta >> tb;
mp[ta][tb] = mp[tb][ta] = 1;
deg[ta]++;
deg[tb]++;
}
priority_queue<pair<int,int> >Q;
for(int i = 1;i <= n;++i)
for(int j = i + 1;j <= n;++j)
if(!mp[i][j]){
in[i][j] = in[j][i] = deg[i] + deg[j];
Q.push(make_pair(deg[i]+deg[j],i*1000+j));
}
int ans = INF;
while(!Q.empty()){
int d = Q.top().first;
int x = Q.top().second / 1000;
int y = Q.top().second % 1000;
Q.pop();
if(mp[x][y])continue;
ans = min(ans,deg[x] + deg[y]);
mp[x][y] = mp[y][x] = 1;
deg[x]++;deg[y]++;
queue<int>QQ;QQ.push(x);QQ.push(y);vis[x] = vis[y] = 1;
while(!QQ.empty()){
int now = QQ.front();QQ.pop();
vis[now] = 0;
for(int i = 1;i <= n;++i){
if(now!=i && !mp[now][i] && deg[now] + deg[i] > in[now][i]){
if(deg[now] + deg[i] >= ans){
mp[now][i] = mp[i][now] = 1;
deg[now]++;deg[i]++;
if(!vis[now]){QQ.push(now),vis[now] = 1;} // 它不在队列中,我就加到队列中
if(!vis[i]){QQ.push(i),vis[i] = 1;}
}else{
in[now][i] = in[i][now] = deg[now] + deg[i]; // 更优与优先队列中的,放到优先队列中
Q.push(make_pair(in[now][i],i * 1000 + now));
}
}
}
}
}
cout << ans;
return 0;
}