CodeForces 1149D Abandoning Roads
题目大意
给定一张无向图,边权只有A,B(A<B)A,B(A<B)A,B(A<B)两种情况,要求求出在这张图上的所有最小生成树中,从111到iii的路径的最小值。
分析
结论(1): 边权为AAA的边必须被选做生成树上的边。
证明(1): 根据 Kruskal 算法求解生成树的过程显然可得。
这样一来整张图就被分成了许多个连通块。问题就变为用边权为BBB的边连接这些散着的连通块,使得111到iii的路径长度最小。
结论(2): 若两个点之间的路径中有一条只有AAA边,那么两个点在生成树上的路径上不可能有BBB边。
证明(2): 由生成树的性质可得显然正确。
结论(3): 一条路径可能在最小生成树上当且仅当这条路径没有离开当前连通块再回来。注意若一个连通块中有BBB边也不能够走。
这样一来就可以用状压 DP 来做了。
考虑状压 DP ,设状态f(S,u)f(S,u)f(S,u)为当前在点uuu,已经经过了SSS中的所有连通块的路径长度最小值。
由于f(S,u)f(S,u)f(S,u)和f(S,v)f(S,v)f(S,v)会互相影响,所以我们用最短路来转移。
时间复杂度为O(2NNlog?2(2NN))O(2^NN\log_2(2^NN))O(2NNlog2?(2NN)),但由于N≤70N\le 70N≤70,这样做显然时间和空间都要炸裂。
考虑优化:
结论(4): 连通块中点数小于等于333的可以不压进状态中。
证明(4): 由于离开一个连通块再回来必须经过至少两条边权为BBB的边(除非是直接用BBB边连起来的)。而当连通块的点数小于等于333时连通块中任意两个点都可以通过不超过两条AAA边互相到达。显然对于这样的一个连通块,我们走AAA边比走BBB边更好。
这样一来我们需要压进状态中的连通块个数就不会超过181818个了。于是复杂度变成大约O(218Nlog?2(218N))O(2^{18}N\log_2(2^{18}N))O(218Nlog2?(218N))。
参考代码
#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 LOACLfreopen("in.txt", "r", stdin);freopen("out.txt", "w", stdout);
#endifscanf("%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;
}