codechef Annual Parade

题目大意

一张 n 个点m条边的带边权有向图。有 K 组询问,每组询问给出一个整数C.

对于每组询问.你需要从图中选出若干条路径,相同的边可以经过多次.一个方案的代价是所有经过的道路(多次经过重复统计)的边权和加上起点不等于终点的路径条数乘 C 再加上没有经过的城市数目乘C.

对每组询问你需要计算最少代价。

2n250,1m3×104,1k104

解题思路

注意到 k 比较大,可能最终C不是最重要的。。。。。
假如只有一个询问的话,我们应该怎么做??!!!

我们好像看到有路径覆盖,还有 Min(Cost) ,估计就是 MaxflowMinCost .

考虑如何构图:
将每个点拆为 i ,i1
Si ,一条流量为1,费用为0的边
i1T ,一条流量为1,费用为0的边
i1i 一条流量为 ,费用为0的边

对于一条原图中的边 (u,v,cost)
uv1 一条流量为 ,费用为cost的边

我们看一下,假如我们现在从原点增广出了一条新的增广路,他的意义是什么。。。。

1:将两条原本不相交的路径连接在了一起,那么我们就少付了一次非环的钱, CostC
2:连出了一个环, CostC

(其实你可以把一个点想象成一个超短的边。。。。。那么点没有被覆盖其实相当于不是一个环)
我们对于一个 C ,Cost=NC

然后我们每次增广出一条增广路对答案的贡献为 PathCostC

因为我们用的是最小费用最大流算法,所以增广出来的 PathCost 是递增的。。。

PathCost>C 的时候,我们就没有必要做了。

我们对于一个询问,我们可以二分出增广到哪一条路,然后最后全部加起来就好了

时间复杂度为 O(MinCostMaxFlow(N,M)+KlogN)

参考代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define maxn 505
#define maxm 300005
#define mem(a,b) memset(a,b,sizeof(a))
#define oo 1e9
#define mo 503
using namespace std;

int head[maxn],t[maxm],next[maxm],v[maxm],cost[maxm],sum;

int f[maxn][maxn];

int n,m,q;

int S,T;

int a[maxn],s[maxn];

void ins(int x,int y,int z,int co){
    t[++sum]=y;
    v[sum]=z;
    cost[sum]=co;
    next[sum]=head[x];
    head[x]=sum;
}

void insert(int x,int y,int z,int co){
    ins(x,y,z,co);
    ins(y,x,0,-co);
}

int pre[maxn];

int dist[maxn];

bool bz[maxn];

int d[mo+5];

bool spfa(){
    mem(dist,63);
    dist[S]=0;
    int l=0,r=1;
    d[1]=S;
    while (l!=r) {
        l=(l+1) % mo;
        int now=d[l];
        bz[now]=0;
        for(int tmp=head[now];tmp;tmp=next[tmp]) {
            if (v[tmp]==0) continue;
            if (dist[t[tmp]]>dist[now]+cost[tmp]) {
                dist[t[tmp]]=dist[now]+cost[tmp];
                pre[t[tmp]]=tmp;
                if (!bz[t[tmp]]) {
                    bz[t[tmp]]=1;
                    r=(r+1) % mo;
                    d[r]=t[tmp];
                    if (dist[d[r]]<dist[d[(l+1) % mo]]) swap(d[r],d[(l+1) % mo]);
                }
            }
        }
    }
    return dist[T]<oo / 10;
}

int main(){
    scanf("%d%d%d",&n,&m,&q);
    S=0;
    T=n+n+1;
    sum=1;
    mem(f,63);
    fo(i,1,m) {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        f[x][y]=min(f[x][y],z);
    }
    fo(i,1,n) insert(S,i,1,0),insert(i+n,T,1,0),insert(i+n,i,oo,0);
    fo(i,1,n)
        fo(j,1,n) {
            if (f[i][j]>10000) continue;
            insert(i,j+n,oo,f[i][j]);
        }
    while (spfa()){
        a[++a[0]]=dist[T];
        s[a[0]]=s[a[0]-1]+a[a[0]];
        for(int tmp=T;tmp!=S;tmp=t[pre[tmp] ^ 1]) {
            v[pre[tmp]]--;
            v[pre[tmp] ^ 1]++;
        }
    }
    while (q--) {
        int w=0,x;
        scanf("%d",&x);
        int l=1,r=a[0];
        while (l<=r) {
            int mid=(l+r) >> 1;
            if (a[mid]<x) {
                w=mid;
                l=mid+1;
            }
            else r=mid-1;
        }
        printf("%d\n",x*(n-w)+s[w]);
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值