多校HDU 3917 Road constructions(最大权闭合图)

题意复杂,而且感觉很不严谨,大概的意思就是给几个公司,告诉每个公司你税收,这个公司要修得路(如果选择了某个公司,则必须要全修,如果有associated关系,要连锁选择)及花费,求政府的最大收益(所选的公司的收税减其花费)。

解法为求最小割,构图方法以公司为点,如果公司之间有关系,则连一条容量为无穷的有向边,对每个公司,如果该公司收益为正,则从s到该公司连一条容量为收益的有向边,如果为负向t连一条容量为该公司收益绝对值的边,为0时选和不选不影响结果,可以舍去。每一种可行情况都对应图中的一个ST割,其消费为所有收益和-所选割中正收益-所有割中负收益(减去负的即为加上绝对值),又因为公司之间边为正无穷,最小割一定存在,则一定不包含公司间的边,这样若求最大收益,即求图中最小割。

用的SAP的优化算法 跑

 312ms,只要是懒的模拟数组临界表了

#include <cstdio>
#include <string.h>
#include <vector>
using namespace std;

const int N=5010;
const int M=100000;//边是双向存的(注意不是无向)要开正常的2倍大
const int inf=0x3fffffff;
int head[N];
struct Edge{
    int v,next,w;
} edge[M];
int cnt,s=0;
vector <int> st[N],to[N];//st[i],to[i]表示以city i位起点和终点的公司 

void addedge(int u,int v,int w)//这里存的还是一条有向边 
{
    edge[cnt].v=v;
    edge[cnt].w=w;
    edge[cnt].next=head[u];
    head[u]=cnt++;
    edge[cnt].v=u;
    edge[cnt].w=0;
    edge[cnt].next=head[v];
    head[v]=cnt++;
}

int sap(int t){
    int pre[N],cur[N],dis[N],gap[N];
    int flow=0,aug=inf,u;
    bool flag;
    for(int i=0; i<=t; i++){
        cur[i]=head[i];
        gap[i]=dis[i]=0;
    }
    gap[s]=t+1;
    u=pre[s]=s;
    while(dis[s]<=t){
        flag=0;
        for(int &j=cur[u]; ~j; j=edge[j].next){
            int v=edge[j].v;
            if(edge[j].w>0&&dis[u]==dis[v]+1){
               flag=1;
               if(edge[j].w<aug) aug=edge[j].w;
               pre[v]=u;
               u=v;
               if(u==t){
                    flow+=aug;
                    //printf("%d\n",flow);
                    while(u!=s){
                       u=pre[u];
                       edge[cur[u]].w-=aug;
                       edge[cur[u]^1].w+=aug;//异或是找与其配对的边 
                    }
                    aug=inf;
                }
                break;
            }
        }
        if(flag) continue;
        int mindis=t+1;
        for(int j=head[u]; ~j; j=edge[j].next){
            int v=edge[j].v;
            if(edge[j].w>0&&dis[v]<mindis){
                mindis=dis[v];
                cur[u]=j;
            }
        }
        if((--gap[dis[u]])==0)
            break;
        gap[dis[u]=mindis+1]++;
        u=pre[u];
    }
    return flow;
}

int tax[N];
int sum;
int n,m,k;

void build ()
{
     for (int i=1; i<=m ; ++i)
     {
         if(tax[i]>0)addedge(0,i,tax[i]),sum+=tax[i];
         if(tax[i]<0)addedge(i,m+1,-tax[i]);
     }
     for (int i=0 ; i<n ; ++i)
      for (int j=0 ; j<st[i].size() ; ++j)
       for (int p=0 ; p<to[i].size() ; ++p)
        addedge(to[i][p],st[i][j],inf);
     //printf("%d\n",sum);
}

void init ()
{
     sum=cnt=0;
     memset(head,-1,sizeof(head));
}

int  main()
{
    int i,j,u,v,w,company;
    //freopen ("a.txt","r",stdin);
    //freopen ("b.txt","w",stdout);
    while(scanf("%d%d",&n,&m),n||m)
    {
         for (i=0 ; i<n ; ++i)
         st[i].clear(),to[i].clear();
         init();
         for (i=1 ; i<=m ; ++i)
          scanf("%d",tax+i);
         scanf("%d",&k);
         for (i=0 ; i<k ; ++i)
         {
             scanf("%d%d%d%d",&u,&v,&company,&w);
             u--;v--;
             st[u].push_back(company);
             to[v].push_back(company);
             tax[company]-=w;
         }
         build();
         int ans=sap(m+1);
         printf("%d\n",sum-ans);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值