问题 A: 药水(2018icpc南京i题(匈牙利(已A)or最大流))

问题 A: 药水

时间限制: 1 Sec  内存限制: 128 MB
提交: 165  解决: 26
[提交] [状态] [命题人:admin]

题目描述

冬之国有N个勇士,他们要屠M条龙,每个勇士只能够屠一条龙。
每个勇士的技能属性不同,因此他们能够屠的龙也不同,第i个勇士可以屠编号在集合Si内的龙。
作为冬之国最恶毒的女巫小M,她有K瓶药水,每一瓶药水可以使得一个勇士在屠完一条龙之后不会疲倦,当然药水药效有限,只能够支持勇士们再多屠一条龙。同时小M配的药水有一定的副作用,因此每个勇士最多只能喝一瓶药水。
现在小M想要知道,在她的帮助下,勇士们最多能屠多少条龙。

输入

第一行三个正整数N,M,K。
接下来N行,每行|Si|+1个正整数,首先是|Si|描述集合大小,然后是这个集合中的元素。

输出

输出一行一个整数表示答案。

样例输入

复制样例数据

3 5 2
4 1 2 3 5
2 2 5
2 1 2

样例输出

4

提示

对于100%的数据,N,M,K≤500。

 

[提交][状态]

GPLv2 licensed by HUSTOJ 2019

 

 

最大流板子、

也可以写匈牙利

 

我本来写匈牙利的,调了半天没调出来,拿最大流dinic过了后,对拍,把匈牙利的hack点找出来的两个,然后就A了

目前我的匈牙利可以A掉upc的药水(别的地方没试过)

 

最大流的话,建图如下,就是建完图套个板子

 

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e3+7,mod = 1e9+7;
const ll inf = 1e16;
struct node{
    ll t,cap,flow,next;
}e[maxn*maxn];
int head[maxn],cur[maxn],cnt;
void add(int u,int v,ll cap){
    e[cnt]=node{v,cap,0,head[u]};
    head[u]=cnt++;
    e[cnt]=node{u,0,0,head[v]};
    head[v]=cnt++;
}
int d[maxn];
bool bfs(int s,int t){
    memset(d,0,sizeof(d));
    queue<int>q;
    q.push(s);
    d[s]=1;
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=head[u];~i;i=e[i].next){
            int v=e[i].t;
            if(d[v]==0&&e[i].cap-e[i].flow>0){
                d[v]=d[u]+1;
                q.push(v);
            }
        }
    }
    return d[t]>0;
}
ll dfs(int s,int t,ll minedge){
    if(s==t)return minedge;
    ll flow=0;
    for(int &i=cur[s];~i;i=e[i].next){
        int v=e[i].t;
        if(d[v]==d[s]+1&&e[i].cap-e[i].flow>0){
            ll temp=dfs(v,t,min(minedge-flow,e[i].cap-e[i].flow));
            e[i].flow+=temp;
            e[i^1].flow-=temp;
            flow+=temp;
            if(flow==minedge)return flow;
        }
    }
    if(flow==0)d[s]=0;
    return flow;
}
ll dinic(int s,int t){
    ll maxflow=0;
    while(bfs(s,t)){
        memcpy(cur,head,sizeof(head));
        maxflow+=dfs(s,t,inf);
    }
    return maxflow;
}
int main(){
    memset(head,-1,sizeof head);
    int n,m,k;
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1,k;i<=n;i++){
        scanf("%d",&k);
        for(int j=1,x;j<=k;j++){
            scanf("%d",&x);
            add(i,n+x,1);
        }
    }
    for(int i=1;i<=n;i++)add(n+m+1,i,1);//s
    for(int i=1;i<=m;i++)add(n+i,n+m+2,1);//t
    for(int i=1;i<=n;i++)add(n+m+3,i,1);//k
    add(n+m+1,n+m+3,k);
    ll ans = dinic(n+m+1,n+m+2);
    printf("%lld\n",ans);
    return 0;
}

 

 

匈牙利的话

大体思路:

考虑多开一倍的点(因为最多喝一瓶药),直接跑匈牙利,然后限制答案在(n+k)范围内

仔细看,如果k大于n的话,n+k那就不对了(最多喝一瓶,这样明显多于2*n了),所以

限制范围在n+min(n,k),然后我就没再想出来hack点

 

今天对拍后,得到下面这个图

 

突然惊醒,不能是n+min(n,k)

因为人数可能比n要小,这里统计的人数为cnt个

答案就应该限制在cnt+min(cnt,k)

 

然后我一交,wa了

继续对拍,得到下面这个图

 

这个算出来的话是4,但是答案实际上是3

我想了想,不应该是在cnt个人的基础上增加min(cnt,k)

而是应该在原先图的基础上跑一个匈牙利得到tans,然后再考虑增加min(cnt,k)

 

然后我又一交,嗯嗯,A了

 

代码

 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e3+7;
int mmp[maxn][maxn];
int last_match[maxn];
bool vis[maxn];
int n,m,k;
bool dfs(int u){
    for(int i=1;i<=m;i++)if(mmp[u][i]){
        if(!vis[i]){
            vis[i] = 1;
            if(!last_match[i]||dfs(last_match[i]))
                return last_match[i] = u,1;
        }
    }
    return 0;
}
int Hry(int ans = 0){
    memset(last_match,0,sizeof last_match);
    for(int i=1;i<=n;i++){
        memset(vis,0,sizeof vis);
        if(dfs(i))
            ans++;
    }
    return ans;
}
int main(){
    scanf("%d%d%d",&n,&m,&k);
    int cnt = 0;
    for(int i=1,k;i<=n;i++){
        scanf("%d",&k);
        if(k)cnt++;
        for(int j=1,x;j<=k;j++){
            scanf("%d",&x);
            mmp[i][x] = mmp[i+n][x] = 1;
        }
    }
    int tans = Hry();
    n <<= 1;
    int ans = Hry();
    n >>= 1;
    ans = min(ans,tans+min(cnt,k));
    printf("%d\n",ans);
    return 0;
}
/*
5 5 0
0
4 1 2 3 4
2 2 4
1 3
1 3
*/

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值