Atcoder abc308

https://atcoder.jp/contests/abc308/tasks

Ex - Make Q

简单无向图, N点,M边, 初始边白色, 如果要把边染色成黑色代价Ci

也就是 要 给一个环上色,且延伸一个 环内点-环外点的边,也就是视觉意义上的Q

问 画出Q的最小代价

n300

ci 1e5

4s

1024mb

我的思路

这个n300 不知道怎么用,看起来像是n^3 一类的玩意

如果给定了圆,那么延伸边,可以直接暴力枚举O(n^2)?

一个思路是最小生成树,而和最小生成树不同的是,当有同并查集的时候 直接触发 找环

问题是,每边尽量短并不意味环更短,10个1 > 3个2


钦定 环上延伸边的点

环上延伸边的点是u, 然后找环,再定延伸边

for u (O(n)): 找环?, 延伸边:O(n)

如果先定延伸边,那么就是 求不含延伸点,含固定点的最小环

似乎并不会找 含 固定点的最小环

另外,如果再处理一下, 先找含固定点的最小环,那么未包含的点就可以作为延伸点,包含的点再考虑

1
2
3
4
5
6
1-(1)-2-(1)-3
1-(1)-4-(1)-3
1-(3)-3
1-(100)-5
显然 最小环是 1-2-3-4-1, 长度是4,但是延伸点会是1-5长度100
而 1-2-3-1 环更大是5,可以选延伸边1-4长度1

题解

1
2
3
b-a-c (环上)
|
d (环外)

这样的话 for a

对于给定a

yukicokder No.1320, 找包含点a的环的时间复杂度是O(N^2)

方法:

首先以a为根建立 最短距离树

L[u] = u向上的祖先除了a深度最低的节点, L[a] = a

对于所有边 L[e[i].u] != L[e[i].v], 相当于有环 a-L[e[i].u]-...-e[i].u-e[i].v-...-L[e[i].v]-a

// 我也这样想了,没想到总的只用建立1个树,而是想成从a每条边延伸建1个树,


而这里还出了一个性质

如果 d不是b,c也是环上点那么 该方案一定不会产生贡献

1
2
3
4
5
6
7
b-a-c
|
d 如果在环上

当前贡献 = 含a最小环 + (a-d)

而 环 a-b-..-d-a + (a-c) < 这种方案

因此 ans = min(图形Q) = min(图形环+一条边环上点的额外延伸边(没有非环上点的性质)) !!!!!!!!

所以这样的话,只要不是b和c就可以了!!

这样的话,那么只需要再考虑 额外边是a-b,和额外边是a-c的两种情况!!!!

代码

https://atcoder.jp/contests/abc308/submissions/43439486

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
#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for (int i=a;i<(int)(n);i++)
int read(){int r;scanf("%d",&r);return r;}
const int inf = 1e8; // 300 * 1e5 = 3e7
pair<int, vector<int>> shortest_cycle(const vector<vector<int>> &G, int r) {
int n = G.size();
vector<int> dist(n, 2 * inf), p(n, -1)/*父节点*/, g(n); // g[u] = u 的祖先中 r向下一个点, g[r]=r
vector<bool> seen(n);
dist[r] = 0;
g[r] = r;
rep(_,0,n){ // bfs, 建立树
int mn = 2*inf, pos = -1;
rep(i,0,n) if (!seen[i] and dist[i] < mn) { // 可能不连通!
mn = dist[i];
pos = i;
}
assert(pos != -1);
seen[pos] = true;
rep(i,0,n) if (dist[i] > dist[pos] + G[pos][i]) {
dist[i] = dist[pos] + G[pos][i];
p[i] = pos;
g[i] = (pos == r ? i : g[pos]);
}
}
array<int,3> mnij = {5*inf,-1,-1}; // 可能和1不连通的点
rep(i,0,n) rep(j,i+1,n){
if (p[i] == j or p[j] == i) continue; // 树边
if (g[i] == g[j]) continue; // 同源
mnij = min(mnij, {dist[i] + dist[j] + G[i][j],i,j}); // r-g[i]-...-i-j-...-g[j]-r
}
vector<int> res;
auto [len,a,b] = mnij;
for(;a != r;a=p[a]) res.push_back(a);
res.push_back(a);
reverse(res.begin(), res.end());
for(;b != r;b=p[b]) res.push_back(b);
return {len, res};
}

int main() {
int n=read();
int m=read();
vector G(n, vector<int>(n, inf));
rep(i,0,m){
int a=read()-1;
int b=read()-1;
int c=read();
G[a][b] = G[b][a] = c;
}
int ans = inf;
rep(a,0,n) {
auto [len, cycle] = shortest_cycle(G, a);
assert(int(cycle.size()) >= 3 and cycle[0] == a);
vector<int> adj = {cycle[1], cycle.back()};
// ans = min(Q) = min(O + -)
rep(i,0,n) if(!(i == a or i == adj[0] or i == adj[1])) ans = min(ans, len + G[a][i]);
for(auto d:adj){
int ori = G[a][d];
G[a][d] = G[d][a] = inf;
ans = min(ans, shortest_cycle(G, a).first + ori);
G[a][d] = G[d][a] = ori;
}
}
printf("%d\n",ans == inf ? -1 : ans);
return 0;
}

总结

Ex

学习了,如何在给定图中找包含点A的最小环

虽然想到了固定 特殊点,但是没相出这个 额外引入不是Q但是不影响结果的性质

参考

官方题解