UOJ 575 光伏元件

#575. 【ULR #1】光伏元件

好牛的题

首先行、列拆点,\(i\)表示第\(i\)行,\(i+n\)表示第\(i\)

先考虑如何表达\(|c_{0,i}-c_{1,i}|\leq k\)这一限制,我们假设\(t=\min(c_{0,i},c_{1,i})\),则\(c_{0,i}\leq t+k\)\(c_{1,i}\leq t+k\),那么考虑建一条边,\((i+n,i,t,0)\),这样的话,就用\(i\)的入流表示第\(i\)行的\(1\)的个数,\(i+n\)的出流表示第\(i+n\)行的\(1\)的个数,对于拆成的两个限制,连边\((s,i,[0,k],0)\)\((i+n,t,[0,k],0)\)即可,不难发现\(t\in[l,r-k-l]\)时能恰好表示所有的情况,但此时\(t\)的含义已经不仅是\(min\)值了,这里\(k\)\(r-l\)\(min\)

对于\(a_{i,j}=1\),先建边\((i,j+n,[1,1],0)\),这里相当于强制让\(i\)\(j+n\)都会分别至少有一个入流和一个出流(为什么要这么别扭的建边呢,因为我们前文提到用\(i\)的入流表示第\(i\)\(1\)的数量,\(i+n\)的出流表示第\(i+n\)行的\(1\)的个数,那么既然我们的第一个限制条件是用\((s,i)\)\((i+n,t)\)\((i+n,i)\)这些边表示出来的,那么我们后面就不能破坏这些限制条件,具体来说,就是\(i\)不能添加新的入边,\(i+n\)不能添加新的出边),然后若这个点能修改,那么其实就相当于反悔操作,建边\((j+n,i,[0,1],c_{i,j})\)即可(因为这里相当于是反悔,也就是说,这条边是基于满足第一个限制之上的,选择这条边只是相当于去除\(i\)\(j+n\)分别一条合法的入边和出边,去除过后的肯定也是合法的,所以此处\(i\)的入边、\(j+n\)的出边是合法的)

对于\(a_{j,i}=0\),建边\((i,j+n,[0,1],c_{i,j})\)

然后这就是一个有源汇上下界费用流了,保证有解,直接跑就行

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=105,INF=1e9;
const ll inf=1e18;
int n,a[N][N],d[N<<1],s,t,S,T,edge[N][N];
ll ans;
int head[N<<1],cnt=1;
struct node{
    int nxt,v,val,w;
}tree[N*(N+5)<<1];
void add(int u,int v,int val,int w=0){
    tree[++cnt]={head[u],v,val,w},head[u]=cnt;
    tree[++cnt]={head[v],u,0,-w},head[v]=cnt;
}
ll dis[N<<1];
bool vis[N<<1];
queue<int> q;
bool SPFA(){
    for(int i=1;i<=T;++i) dis[i]=inf,vis[i]=false;
    dis[S]=0,q.push(S);
    while(!q.empty()){
        int x=q.front(); q.pop(),vis[x]=false;
        for(int i=head[x],y;i;i=tree[i].nxt) if(tree[i].val&&dis[y=tree[i].v]>dis[x]+tree[i].w){
            dis[y]=dis[x]+tree[i].w;
            if(!vis[y]) vis[y]=true,q.push(y);
        }
    }
    return dis[T]<inf;
}
int dinic(int x,int flow){
    if(x==T) return flow;
    int r=flow; vis[x]=true;
    for(int i=head[x],y;i;i=tree[i].nxt) if(dis[y=tree[i].v]==dis[x]+tree[i].w&&tree[i].val&&!vis[y]){
        int k=dinic(y,min(r,tree[i].val));
        if(!k) dis[y]=INF;
        tree[i].val-=k,tree[i^1].val+=k,r-=k,ans+=1ll*k*tree[i].w;
        if(!r) return flow;
    }
    vis[x]=false;
    return flow-r;
}
int main(){
    scanf("%d",&n),s=(n<<1)+1,t=s+1,S=t+1,T=S+1;
    for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) scanf("%d",&a[i][j]),(a[i][j])&&(--d[i],++d[j+n]);
    for(int i=1,v;i<=n;++i) for(int j=1;j<=n;++j)
        if(scanf("%d",&v),v>=0){
            if(a[i][j]) add(j+n,i,1,v); else add(i,j+n,1,v);
            edge[i][j]=cnt;
        }
    add(t,s,INF);
    for(int i=1,l,r,k;i<=n;++i){
        scanf("%d%d%d",&l,&r,&k),k=min(k,r-l);
        d[i]+=l,d[i+n]-=l,add(s,i,k),add(i+n,t,k),add(i+n,i,r-k-l);
    }
    for(int i=1;i<=t;++i){
        if(d[i]<0) add(i,T,-d[i]);
        if(d[i]>0) add(S,i,d[i]);
    }
    while(SPFA()) dinic(S,INF);
    printf("%lld\n",ans);
    for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) printf("%d%c",a[i][j]^tree[edge[i][j]].val,(j==n)?'\n':' ');

    return 0;
}

核心就是这里对于限制关系的表达,将\(|a-b|\leq c\)变成了\(d=\min(a,b)\)\(b\leq d+c\)\(a\leq d+c\),其它的都是基于这一步推出

另一个比较重要的就是,当我们基于某一种对于一类边的定义、建出了满足某一限制的边之后,再去做其他的限制时,不能再使用这一类边,因为会破坏这一限制,但是有一种例外,就是我们建出了这一类边的一个反边\(a\),然后再建出\(a\)的反边\(b\)\(b\)肯定是属于这一类边的),此时若满足只有\(a\)被选中了之后,才能选中\(b\),那么这个\(b\)也是允许存在的,也就是说,\(b\)\(a\)的反悔,此时因为选中\(a\)的时候是一定合法的,所以\(b\)将其反悔掉了过后整个局面也是合法的

posted @ 2024-02-27 21:42  LuoyuSitfitw  阅读(7)  评论(0编辑  收藏  举报