codechef Annual Parade

本文介绍了一种解决特定图论问题的方法——最小费用最大流算法。针对一张带有边权的有向图,通过构建增广路来求解最优路径覆盖问题。详细解释了算法流程与实现,并给出了具体的时间复杂度分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

一张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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值