BZOJ4681 : [Jsoi2010]旅行
将边按权值从小到大排序。
考虑一条路径,一定是最大的若干条边和最小的相应的没选的边进行交换。
这会导致存在一个分界线$L$,交换之后恰好选中前$L$小的边,且只允许$>L$的边与$\leq L$的边进行交换。
枚举$L$,设$f[i][j][k]$表示从$1$到$i$,经过了$j$条前$L$小的边,舍弃了$k$条$>L$的边时,$>L$且未舍弃的边权和的最小值。
用Dijkstra算法求出$f$,更新答案即可。
时间复杂度$O((n+m)m^2k)$。
#include<cstdio> #include<queue> #include<vector> #include<algorithm> using namespace std; typedef pair<int,int>P; const int N=55,M=155,inf=~0U>>1; int n,m,K,i,j,k,L,g[N],v[M<<1],nxt[M<<1],ed,base,ans=inf,f[N][M][22]; priority_queue<P,vector<P>,greater<P> >q; struct E{int x,y,w;}e[M]; inline bool cmp(const E&a,const E&b){return a.w<b.w;} inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;} inline void ext(int x,int y,int z,int w){ if(y>L||z>K)return; if(f[x][y][z]<=w)return; q.push(P(f[x][y][z]=w,(x<<13)|(y<<5)|z)); } int main(){ scanf("%d%d%d",&n,&m,&K); for(i=1;i<=m;i++)scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].w); sort(e+1,e+m+1,cmp); for(ed=i=1;i<=m;i++)add(e[i].x,e[i].y),add(e[i].y,e[i].x); for(L=0;L<=m;L++){ base+=e[L].w; if(base>=ans)break; for(i=1;i<=n;i++)for(j=0;j<=L;j++)for(k=0;k<=K;k++)f[i][j][k]=inf; ext(1,0,0,base); while(!q.empty()){ P t=q.top();q.pop(); int z=t.second&31;t.second>>=5; int y=t.second&255;t.second>>=8; int x=t.second; if(f[x][y][z]<t.first)continue; for(i=g[x];i;i=nxt[i])if((i>>1)<=L)ext(v[i],y+1,z,t.first); else{ ext(v[i],y,z,t.first+e[i>>1].w); ext(v[i],y,z+1,t.first); } } for(j=0;j<=L;j++)for(k=0;k<=K;k++)if(j+k<=L&&f[n][j][k]<ans)ans=f[n][j][k]; } return printf("%d",ans),0; }