Forethought Future Cup - Elimination Round G. Zoning Restrictions 最大流(最小割)

题目链接: https://codeforces.com/contest/1146/problem/G

题意:

你现在要给 n n n 个位置制定高度 h i ( 1 < = h i < = H ) h_i (1<=h_i<=H) hi(1<=hi<=H) ,每个位置 i i i 的高度可以带来 h i 2 h_i^2 hi2 的收益,但是有 m m m 条限制,每个限制 j j j 的信息为 l j , r j , x j , c j l_j,r_j,x_j,c_j lj,rj,xj,cj 表示从位置 l j l_j lj 位置 r j r_j rj 当中如果有任意一个位置的高度超过了 x j x_j xj 那么就要罚款 c j c_j cj ,现在问你最大的总收益为多少。

做法:

因为 n , H , m n,H,m n,H,m 的大小均不超过 50 50 50 ,所以还是很明显能感受到是最大流的。套路无非就是 原最大收益-最小代价,代价在 ① ① 只到达这个高度的损失 和 ② ② 到达这个高度所要接受的罚款 里面选出更小的,那么这个更小的值的就靠 最大流=最小割 来维护了。

我想了挺久的,其实一开始方向蛮对的,每个位置每个高度一个点,每个条件一个点,但是不知道为什么后来就偏了,想用区间来表示(脑子瓦特了还顺着这个错误的方向想了很久)。

略过略过,其实沿着一开始的思路的话还是很顺的想的。把点按照高度拆开,高度 z z z 向高度 z + 1 z+1 z+1 连一条只达到高度 z + 1 z+1 z+1 时要损失的代价。然后将限制条件 m j m_j mj中每一个合法的位置 p o s ( l j < = p o s < = r j ) pos(l_j<=pos<=r_j) pos(lj<=pos<=rj) 的高度为 x j + 1 x_j+1 xj+1 的点向限制条件连一条边, m j m_j mj 本身向汇点连一条代价这么多的边即可。

画了个丑陋的图,第一个样例帮助理解。
在这里插入图片描述

代码

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i = (int)a;i<=(int)b;i++)
#define pb push_back
#define lson rt<<1
#define rson rt<<1|1
#define mid (l+r)/2
using namespace std;
const int maxn=2800;
const int maxm=30005;
const int inf = 1<<30;
typedef long long ll;
int nex[maxm],to[maxm],cap[maxm],from[maxm];
int n,m,h,cnt,head[maxn],d[maxn],sp,tp;//原点,汇点
//理论复杂度n2*m
void add(int u,int v,int c){
    //printf("from = %d to = %d  cap = %d\n",u,v,c);
    from[cnt]=u,to[cnt]=v,cap[cnt]=c,nex[cnt]=head[u],head[u]=cnt++;
    from[cnt]=v,to[cnt]=u,cap[cnt]=0,nex[cnt]=head[v],head[v]=cnt++;
}
int bfs(){
    queue <int> q;
    memset(d,-1,sizeof(d));
    d[sp]=0;
    q.push(sp);
    while(!q.empty()){
        int cur=q.front();
        q.pop();
        for(int i=head[cur];i!=-1;i=nex[i]){
            int u=to[i];
            if(d[u]==-1 && cap[i]>0){
                d[u]=d[cur]+1;
                q.push(u);
            }
        }
    }
    return d[tp] != -1;
}
int dfs(int a,int b){
    int r=0;
    if(a==tp)return b;
    for(int i=head[a];i!=-1 && r<b;i=nex[i])
    {
        int u=to[i];
        if(cap[i]>0 && d[u]==d[a]+1)
        {
            int x=min(cap[i],b-r);
            x=dfs(u,x);
            r+=x;
            cap[i]-=x;
            cap[i^1]+=x;
        }
    }
    if(!r)d[a]=-2;
    return r;
}

int dinic(int sp,int tp){
    int total=0,t;
    while(bfs()){
        while(t=dfs(sp,inf))
        total+=t;
    }
    return total;
}
int gain(int id,int hei){
    return (id-1)*(h+1)+hei+1;
}
int sub(int x){
    return h*h-x*x;
}
int main(){
    cnt=0;
    memset(head,-1,sizeof(head));
    scanf("%d%d%d",&n,&h,&m);
    int ans=n*h*h,np=(h+1)*n;
    sp=0,tp=np+m+1;
    rep(i,1,n){
        add(sp,gain(i,0),inf);
        rep(j,1,h){
            add(gain(i,j-1),gain(i,j),sub(j-1));
        }
    }
    rep(i,1,m){
        int l,r,x,c; scanf("%d%d%d%d",&l,&r,&x,&c);
        if(x==h) continue;
        rep(j,l,r){
            add(gain(j,x+1),np+i,inf);
        }
        add(np+i,tp,c);
    }
    printf("%d\n",ans-dinic(sp,tp));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值