hdu5352城市重建 (km,最大流,费用流)

题意:略

题解:一道将自己隐藏得很好的匹配问题,直接说建图方法吧(按照网络流的方式叙述的,想写km的可以自行转换)

对于N座城市,每座城市都看做一个点,并直接与源点相连,容量为1,对于操作1,将其看做一个点,将此时在联通

块中的点均与其相连,容量为1,然后再将其与汇点相连,容量为k(km的话,这里就需要加k个点了,所以跑得并没有

网络流快)对于2,3操作,就是就按照它所说删边加边就是了

因为要按字典序拍序,于是可以转成费用流,越靠后的点费用越小(解释得有点模糊,详见代码)

这里只有费用流代码,km又麻烦又慢,不想写,费用流虽然看着有点长,但大多都是版

code:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
 
const int inf=0x3f3f3f3f;
const int MAXN=2000+5;
const int MAXM=2000+5;
const int MAXK=2000+5;
using namespace std;
struct node{
    int c,next,e,cost;
}h[MAXM*MAXN];
int fir[MAXN+MAXM],cnt=1,ans,N,K;
bool inq[MAXN+MAXM],vis[MAXN+MAXM];
int dis[MAXN],source,target,year;
queue<int> Q; int temp[MAXN],acnt;
bool Map[MAXN][MAXN],flag[MAXN];
 
inline void addedge(int t1,int t2,int c,int cost){
    h[++cnt].e=t2;
    h[cnt].next=fir[t1];
    h[cnt].c=c; h[cnt].cost=cost;
    fir[t1]=cnt;
 
    h[++cnt].e=t1;
    h[cnt].next=fir[t2];
    h[cnt].cost=-cost;
    fir[t2]=cnt;
}
void dfs(int s){
    flag[s]=1; addedge(s+1,year+N+1,1,-year);
    for(int i=1;i<=N;i++)
        if(Map[s][i]&&!flag[i]) dfs(i);
}
inline bool spfa(){
    Q.push(target);
    memset(dis,0x3f,sizeof dis);
    dis[target]=0; inq[target]=1;
    while(!Q.empty())
    {
        int s=Q.front(); Q.pop();
        inq[s]=0;
        for(int i=fir[s];i;i=h[i].next)
        {
            int e=h[i].e;
            if(h[i^1].c<=0) continue;
            if(dis[e]>dis[s]+h[i^1].cost)
            {
                dis[e]=dis[s]+h[i^1].cost;
                if(!inq[e])
                    Q.push(e),inq[e]=1;
            }
        }
    }
    if(dis[source]==dis[0]) return 0;
    else return 1;
}
int aug(int s,int augco){
    if(s==target)
    {
        ans+=dis[source]*augco;
        return augco;
    }
    int delta,augc=augco;
    for(int i=fir[s];i&&augc>0;i=h[i].next)
    {
        int e=h[i].e;
        if(vis[e]||h[i].c<=0) continue;
        if(dis[s]==dis[e]+h[i].cost)
        {
            vis[e]=1;
            delta=min(h[i].c,augc);
            delta=aug(e,delta);
            h[i].c-=delta;
            h[i^1].c+=delta;
            augc-=delta;
        }
    }
    return augco-augc;
}
int costflow(){
    ans=0; int flow=0;
    while(spfa())
    {
        memset(vis,0,sizeof vis);
        vis[source]=1; flow+=aug(source,inf);
    }
    return flow;
}
inline void Clear(){
    cnt=1; acnt=year=0;
    memset(Map,0,sizeof Map);
    memset(fir,0,sizeof fir);
    memset(h,0,sizeof h);
    memset(temp,0,sizeof temp);
}
int T,M,opt,x,y,q;
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        Clear();
        scanf("%d%d%d",&N,&M,&K);
        for(int i=1;i<=M;i++)
        {
            scanf("%d",&opt);
            switch(opt)
            {
                case 1:
                    scanf("%d",&x);
                    year++; dfs(x);
                    memset(flag,0,sizeof flag);
                break;
                case 2:
                    scanf("%d%d",&x,&y);
                    Map[x][y]=Map[y][x]=1;
                break;
                case 3:
                    scanf("%d",&q);
                    while(q--)
                    {
                        scanf("%d%d",&x,&y);
                        Map[x][y]=Map[y][x]=0;
                    }
                break;
            }
        }
        target=year+N+2; source=1;
        for(int i=1;i<=year;i++)
            addedge(i+N+1,target,K,0);
        for(int i=1;i<=N;i++) addedge(1,i+1,1,0);
        printf("%d\n",costflow());
        for(int i=fir[target];i;i=h[i].next)
            temp[++acnt]=h[i].c;
        for(int i=acnt;i;i--)
            if(i!=1) printf("%d ",temp[i]);
            else printf("%d\n",temp[i]);
    }
}
/*
n 1000
m 1000
k 1000
这道题呢,到是有点思路,最开始,先把输入离线下来
然后看,对于1操作,将k个城市拆成k个点,将处于该联通
块内的每个城市都与这k个点相连,权值为这座城市出现的次数
至于加边加点的操作,用矩阵应该比较方便吧,或者把所有
联通块都给存下来?
只是这个
建图方法:
将源点与所有N座城市均连一条边,容量为1,费用为0
对于每次1操作,将所有联通块内的节点与该年连边,容量为1,
费用为之前进行了几次1操作的数量取负,即越靠后的费用越小
最后,把所有年份与汇点连边,容量为K,费用为0
1
5 6 2
2 1 2
2 1 3
1 1
1 2
3 1 1 2
1 2
*/


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值