CodeForces 1149D Abandoning Roads
題目大意
給定一張無向圖,邊權只有兩種情況,要求求出在這張圖上的所有最小生成樹中,從到的路徑的最小值。
分析
結論(1): 邊權爲的邊必須被選做生成樹上的邊。
證明(1): 根據 Kruskal 算法求解生成樹的過程顯然可得。
這樣一來整張圖就被分成了許多個連通塊。問題就變爲用邊權爲的邊連接這些散着的連通塊,使得到的路徑長度最小。
結論(2): 若兩個點之間的路徑中有一條只有邊,那麼兩個點在生成樹上的路徑上不可能有邊。
證明(2): 由生成樹的性質可得顯然正確。
結論(3): 一條路徑可能在最小生成樹上當且僅當這條路徑沒有離開當前連通塊再回來。注意若一個連通塊中有邊也不能夠走。
這樣一來就可以用狀壓 DP 來做了。
考慮狀壓 DP ,設狀態爲當前在點,已經經過了中的所有連通塊的路徑長度最小值。
由於和會互相影響,所以我們用最短路來轉移。
時間複雜度爲,但由於,這樣做顯然時間和空間都要炸裂。
考慮優化:
結論(4): 連通塊中點數小於等於的可以不壓進狀態中。
證明(4): 由於離開一個連通塊再回來必須經過至少兩條邊權爲的邊(除非是直接用邊連起來的)。而當連通塊的點數小於等於時連通塊中任意兩個點都可以通過不超過兩條邊互相到達。顯然對於這樣的一個連通塊,我們走邊比走邊更好。
這樣一來我們需要壓進狀態中的連通塊個數就不會超過個了。於是複雜度變成大約。
參考代碼
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int Maxn = 70;
const int Maxm = 200;
struct DSU {
int fa[Maxn + 5];
void init(int n) {
for(int i = 1; i <= n; i++)
fa[i] = i;
}
int find(int u) {
return u == fa[u] ? u : fa[u] = find(fa[u]);
}
bool unite(int u, int v) {
u = find(u), v = find(v);
if(u == v) return false;
fa[u] = v;
return true;
}
};
struct Edges {
int u, v, w;
bool operator < (const Edges &rhs) const {return w < rhs.w;}
};
struct QNode {
int sta, u;
int dis;
QNode(){}
QNode(int _sta, int _u, int _dis) {sta = _sta, u = _u, dis = _dis;}
bool operator < (const QNode &rhs) const {
return dis > rhs.dis;
}
};
struct Edge {
int to, dis;
Edge *nxt;
};
Edge pool[Maxm * 2 + 5];
Edge *G[Maxn + 5], *ecnt = &pool[0];
void addedge(int u, int v, int w) {
Edge *p = ++ecnt;
p->to = v, p->dis = w;
p->nxt = G[u], G[u] = p;
}
int N, M, A, B;
Edges e[Maxm + 5];
DSU st;
void Kruskal() {
st.init(N);
sort(e + 1, e + M + 1);
for(int i = 1; i <= M; i++) {
int u = e[i].u, v = e[i].v;
if(e[i].w == B || !st.unite(u, v)) continue;
}
}
int siz[Maxn + 5];
int id[Maxn + 5], nid[Maxn + 5];
int ans[Maxn + 5];
int dis[Maxn + 5][(1 << 18) + 5];
bool vis[Maxn + 5][(1 << 18) + 5];
void Dijkstra() {
priority_queue<QNode> q;
memset(dis, 0x3f, sizeof dis);
memset(vis, false, sizeof vis);
q.push(QNode(0, 1, 0)), dis[1][0] = 0;
while(!q.empty()) {
QNode tmp = q.top();
q.pop(), ans[tmp.u] = min(ans[tmp.u], tmp.dis);
if(vis[tmp.u][tmp.sta]) continue;
int u = tmp.u, s = tmp.sta;
vis[u][s] = true;
for(Edge *p = G[u]; p != NULL; p = p->nxt) {
int v = p->to;
if(id[v] == id[u] && p->dis == B) continue;
//不能在連通塊內部走邊權爲 B 的邊
if(nid[v] && (s & (1 << (nid[v] - 1)))) continue;
//不能經過重複的連通塊
int t = s;
if(nid[u] && nid[v] != nid[u]) t |= (1 << (nid[u] - 1));
if(dis[v][t] > dis[u][s] + p->dis) {
dis[v][t] = dis[u][s] + p->dis;
q.push(QNode(t, v, dis[v][t]));
}
}
}
}
int main() {
#ifdef LOACL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
scanf("%d %d %d %d", &N, &M, &A, &B);
for(int i = 1; i <= M; i++) {
scanf("%d %d %d", &e[i].u, &e[i].v, &e[i].w);
addedge(e[i].u, e[i].v, e[i].w);
addedge(e[i].v, e[i].u, e[i].w);
}
Kruskal();
int cnt = 0;
for(int i = 1; i <= N; i++) {
siz[st.find(i)]++;
if(st.find(i) == i) id[i] = ++cnt;
}
for(int i = 1; i <= N; i++) id[i] = id[st.find(i)];
cnt = 0;
for(int i = 1; i <= N; i++)
if(siz[i] >= 4) nid[i] = ++cnt;
for(int i = 1; i <= N; i++) nid[i] = nid[st.find(i)];
memset(ans, 0x3f, sizeof ans);
Dijkstra();
for(int i = 1; i < N; i++)
printf("%d ", ans[i]);
printf("%d\n", ans[N]);
return 0;
}