一、题目
二、解法
套路:当只有两个关键状态量时,我们以一个量为主,一个量为辅思考问题。
那么我们以 (a) 边为主,因为不可能表示出原图的最小生成树所以我们开始找结论。根据 ( t kruskall) 算法我们先把所有 (a) 边连起来,那么会形成若干个 (a) 边连通块,考虑 (b) 边会把这些连通块串成一棵生成树。
结论:(a ightarrow b) 的路径可能在最小生成树中出现,当且仅当这条路径没有通过 (b) 边走出 (a) 连通块再通过 (b) 边走回来,并且不经过 (a) 连通块内的 (b) 边,因为走出去和走回来的两条 (b) 边是不可能同时出现的,而 (a) 连通块内的 (b) 边无论如何都不在最小生成树中。但 (a) 连通块内部的 (a) 边路径和经过 (b) 边走到其他连通块的路径都是合法的,证毕。
知道这个结论以后就得到了一个最短路问题,由于限制有不能走回以前的连通块,所以我们记录已经离开的连通块有哪些。设 (dp[s][u]) 为离开过的连通块集合为 (s),现在所处的点为 (u),然后跑 ( t dijkstra) 即可。
但是 (s) 可能很大,考虑大小 (leq 3) 的连通块是不需要记录在 (s) 中的,因为最优决策下绝不会出现通过 (b) 边走出去再走回来的情况,那么时间复杂度 (O(2^{n/4}cdot mcdot log))
三、总结
首先是开头那个很重要的套路。
此外猜结论要对着要求的东西猜,比如这道题要求的是最短路就找最短路合法的条件。
最后是有关连通块的状压,可以讨论一些较小的连通块可以大幅度降低复杂度。
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int M = 75;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,a,b,tot,cnt,tmp,id[M],vis[M],f[M],dp[1<<17][M];
struct edge
{
int v,c,next;
}e[M*M];
struct node
{
int s,u,c;
bool operator < (const node &b) const
{
return c>b.c;
}
};
void dfs(int u,int c)
{
id[u]=c;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(id[v]==-1 && e[i].c==a) dfs(v,c);
}
}
int cal(int u)
{
vis[u]=1;int r=1;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(!vis[v] && e[i].c==a) r+=cal(v);
}
return r;
}
void dijk()
{
memset(dp,0x3f,sizeof dp);
priority_queue<node> q;
q.push(node{0,1,0});dp[0][1]=0;
while(!q.empty())
{
int s=q.top().s,u=q.top().u,c=q.top().c;
q.pop();
if(c>dp[s][u]) continue;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v,ts=s;
//leave the block before
if(id[v]<tmp && (s&(1<<id[v]))) continue;
//in the same block but use b
if(id[u]==id[v] && e[i].c==b) continue;
//enter a brand new block
if(id[u]<tmp && id[u]!=id[v]) ts|=(1<<id[u]);
if(dp[ts][v]>dp[s][u]+e[i].c)
{
dp[ts][v]=dp[s][u]+e[i].c;
q.push(node{ts,v,dp[ts][v]});
}
}
}
for(int i=1;i<=n;i++)
{
int ans=1e9;
for(int j=0;j<(1<<tmp);j++)
ans=min(ans,dp[j][i]);
printf("%d ",ans);
}
}
signed main()
{
n=read();m=read();a=read();b=read();
for(int i=1;i<=m;i++)
{
int u=read(),v=read(),c=read();
e[++tot]=edge{v,c,f[u]},f[u]=tot;
e[++tot]=edge{u,c,f[v]},f[v]=tot;
}
memset(id,-1,sizeof id);
for(int i=1;i<=n;i++)
if(!vis[i] && cal(i)>=4) dfs(i,cnt++);
tmp=cnt;
memset(vis,0,sizeof vis);
for(int i=1;i<=n;i++)
if(!vis[i] && cal(i)<4) dfs(i,cnt++);
dijk();
}