BZOJ4681 : [Jsoi2010]旅行

Claris發表於2017-09-01

將邊按權值從小到大排序。

考慮一條路徑,一定是最大的若干條邊和最小的相應的沒選的邊進行交換。

這會導致存在一個分界線$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;
}

  

相關文章