十二桥问题
给你一个n个点m条边的无向图,每条边有一个权值d,其中有k条边必须经过,从1号点出发,求经过这k条边,并且回到1号点的最小花费。
n ≤ 50000,m ≤ 200000, d ≤ 1e9, k ≤ 12
首先拿到这道题,我们看到一个k ≤ 12的范围,是不是就想到了状压dp?
可是我们一般状压dp上面存的是点,(比如:dp[S][i] 表示选了集合S,最后一个点是i的最小花费),这道题要我们存边,如果把每一条边上的点都拿来做的话,点数猛增到24,然而
2 24 = 16 , 777 , 216 2^{24}=16,777,216 224=16,777,216
我们还要转移,相当于直接给这个方法判了死刑,那我们要怎么转移呢?
我们用 dp[S][i][0/1] 表示集合S上最后是第i条边 (这里的i是指这k条必走的边中的顺序,下方的i同义) 上的第一个/第二个点时的最小花费。
转移就是
for(re int S = 3; S < (1<<k);S += 2)
for(re int i=1; i<=k; i++)//i: decided
{
int biti = 1 << (i-1);
if( !(S & biti) ) continue;
for(re int j=1; j<=k; j++)//j: going to decide
{
int bitj = 1 << (j-1);
if( i == j || !(S & bitj) ) continue;
dp[S][j][0] = min(dp[S][j][0], min( dp[S^bitj][i][0] + cost[i*2-1][j*2],
dp[S^bitj][i][1] + cost[i*2][j*2]) + a[j].val);
dp[S][j][1] = min(dp[S][j][1], min( dp[S^bitj][i][0] + cost[i*2-1][j*2-1],
dp[S^bitj][i][1] + cost[i*2][j*2-1]) + a[j].val);
}
}
其中 cost[i][j] 是第i个点到第j个点的距离, a[i].val 表示第i条边的长度。
最短路算法就不多说了,这里主要是分析一下计算最短路消耗的时间。
首先,如果我们算出每两个点之间的距离,每一次Dijkstra算法复杂度为 O ( m log 2 n ) O(m\log_2n) O(mlog2n) ,要做n次,那么求最短路的总时间就是 O ( m ∗ n log 2 n ) O(m*n\log_2n) O(m∗nlog2n),明显过不了。
那么我们考虑,事实上会用到的点只有这k条重要边上的点,我们其实只需要计算每个点到其他重要点的距离就可以了。复杂度为 O ( k ∗ m log 2 n ) O(k*m\log_2n) O(k∗mlog2n),可以接受。
void Dij(int s)
{
memset(dis, 0x3f, sizeof dis);inf=dis[0];
while(!Q.empty()) Q.pop();
Q.push(node(s, 0));dis[s]=0;
while(!Q.empty())
{
node t = Q.top(); Q.pop();
int u = t.p;
if(dis[u] < t.d) continue;
for(re int i=h[u]; ~i; i=e[i].nxt)
{
int v = e[i].to;
LL val = e[i].val;
LL tmp = dis[u];
if(tmp + val < dis[v])
{
dis[v] = tmp + val;
Q.push(node(v, dis[v]));
}
}
}
}
for(re int i=1; i<=k; i++)
{
u=a[i].a;v=a[i].b;
Dij(u);
for(re int j=1; j<=k; j++)
{
cost[i*2-1][j*2-1] = dis[a[j].a];
cost[i*2-1][j*2] = dis[a[j].b];
}
Dij(v);
for(re int j=1; j<=k; j++)
{
cost[i*2][j*2-1] = dis[a[j].a];
cost[i*2][j*2] = dis[a[j].b];
}
}
事实上,细心的读者已经发现了我们之前的dp状态转移时集合 S 的枚举是很奇怪的:
for(re int S = 3; S < (1<<k);S += 2)
那么就要提到这道题的一个性质了,**我们必须要从1号点出发。**而普通的状压dp并不会固定第一个枚举的是什么,然而我们要特殊处理第一个点又很麻烦。
那么我们要怎么能在确保首先选第一个点的情况下,不增加太多代码难度呢?
其实我们可以把1号点看做第一个特殊点,不管它是否连重要边,然后从dp[1][1][0/1]开始转移即可
最后一个1号点,只需要统计完后再在结果中去计算最后一个1号点,统计答案即可
综上,总的时间复杂度为 O ( k ∗ m log 2 n + 2 k ∗ k 2 ) O( k*m\log_2n+ 2^k*k^2) O(k∗mlog2n+2k∗k2)
除了一些很模板的算法之外,再加上快读,register,inline等技巧(虽然说程序还是取决于算法,可是这些用来 骗骗分 还是很有用的。。。情况好的话可以多10分左右),性能上还是不错的
#include
#include
#include
#include
#include
#include
#include
#include
#define re register
#define ud unsigned
using namespace std;
typedef long long LL;
#define gc() getchar()
#define digit(x) (x>='0' && x<='9')
LL read()
{
LL x=0,f=1; char c=gc();
while(!digit(c)){if(c=='-') f=-f; if(c==-1) return -1; c=gc();}
while(digit(c)) x=(x<<3)+(x<<1)+c-'0', c=gc();
return x*f;
}
#undef gc
#undef digit
const int N = 50005, M = 200005, K = 16;
LL inf;
struct Edge
{
int to, nxt;
LL val;
Edge(){}
Edge(int T, int N, LL V){to=T;nxt=N;val=V;}
}e[M<<1];int cnte;
struct E
{
int a, b;
LL val;
E(){}
E(int A, int B, LL V){a=A;b=B;val=V;}
}a[K];
int h[N];
inline void Add_edge(int u, int v, LL val)
{
e[++cnte] = Edge(v, h[u], val), h[u]=cnte;
e[++cnte] = Edge(u, h[v], val), h[v]=cnte;
}
struct node
{
int p;
LL d;
node(){}
node(int P, LL D){p=P;d=D;}
inline bool operator < (const node &b)const
{return d > b.d;}
};
int n, m, k;
LL dis[N];
LL cost[K<<1][K<<1];
LL dp[1<<K][K][2];
priority_queue<node> Q;
void Dij(int s)
{
memset(dis, 0x3f, sizeof dis);inf=dis[0];
while(!Q.empty()) Q.pop();
Q.push(node(s, 0));dis[s]=0;
while(!Q.empty())
{
node t = Q.top(); Q.pop();
int u = t.p;
if(dis[u] < t.d) continue;
for(re int i=h[u]; ~i; i=e[i].nxt)
{
int v = e[i].to;
LL val = e[i].val;
LL tmp = dis[u];
if(tmp + val < dis[v])
{
dis[v] = tmp + val;
Q.push(node(v, dis[v]));
}
}
}
}
int main()
{
memset(h, -1, sizeof h);
n=read();m=read();k=read();
int u, v;LL P;
a[1].a=1;a[1].b=1;a[1].val=0;
for(re int i=1; i<=m; i++)
{
u=read();v=read();P=read();
if(i<=k) a[i+1].a=u, a[i+1].b=v, a[i+1].val=P;
Add_edge(u, v, P);
}
k++;
for(re int i=1; i<=k; i++)
{
u=a[i].a;v=a[i].b;
Dij(u);
for(re int j=1; j<=k; j++)
{
cost[i*2-1][j*2-1] = dis[a[j].a];
cost[i*2-1][j*2] = dis[a[j].b];
}
Dij(v);
for(re int j=1; j<=k; j++)
{
cost[i*2][j*2-1] = dis[a[j].a];
cost[i*2][j*2] = dis[a[j].b];
}
}
memset(dp, 0x3f, sizeof dp);
dp[1][1][0]=dp[1][1][1]=0;
for(re int S = 3; S < (1<<k);S += 2)
for(re int i=1; i<=k; i++)//i: decided
{
int biti = 1 << (i-1);
if( !(S & biti) ) continue;
for(re int j=1; j<=k; j++)//j: going to decide
{
int bitj = 1 << (j-1);
if( i == j || !(S & bitj) ) continue;
dp[S][j][0] = min(dp[S][j][0], min( dp[S^bitj][i][0] + cost[i*2-1][j*2],
dp[S^bitj][i][1] + cost[i*2][j*2]) + a[j].val);
dp[S][j][1] = min(dp[S][j][1], min( dp[S^bitj][i][0] + cost[i*2-1][j*2-1],
dp[S^bitj][i][1] + cost[i*2][j*2-1]) + a[j].val);
}
}
LL ans = inf;
for(re int i=2; i<=k; i++)
ans = min( ans, min( dp[ (1 << k) - 1][i][0] + cost[i*2-1][1], dp[ (1 << k) - 1][i][1] + cost[i*2][1] ) );
printf("%lld\n", ans);
return 0;
}