1. 程式人生 > >[APIO2008]免費道路

[APIO2008]免費道路

\n cmp != 限制 端點 答案 truct api 很多

# [APIO2008]免費道路

### 第一反應

考慮樸素的克魯斯卡爾算法加一個限制,先選鵝卵石路,且選到k個就停止

帶來的問題:

- ~~葉子節點特殊處理,都選上~~(但其實是連通性)

- ~~而且你詭異的發現,tm,這個鵝卵石路可以突破最小生成樹!!!~~(不仔細看題面的後果)


### 正解

考慮上文中的連通性,先用水泥路跑一遍$Kruskal$,然後不連通的且用到鵝卵石路的都要選上.剩下的既然水泥路可以,那麽鵝卵石路也可以代替嘛,先選鵝卵石路,選到$k$個就停止

emm,那麽什麽時候是無解呢,有這麽幾個情況

- **關鍵鵝卵石邊**太多啦,超過$k$個
- **能選鵝卵石邊**太少了,少於$k$個

- 此圖不連通

### 代碼

```cpp
// luogu-judger-enable-o2
#include <iostream>
#include <cstdio>
#include <algorithm>

using std::sort;

const int M = 100005;
const int N = 20005;

int n, m, k, a_cnt;
int fa[N];

struct Aha{
int s, t, val, flag;
}l[M], ans[M];//s,t兩個端點, val路性質, flag,聯通

inline bool cmp(Aha a, Aha b){

return a.val < b.val;
}

int find(int x){
return x == fa[x] ? x : fa[x] = find(fa[x]);
}//並茶幾沒有路徑壓縮的傻逼就是lmsh7

inline int read(){
int x = 0, f = 1;
char c = getchar();
while(c < ‘0‘ || c > ‘9‘){
if(c == ‘-‘)
f = -1;
c = getchar();
}
while(c >= ‘0‘ && c <= ‘9‘){

x = (x << 3) + (x << 1) + (c ^ 48);
c = getchar();
}
return x * f;
}

int main(){
n = read(), m = read(), k = read();
for(int i = 1; i <= n; ++i){
fa[i] = i;
}

for(int i = 1; i <= m; ++i){
l[i].s = read(), l[i].t = read(), l[i].val = read();
if(l[i].val){
fa[l[i].s] = find(l[i].s);
fa[l[i].t] = find(l[i].t);

if(fa[l[i].t] != fa[l[i].s]){
fa[fa[l[i].t]] = fa[l[i].s];
}
}
}

for(int i = 1; i <= m; ++i){
if(!l[i].val && find(l[i].t) != find(l[i].s)){
fa[fa[l[i].t]] = fa[l[i].s];
ans[++a_cnt].s = l[i].s;
ans[a_cnt].t = l[i].t;
ans[a_cnt].val = l[i].val;//加入答案
l[i].flag = 1;
k--;
}
}

if(k < 0){
printf("no solution\n");//必須要加的邊超過了k
return 0;
}

fa[1] = find(1);

for(int i = 2; i <= n; ++i){
fa[i] = find(i);
if(fa[i] != fa[i - 1]){//不聯通
printf("no solution\n");
return 0;
}
}

for(int i = 1; i <= n; ++i){
fa[i] = i;
}

for(int i = 1; i <= m; ++i){
if(l[i].flag)
fa[fa[l[i].s]] = fa[l[i].t];
}

sort(l + 1, l + 1 + m, cmp);
for(int i = 1; i <= m; ++i){
if((!k && !l[i].val) || l[i].flag) continue;//是割橋或者鵝卵石路且k用完

fa[l[i].s] = find(l[i].s);
fa[l[i].t] = find(l[i].t);
if(fa[l[i].t] != fa[l[i].s]){
fa[fa[l[i].t]] = fa[l[i].s];
ans[++a_cnt].s = l[i].s;
ans[a_cnt].t = l[i].t;
ans[a_cnt].val = l[i].val;
if(!l[i].val) k--; //錯誤:把這個放在了外面
}
}

if(k){
printf("no solution\n");
return 0;
}

for(int i = 1; i <= a_cnt; ++i){
printf("%d %d %d\n", ans[i].s, ans[i].t, ans[i].val);
}

return 0;
}
```

~~雖然又臭又長,但完全是自己敲出來的啊~~

### 錯誤

我都犯了什麽錯呢,這才是重點啊.在luogu交了7次在bzoj交了37次,才完全明白過來很多地方為什麽不可以.

1. 不可饒恕的錯誤,**並查集忘記路徑壓縮**
2. 第二遍最小生成樹的時候把減小$k$的操作放在了外面
3. **並查集操作不熟練**,導致了判斷圖的連通性操作失誤

##### 錯誤代碼:

```cpp
for(int i = 2; i <= n; ++i){
fa[i] = find(i);
if(fa[i] != fa[i - 1]){
printf("no solution\n");
return 0;
}
}
```

4. bzoj為啥要求"no solution"帶換行啊,以後還是長個心眼
5. 代碼寫的醜,調碼難炸天

[APIO2008]免費道路