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;
}

  

posted @ 2017-09-01 03:24  Claris  阅读(519)  评论(0编辑  收藏  举报