【网络流-最大流】洛谷P3701「伪模板」主席树

链接

https://www.luogu.org/problemnew/show/P3701


大意

定义一些攻击规则,问 A A 方手下的人总共能赢得几场比赛?


思路

真的是伪模板。。。
可以观察发现,这道题是一道典型的最大匹配题
把一点血量当作一个人,续命理解为新增一个人,那么这样就形成了下面这张图
...
但如果我们每个点每种血量都专门建入点和出点,因为生命最多为五十,这样子的时间复杂度为O((50n+n2)3)是不能接受的

考虑到这些容量为1的点是可以组合在一起的,所有就有了下面这张图

2
这样子的时间复杂度就被压缩成了 O(n3) O ( n 3 )


代码

#include<queue>
#include<cstdio>
#include<cstring>
#define N 101
#define M 160001
#define r(i,a,b) for(register int i=a;i<=b;i++)
using namespace std;int n,m,tot,l[M],d[M],S,T,ans,ba[N],bs[N],jxa,jxs;
char ra[N][3],rs[N][3];
struct node{int next,to,w;}e[M<<1];
inline int read()//输入优化
{
    int f=0;char c;
    while(c=getchar(),c<48||c>57);f=(f<<3)+(f<<1)+c-48;
    while(c=getchar(),c>47&&c<58) f=(f<<3)+(f<<1)+c-48;
    return f;
}
inline void add(register int u,register int v,register int w)//建边
{
    e[tot]=(node){l[u],v,w};l[u]=tot++;
    e[tot]=(node){l[v],u,0};l[v]=tot++;
    return;
}
inline bool bfs()//bfs建造分层图
{
    memset(d,-1,sizeof(d));
    queue<int>q;d[S]=0;q.push(S);
    while(q.size())
    {
        int x=q.front();q.pop();
        for(int i=l[x];~i;i=e[i].next)
        {
            int y=e[i].to;
            if(e[i].w&&d[y]==-1)
            {
                d[y]=d[x]+1;
                q.push(y);
                if(y==T) return true;
            }
        }
    }
    return false;
}
inline int dfs(register int x,register int flow)//dfs计算可行流
{
    if(x==T||!flow) return flow;
    int rest=0,f=0;
    for(int i=l[x];~i;i=e[i].next)
    {
        int y=e[i].to;
        if(d[x]+1!=d[y]||!e[i].w)continue;
        f=dfs(y,min(flow-rest,e[i].w));
        if(!f) {d[y]=-1;continue;}
        e[i].w-=f;rest+=f;e[i^1].w+=f;
        if(rest==flow) return flow;
    }
    return rest;
}
inline void dinic(){while(bfs()) ans+=dfs(S,2147483647);return;}//dinic计算最大流
int main()
{
    memset(l,-1,sizeof(l));
    n=read();m=read();
    r(i,1,n) scanf("%s",ra[i]),jxa+=ra[i][0]=='Y'?1:0;getchar();//输入
    r(i,1,n) scanf("%s",rs[i]),jxs+=rs[i][0]=='Y'?1:0;getchar();//输入
    r(i,1,n) ba[i]=read();
    r(i,1,n) bs[i]=read();//输入
    S=0;T=n<<1|1;//建立源点和汇点
    r(i,1,n)
    r(j,1,n)
    {
        char s1=ra[i][0],s2=rs[j][0];
        if(s1=='J'&&(s2=='H'||s2=='W'))
        add(i,j+n,1);
        if(s1=='E'&&(s2=='J'||s2=='Y'))
        add(i,j+n,1);
        if(s1=='Y'&&(s2=='J'||s2=='H'))
        add(i,j+n,1);
        if(s1=='H'&&(s2=='E'||s2=='W'))
        add(i,j+n,1);
        if(s1=='W'&&(s2=='Y'||s2=='E'))
        add(i,j+n,1);//若能击败则连边
    }
    r(i,1,n)
    {
        char s1=ra[i][0],s2=rs[i][0];
        if(s1=='J') add(S,i,ba[i]+jxa);else add(S,i,ba[i]);
        if(s2=='J') add(i+n,T,bs[i]+jxs);else add(i+n,T,bs[i]);//若有加血记得加上加血
    }
    dinic();
    if(m<ans) ans=m;//因为我们可能可以多次击败,但是不能超过m
    printf("%d",ans);//输出
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值