- P3376 【模板】网络最大流
- P4722 【模板】最大流 加强版 / 预流推进
- P2065 [TJOI2011]卡片
- P2763 试题库问题
- P2472 [SCOI2007]蜥蜴
- P2754 [CTSC1999]家园 / 星际转移问题
- P2765 魔术球问题
- P2766 最长不下降子序列问题
- P4174 [NOI2006]最大获利
- P2805 [NOI2009]植物大战僵尸
- P3749 [六省联考2017]寿司餐厅
1.P3376 【模板】网络最大流
#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn];
int cnt=1,n,m,s,t;
struct edge{
int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].w=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
bool bfs(){
memset(dep,0,sizeof(dep));
for(int i=1;i<=n;i++){//如果要拆点或者其他的一定记得把范围开大点!!!
cur[i]=head[i];
}
dep[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!dep[v]&&e[i].w){
dep[v]=dep[u]+1;
q.push(v);
if(v==t)return true;
}
}
}
return false;
}
int dfs(int u,int now){
if(u==t||now==0){
return now;
}
int flow=0,rlow=0;
for(int i=cur[u];i;i=e[i].nex){
int v=e[i].v;
if(e[i].w&&dep[v]==dep[u]+1){
if(rlow=dfs(v,min(now,e[i].w))){
flow+=rlow;
now-=rlow;
e[i].w-=rlow;
e[i^1].w+=rlow;
if(now==0)return flow;
}
}
}
if(!flow)dep[u]=-1;
return flow;
}
int dinic(){
int maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
return maxflow;
}
int main(){
scanf("%d%d%d%d",&n,&m,&s,&t);
int u,v,w;
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
add_edge(u,v,w);
}
printf("%d",dinic());
return 0;
}
2.P4722 【模板】最大流 加强版 / 预流推进
鬼畜的HLPP,之后补板子
3.P2065 [TJOI2011]卡片
这题是个蓝题我觉得是不合理的,比后面的紫题没得简单
暴力
g
c
d
gcd
gcd会直接
T
L
E
TLE
TLE飞
先跑一遍欧拉筛
对于红蓝卡片的每一个数,枚举与这些数不互质的数
i
i
i,红牌向
i
i
i连边,蓝牌被
i
i
i连边,然后
从源点向红牌连边,蓝牌向汇点连边,求出最大流即可
#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e4+5,maxm=1e4+5;
const int MAX=0x3f3f3f3f;
int cur[maxn],head[maxn],dep[maxn];
int a[maxn],b[maxn];
int cnt=1,maxflow=0,n,m,s,t;
struct edge{
int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].w=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
bool bfs(){
memset(dep,0,sizeof(dep));
memcpy(cur,head,sizeof(head));
dep[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!dep[v]&&e[i].w){
dep[v]=dep[u]+1;
q.push(v);
if(v==t)return true;
}
}
}
return false;
}
int dfs(int u,int now){
if(u==t||now==0){
return now;
}
int flow=0,rlow=0;
for(int i=cur[u];i;i=e[i].nex){
int v=e[i].v;
if(e[i].w&&dep[v]==dep[u]+1){
if(rlow=dfs(v,min(now,e[i].w))){
flow+=rlow;
now-=rlow;
e[i].w-=rlow;
e[i^1].w+=rlow;
if(now==0)return flow;
}
}
}
if(!flow)dep[u]=-1;
return flow;
}
int dinic(){
while(bfs()){
maxflow+=dfs(s,inf);
}
return maxflow;
}
inline void init(){
memset(head,0,sizeof head);
maxflow=0;
cnt=1;
}
int k,c;
const int maxnn=1e7+10;
int prime[2000005];//存素数
bool sf[maxnn];//判断这个数是否是素数
int num=0;
void sushu(){
memset(sf,true,sizeof(sf));
for(int i=2;i<=maxnn;i++){
if(sf[i]) prime[++num]=i;
for(int j=1;j<=num;j++){
if(i*prime[j]>maxnn) break;
sf[i*prime[j]]=false;
if(i%prime[j]==0) break;
}
}
sf[1]=false;
sf[0]=false;
}
inline void link1(int x,int id){
for(int i=1;i<=num&&prime[i]<=x;i++){
if(x%prime[i]==0){
add_edge(id,i+n+m,MAX);
t=max(t,i+n+m);
while(x%prime[i]==0)x/=prime[i];
}
}
}
inline void link2(int y,int id){
for(int i=1;i<=num&&prime[i]<=y;i++){
if(y%prime[i]==0){
add_edge(i+n+m,id+n,MAX);
t=max(t,i+n+m);
while(y%prime[i]==0)y/=prime[i];
}
}
}
int main(){
int tt;
cin>>tt;
sushu();
while(tt--){
scanf("%d%d",&n,&m);
init();
t=0;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
link1(a[i],i);
}
for(int i=1;i<=m;i++){
scanf("%d",&b[i]);
link2(b[i],i);
}
s=0;
t+=1;
for(int i=1;i<=n;i++){
add_edge(0,i,1);
}
for(int i=1;i<=m;i++){
add_edge(i+n,t,1);
}
printf("%d\n",dinic());
}
}
4.P2763 试题库问题
直接匹配或者最大流都可, d f s dfs dfs的过程中记录下流向各种类型的出发点
#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn],to[maxn],flag[maxn];
int cnt=1,n,m,s,t,k;
vector<int>jl[maxn];
struct edge{
int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].w=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
bool bfs(){
memset(dep,0,sizeof(dep));
for(int i=0;i<=t+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!!
cur[i]=head[i];
}
dep[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!dep[v]&&e[i].w){
dep[v]=dep[u]+1;
q.push(v);
if(v==t)return true;
}
}
}
return false;
}
int dfs(int u,int now){
if(u==t||now==0){
return now;
}
int flow=0,rlow=0;
for(int i=cur[u];i;i=e[i].nex){
int v=e[i].v;
if(e[i].w&&dep[v]==dep[u]+1){
if(rlow=dfs(v,min(now,e[i].w))){
flow+=rlow;
now-=rlow;
e[i].w-=rlow;
e[i^1].w+=rlow;
if(v>=n+1&&v<=n+k){
jl[v-n].push_back(u);
}
if(now==0)return flow;
}
}
}
if(!flow)dep[u]=-1;
return flow;
}
int dinic(){
int maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
return maxflow;
}
int main(){
cin>>k>>n;
s=0,t=n+k+1;
int x;
for(int i=1;i<=k;i++){
scanf("%d",&x);
add_edge(n+i,t,x);
m+=x;
}
int p;
for(int i=1;i<=n;i++){
scanf("%d",&p);
add_edge(0,i,1);
for(int j=1;j<=p;j++){
scanf("%d",&x);
add_edge(i,n+x,1);
}
}
int ss=dinic();
if(ss<m){
printf("No Solution!");
}
else{
for(int i=1;i<=k;i++){
printf("%d:",i);
for(int j=0;j<jl[i].size();j++){
printf(" %d",jl[i][j]);
}
printf("\n");
}
}
return 0;
}
5.P2472 [SCOI2007]蜥蜴
看到点限制的题第一时间想到拆点!拆点!拆点!重要的事情说三遍
枚举每一个能跳出去的点连向汇点
枚举每一个可到达的点周围可以跳到的可到达点
最大流
#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1005,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn],to[maxn],flag[maxn];
int cnt=1,n,m,s,t,k,tot,l,r,d;
char a[maxn][maxn],h[maxn][maxn];
struct edge{
int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].w=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
bool bfs(){
memset(dep,0,sizeof(dep));
for(int i=0;i<=t+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!!
cur[i]=head[i];
}
dep[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!dep[v]&&e[i].w){
dep[v]=dep[u]+1;
q.push(v);
if(v==t)return true;
}
}
}
return false;
}
int dfs(int u,int now){
if(u==t||now==0){
return now;
}
int flow=0,rlow=0;
for(int i=cur[u];i;i=e[i].nex){
int v=e[i].v;
if(e[i].w&&dep[v]==dep[u]+1){
if(rlow=dfs(v,min(now,e[i].w))){
flow+=rlow;
now-=rlow;
e[i].w-=rlow;
e[i^1].w+=rlow;
if(now==0)return flow;
}
}
}
if(!flow)dep[u]=-1;
return flow;
}
int dinic(){
int maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
return maxflow;
}
inline int js(int i,int j){
return (i-1)*r+j;
}
inline int juli(int x1,int y1,int x2,int y2){
return (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);
}
int main(){
cin>>l>>r>>d;
n=r*l;
s=0,t=l*r*2+1;
for(int i=1;i<=l;i++){
for(int j=1;j<=r;j++){
cin>>h[i][j];
if(h[i][j]-'0'>0)add_edge(js(i,j),n+js(i,j),h[i][j]-'0');
}
}
int watot=0;
for(int i=1;i<=l;i++){
for(int j=1;j<=r;j++){
cin>>a[i][j];
if(a[i][j]=='L'){
watot++;
add_edge(s,js(i,j),1);
}
}
}
for(int i=1;i<=l;i++){
for(int j=1;j<=r;j++){
int x1=i,y1=j;
if(x1-d<1||y1-d<1||x1+d>l||y1+d>r){
add_edge(js(x1,y1)+n,t,inf);
}
for(int x2=1;x2<=l;x2++){
for(int y2=1;y2<=r;y2++){
if(juli(x1,y1,x2,y2)<=d*d){
add_edge(js(x1,y1)+n,js(x2,y2),inf);
}
}
}
}
}
printf("%d",watot-dinic());
return 0;
}
6.P2754 [CTSC1999]家园 / 星际转移问题
这个题很是骚
分层图网络流
每过一天建一套站点,借用一下luogu大佬题解的图片
进行解释一下,样例的走法是:
d
1
d1
d1:到
1
1
1号点
d
2
d2
d2:到
2
2
2号点
d
3
d3
d3:不走
d
4
d4
d4:不走
d
5
d5
d5:走到月球
因为飞船是不会停下来的,有的时候就必须在原地等飞船走到这个路线
飞船的路线之间建立容量为 c a p cap cap的边,前一天的这个点和后一天的这个点建立容量为 i n f inf inf的边(因为所有人都可以继续在这个站点等飞船过来)
然后不断跑最大流,直到 s u m > = k sum>=k sum>=k
设定一个较大的天数,如果超过了就break,代表不能将所有人都运输到月球
#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn],cap[maxn],cz[maxn],mp[205][205];
int cnt=1,n,m,s,t,k;
struct edge{
int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].w=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
bool bfs(){
memset(dep,0,sizeof(dep));
memcpy(cur,head,sizeof head);
dep[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!dep[v]&&e[i].w){
dep[v]=dep[u]+1;
q.push(v);
if(v==t)return true;
}
}
}
return false;
}
int dfs(int u,int now){
if(u==t||now==0){
return now;
}
int flow=0,rlow=0;
for(int i=cur[u];i;i=e[i].nex){
int v=e[i].v;
if(e[i].w&&dep[v]==dep[u]+1){
if(rlow=dfs(v,min(now,e[i].w))){
flow+=rlow;
now-=rlow;
e[i].w-=rlow;
e[i^1].w+=rlow;
if(now==0)return flow;
}
}
}
if(!flow)dep[u]=-1;
return flow;
}
int dinic(){
int maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
return maxflow;
}
int main(){
scanf("%d%d%d",&n,&m,&k);
int u,v,w;
n+=2;
for(int i=1;i<=m;i++){
scanf("%d%d",&cap[i],&cz[i]);
for(int j=0;j<cz[i];j++){
scanf("%d",&mp[i][j]);
mp[i][j]+=2;
}
}
int day=0;
s=0,t=9999;
int sum=0;
while(day<500){
add_edge(s,day*n+2,inf);
add_edge(day*n+1,t,inf);
if(day){
for(int i=1;i<=n;i++){
add_edge((day-1)*n+i,day*n+i,inf);
}
for(int i=1;i<=m;i++){
int x=mp[i][(day-1)%cz[i]];
int y=mp[i][day%cz[i]];
add_edge((day-1)*n+x,day*n+y,cap[i]);
}
}
sum+=dinic();
if(sum>=k)break;
day++;
}
if(day==500)printf("0");
else printf("%d",day);
return 0;
}
7.P2765 魔术球问题
做这题之前我先做了最小路径覆盖问题
然后读懂题目会发现,其实这好像是一样的意思?只不过这题是枚举 n n n,求枚举到 n n n的时候最小路径覆盖数
把图做出来是这个样子的,其实就转化成了最小路径覆盖问题了
#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn],to[maxn],flag[maxn],vis[maxn];
int cnt=1,n,m,s=100001,t=100002,now,p;
struct edge{
int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].w=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
bool bfs(){
memset(dep,0,sizeof(dep));
memcpy(cur,head,sizeof head);
dep[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!dep[v]&&e[i].w){
dep[v]=dep[u]+1;
q.push(v);
if(v==t)return true;
}
}
}
return false;
}
int dfs(int u,int now){
if(u==t||now==0){
return now;
}
int flow=0,rlow=0;
for(int i=cur[u];i;i=e[i].nex){
int v=e[i].v;
if(e[i].w&&dep[v]==dep[u]+1){
if(rlow=dfs(v,min(now,e[i].w))){
flow+=rlow;
now-=rlow;
e[i].w-=rlow;
e[i^1].w+=rlow;
if(u!=t)to[u>>1]=v>>1;
if(now==0)return flow;
}
}
}
if(!flow)dep[u]=-1;
return flow;
}
int dinic(){
int maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
return maxflow;
}
int zz[maxn];
int main(){
scanf("%d",&n);
int p=0;
while(p<=n) {
now++;
add_edge(s,now*2,1);
add_edge(now*2+1,t,1);
// printf("s %d\n",now);
// printf("%d+%d t\n",now,now);
for(int i=sqrt(now)+1;i*i<now*2;i++) {
add_edge((i*i-now)*2,now*2+1,1);
// printf("%d %d+%d\n",(i*i-now),now,now);
}
int flow = dinic();
if(!flow){
zz[++p] = now;
}
}
cout<<now-1<<endl;
for(int i=1;i<=n;i++){
int u=zz[i];
printf("%d",u);
u=to[u];
while(u&&u!=t>>1){
printf(" %d",u);
u=to[u];
}
cout<<endl;
}
return 0;
}
8.P2766 最长不下降子序列问题
- 计算其最长不下降子序列的长度 s s s。
- 如果每个元素只允许使用一次,计算从给定的序列中最多可取出多少个长度为 s s s 的不下降子序列。
- 如果允许在取出的序列中多次使用 x 1 x_1 x1 和 x n x_n xn(其他元素仍然只允许使用一次),则从给定序列中最多可取出多少个不同的长度为 s s s 的不下降子序列。
令 a 1 , a 2 , … , a s a_1, a_2, \ldots, a_s a1,a2,…,as 为构造 S S S 时所使用的下标, b 1 , b 2 , … , b s b_1, b_2, \ldots, b_s b1,b2,…,bs 为构造 T T T 时所使用的下标。且 ∀ i ∈ [ 1 , s − 1 ] \forall i \in [1,s-1] ∀i∈[1,s−1],都有 a i < a i + 1 a_i < a_{i+1} ai<ai+1, b i < b i + 1 b_i <b_{i+1} bi<bi+1。则 S S S 和 T T T 不同,当且仅当 ∃ i ∈ [ 1 , s ] \exists i \in [1,s] ∃i∈[1,s],使得 a i ≠ b i a_i \neq b_i ai=bi。
第一问:求 L I S LIS LIS
拆点
第二问:源点连一条到 d p [ i ] = = 1 dp[i]==1 dp[i]==1点的边, d [ j ] = = s d[j]==s d[j]==s连一条到汇点的边,对于i枚举每一个j, j < i j<i j<i如果 a [ j ] < = a [ i ] a[j]<=a[i] a[j]<=a[i]&& d p [ j ] + 1 = = d p [ i ] dp[j]+1==dp[i] dp[j]+1==dp[i], j j j的出点向 i i i的入点连一条边(以上容量均为1)
第三问:把 1 1 1和 n n n点的拆点容量设为 i n f inf inf
最后,要特判!!!不然 n = 1 n=1 n=1就会炸掉
#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn];
int cnt=1,n,m,s,t,ss,ll;
struct edge{
int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].w=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
bool bfs(){
memset(dep,0,sizeof(dep));
memcpy(cur,head,sizeof head);
dep[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!dep[v]&&e[i].w){
dep[v]=dep[u]+1;
q.push(v);
if(v==t)return true;
}
}
}
return false;
}
int dfs(int u,int now){
if(u==t||now==0){
return now;
}
int flow=0,rlow=0;
for(int i=cur[u];i;i=e[i].nex){
int v=e[i].v;
if(e[i].w&&dep[v]==dep[u]+1){
if(rlow=dfs(v,min(now,e[i].w))){
flow+=rlow;
now-=rlow;
e[i].w-=rlow;
e[i^1].w+=rlow;
if(now==0)return flow;
}
}
}
if(!flow)dep[u]=-1;
return flow;
}
int dinic(){
int maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
return maxflow;
}
int a[maxn],dp[maxn];
inline void init(){
add_edge(s,1,inf);
add_edge(1,1+n,inf);
if(dp[n]==ll){
add_edge(n+n,t,inf);
add_edge(n,n+n,inf);
}
}
int main(){
scanf("%d",&n);
int u,v,w;
s=0,t=n+n+1;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
dp[i]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if(a[j]<=a[i]){
dp[i]=max(dp[i],dp[j]+1);
}
}
}
for(int i=1;i<=n;i++){
ll=max(ll,dp[i]);
// cout<<dp[i]<<" ";
}
cout<<ll<<endl;
for(int i=1;i<=n;i++){
add_edge(i,i+n,1);
if(dp[i]==1){
add_edge(s,i,1);
}
if(dp[i]==ll){
add_edge(i+n,t,1);
}
for(int j=1;j<i;j++){
if(a[j]<=a[i]&&dp[j]+1==dp[i]){
add_edge(j+n,i,1);
}
}
}
int ff=dinic();
printf("%d\n",ff);
init();
ff+=dinic();
printf("%d\n",ff<1e9?ff:1);
return 0;
}
xx.最大权闭合子图:P4174 [NOI2006]最大获利
引用一下大佬对最大权闭合子图的定义:
什么是最大权闭合子图:
先解释一下有向图的闭合图:闭合图内任意点的任意后继也一定还在闭合图中。
物理意义:事物间依赖关系:一个事件要发生,它需要的所有前提也都一定要发生。
最大权闭合图:点权之和最大的闭合图
最大权闭合图构图方法:
1.增加源s汇t
2.源s连接原图的正权点,容量为相应点权
3.原图的负权点连接汇t,容量为相应点权的相反数
4.原图边的容量为正无限。
最大权闭合图 解决:
闭合图V的权为正权点总和减去对应割的容量
当割最小时,闭合图权最大
画个图:(作图网站炸了,用luogu的图)
我最这个模型的理解就是,和源点相连的点全都是收益,和汇点相连的点为代价,割点与源点相连的边代表选择这个收益,割掉与汇点相连的边代表选择付出这个代价
最后 s u m − sum- sum−最小割即为总最大总收益
我们考虑一下这个图,一开始当然是想所有的收益都选,然后我们发现选择4和5的话,代价会比收益更大,所以我们不选,即割掉源点与4,5相连的边,就不用付出4,5到汇点的代价了,选择1,2,3的收益,就要选择付出1,2,3到汇点的代价,割之。
最终我们舍弃了4,5这两个代价比收益还要大的点,并付出了要选择1,2,3的收益需要付出的代价
这就是
s
u
m
−
sum-
sum−最大流 的来由。
#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn];
int cnt=1,n,m,s,t;
struct edge{
int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].w=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
bool bfs(){
memset(dep,0,sizeof(dep));
for(int i=0;i<=t+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!!
cur[i]=head[i];
}
dep[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!dep[v]&&e[i].w){
dep[v]=dep[u]+1;
q.push(v);
if(v==t)return true;
}
}
}
return false;
}
int dfs(int u,int now){
if(u==t||now==0){
return now;
}
int flow=0,rlow=0;
for(int i=cur[u];i;i=e[i].nex){
int v=e[i].v;
if(e[i].w&&dep[v]==dep[u]+1){
if(rlow=dfs(v,min(now,e[i].w))){
flow+=rlow;
now-=rlow;
e[i].w-=rlow;
e[i^1].w+=rlow;
if(now==0)return flow;
}
}
}
if(!flow)dep[u]=-1;
return flow;
}
int dinic(){
int maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
return maxflow;
}
int main(){
scanf("%d%d",&n,&m);
s=0,t=n+m+1;
int u,v,w;
int x;
for(int i=1;i<=n;i++){
scanf("%d",&x);
add_edge(i+m,t,x);
}
int sum=0;
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
add_edge(s,i,w);
sum+=w;
add_edge(i,m+u,inf);
add_edge(i,m+v,inf);
}
printf("%d",sum-dinic());
return 0;
}
9.P2805 [NOI2009]植物大战僵尸
其实我觉得这题和后面一题都应该放在最小割里面
看到有收益有代价,并且有选点有先决条件,这时候想到:
最大权闭合子图
有个特殊问题是如果两个植物他可以互保,那他们以及后面的所有植物都无法被吃,所以我们需要通过
拓扑排序来排除这些点
每个点向被他保护的点连边,进行拓扑排序
模拟一下就能知道,因为拓扑排序过程中要入度为0才会被加入队列,所以入度 > = 2 >=2 >=2的点以及后面的点就不会被取出,我们把所有取出的点用mark标记
如果收益为正,从源点向它连边,容量为负,从它向汇点连边
每一个点对它后面的点连一条容量为 i n f inf inf的边
求出 s u m − sum- sum−最小割即为答案
#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn],degree[maxn];
int cnt=1,n,m,s,t,cc=2;
struct edge{
int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].w=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
bool bfs(){
memset(dep,0,sizeof(dep));
for(int i=0;i<=t+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!!
cur[i]=head[i];
}
dep[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!dep[v]&&e[i].w){
dep[v]=dep[u]+1;
q.push(v);
if(v==t)return true;
}
}
}
return false;
}
int dfs(int u,int now){
if(u==t||now==0){
return now;
}
int flow=0,rlow=0;
for(int i=cur[u];i;i=e[i].nex){
int v=e[i].v;
if(e[i].w&&dep[v]==dep[u]+1){
if(rlow=dfs(v,min(now,e[i].w))){
flow+=rlow;
now-=rlow;
e[i].w-=rlow;
e[i^1].w+=rlow;
if(now==0)return flow;
}
}
}
if(!flow)dep[u]=-1;
return flow;
}
int dinic(){
int maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
return maxflow;
}
inline int id(int x,int y){
return (x-1)*m+y;
}
int mark[maxn];
vector<int>g[maxn];
int f[maxn],sum;
inline void topo(){
queue<int>q;
for(int i=1;i<=m*n;i++){
if(!degree[i])q.push(i);
}
while(!q.empty()){
int u=q.front();
q.pop();
mark[u]=1;
for(auto v:g[u]){
if(!(--degree[v])){
q.push(v);
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
int u,v,w,x,y,num;
s=n*m+1,t=n*m+2;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d%d",&f[id(i,j)],&num);
if(j!=m){
g[id(i,j+1)].push_back(id(i,j));
degree[id(i,j)]++;
}
while(num--){
scanf("%d%d",&x,&y);
g[id(i,j)].push_back(id(x+1,y+1));
degree[id(x+1,y+1)]++;
}
}
}
topo();
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(mark[id(i,j)]){
int c=f[id(i,j)];
if(c>0){
sum+=c;
add_edge(s,id(i,j),c);
}
else if(c<0)add_edge(id(i,j),t,-c);
for(auto v:g[id(i,j)]){
if(mark[v]){
add_edge(v,id(i,j),inf);
}
}
}
}
}
printf("%d",sum-dinic());
return 0;
}
10.P3749 [六省联考2017]寿司餐厅
写到这里我感觉需要把最大权闭合子图的模板填上来一下
每一个收益视为一个点,而必须选择被这个区间包含的区间
每个点向种类连边容量为 i n f inf inf
每个点向他区间包含的点连边:
a d d add add_ e d g e ( i d [ i ] [ j ] , i d [ i ] [ j − 1 ] , i n f ) edge(id[i][j],id[i][j-1],inf) edge(id[i][j],id[i][j−1],inf);
a d d add add_ e d g e ( i d [ i ] [ j ] , i d [ i + 1 ] [ j ] , i n f ) edge(id[i][j],id[i+1][j],inf) edge(id[i][j],id[i+1][j],inf);
并向源点or汇点连边:
i
f
(
c
>
0
)
if(c>0)
if(c>0){
a
d
d
add
add
e
d
g
e
(
s
,
i
d
[
i
]
[
j
]
,
c
)
edge(s,id[i][j],c)
edge(s,id[i][j],c);
s
u
m
+
=
c
sum+=c
sum+=c;
}
e
l
s
e
i
f
(
c
<
0
)
else if(c<0)
elseif(c<0){
a
d
d
add
add
e
d
g
e
(
i
d
[
i
]
[
j
]
,
t
,
−
c
)
edge(id[i][j],t,-c)
edge(id[i][j],t,−c);
}
每个 i = = j i==j i==j的点向种类点连边:
a d d add add_ e d g e ( i d [ i ] [ j ] , c c + a [ i ] , i n f ) edge(id[i][j],cc+a[i],inf) edge(id[i][j],cc+a[i],inf);
最终我们还要处理费用,如果选择过这个种类,则向连接一条 i ∗ i i*i i∗i的边
每次选择这个种类的代价在上面加边的过程中减去即可
做出图来是这个鬼畜的样子(因为我没把种类点分开)
完整代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int inf =0x3f3f3f3f,maxn=10005,maxm=1e6+5;
int cur[maxn],head[maxn],dep[maxn];
int cnt=1,n,m,s,t,cc;
struct edge{
int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].w=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
bool bfs(){
memset(dep,0,sizeof(dep));
for(int i=0;i<=cc+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!!
cur[i]=head[i];
}
dep[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!dep[v]&&e[i].w){
dep[v]=dep[u]+1;
q.push(v);
if(v==t)return true;
}
}
}
return false;
}
int dfs(int u,int now){
if(u==t||now==0){
return now;
}
int flow=0,rlow=0;
for(int i=cur[u];i;i=e[i].nex){
int v=e[i].v;
if(e[i].w&&dep[v]==dep[u]+1){
if(rlow=dfs(v,min(now,e[i].w))){
flow+=rlow;
now-=rlow;
e[i].w-=rlow;
e[i^1].w+=rlow;
if(now==0)return flow;
}
}
}
if(!flow)dep[u]=-1;
return flow;
}
int dinic(){
int maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
return maxflow;
}
int a[105],f[105][105],id[105][105],sum=0;
signed main(){
cc=2;
s=1,t=2;
int maxx=0;
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
maxx=max(maxx,a[i]);
}
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
scanf("%lld",&f[i][j]);
id[i][j]=++cc;
}
}
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
int c=f[i][j];
if(i==j){
if(m)add_edge(id[i][j],cc+a[i],inf);
// printf("%d %d+%d inf\n",id[i][j],cc,a[i]);
c-=a[i];
}
else{
add_edge(id[i][j],id[i][j-1],inf);
add_edge(id[i][j],id[i+1][j],inf);
// printf("%d %d inf\n",id[i][j],id[i][j-1]);
// printf("%d %d inf\n",id[i][j],id[i+1][j]);
}
if(c>0){
add_edge(s,id[i][j],c);
sum+=c;
// printf("s %d %d\n",id[i][j],c);
}
else if(c<0){
add_edge(id[i][j],t,-c);
// printf("%d t %d\n",id[i][j],-c);
}
}
}
if(m){
for(int i=1;i<=maxx;i++){
add_edge(++cc,t,i*i);
// printf("%d t %d\n",cc,i*i);
}
}
printf("%lld",sum-dinic());
return 0;
}