P1791 [国家集训队]人员雇佣(网络流)

解析

熟练和固化在有些时候是等价的。

一个看起来喜闻乐见的模型。
n 2 n^2 n2 信息量你在逗我…
结果是:点数 n 2 n^2 n2 TLE,边数 n 2 n^2 n2 AC。
一种之前所没有见过的打开方式。

还是考虑最小割模型,点 i i i 向原点连一条 A i A_i Ai 的边,断则表示雇佣。
不同的是,不再对每一对 ( i , j ) (i,j) (i,j) 开虚点,而是直接从 i i i 向汇点连一条 ∑ E i , j \sum E_{i,j} Ei,j 的边,表示 i i i 获得了所有的加成收益。
然后我们需要补偿,如果 j j j 没有雇佣,那么 i i i 不仅无法获得收益,还会付出代价,两相做差,应该连一条 ( j , i , 2 E i , j ) (j,i,2E_{i,j}) (j,i,2Ei,j) 的边。

即可。

思维打开!

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define ok debug("OK\n")
using namespace std;

const int N=5e6+100;
const ll inf=2e12;
inline ll read(){
  ll x(0),f(1);char c=getchar();
  while(!isdigit(c)) {if(c=='-')f=-1;c=getchar();}
  while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
  return x*f;
}


int n,m,id;

int s,t,tot;
struct node{
  int to,nxt;
  ll cap;
}p[N<<1];
int fi[N],cur[N],cnt;
inline void Addline(int x,int y,ll cap){
  p[++cnt]=(node){y,fi[x],cap};fi[x]=cnt;
}
inline void add(int x,int y,ll c){
  Addline(x,y,c);Addline(y,x,0);
}
int bel[N];
int q[N],st,ed;
int bfs(){
  memset(bel,0,sizeof(int)*(tot+1));
  bel[s]=1;
  q[st=ed=1]=s;
  while(st<=ed){
    int now=q[st++];
    for(int i=cur[now]=fi[now];~i;i=p[i].nxt){
      int to=p[i].to;
      if(!p[i].cap||bel[to]) continue;
      bel[to]=bel[now]+1;
      q[++ed]=to;
    }
  }
  return bel[t];
}
ll dfs(int x,ll lim){
  if(x==t||!lim) return lim;
  ll res(0);
  for(int &i=cur[x];~i;i=p[i].nxt){
    int to=p[i].to;
    if(bel[to]!=bel[x]+1) continue;
    ll add=dfs(to,min(lim,p[i].cap));
    res+=add;lim-=add;
    p[i].cap-=add;p[i^1].cap+=add;
    if(!lim) break;
  }
  if(!res) bel[x]=-1;
  return res;
}
ll dinic(){
  ll flow(0),tmp(0);
  while(bfs()){
    while((tmp=dfs(s,inf))) flow+=tmp;
  }
  return flow;
}

ll w[N];
signed main(){
  #ifndef ONLINE_JUDGE
  freopen("a.in","r",stdin);
  freopen("a.out","w",stdout);
  #endif
  memset(fi,-1,sizeof(fi));cnt=-1;
  tot=n=read();
  s=++tot;t=++tot;
  ll ans(0);
  for(int i=1;i<=n;i++){
    int x=read();
    add(s,i,x);
  }
  for(int i=1;i<=n;i++){
    for(int j=1;j<=n;j++){
      ll x=read();
      ans+=x;
      add(i,j,x*2);
      w[i]+=x;
    }
  }
  for(int i=1;i<=n;i++) add(i,t,w[i]);
  printf("%lld\n",ans-dinic());
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值