网络流6-10

6.最长不下降子序列问题

思路:

第一问:
直接LIS就能求出来最大长度ma

第二问:
因为每个数只能用一次,把一个点拆成入点和出点,入点连出点,容量为1,这样限制使用次数。
源点连接每个dp[i]=1 的点的入点(即序列起点),dp[i]=ma的点的出点连接汇点(即序列终点)
然后如果对于a[i]<=a[j]且dp[i]+1==dp[j]的两个数,i的出点连接j的入点,容量为1
源点连接每个入点容量为,每个出点连接汇点容量为1,
最大流即为能取出的长度为ma的子序列个数
!注意有一个坑点就是ma可能等于1,所以可能即连接源点也可能连接汇点,判断要用两个if而不能用ifelse

第三问:
把和a1与an有关的边权设置为inf就行了
直接再残余网络上加边,新跑出来的流是原来的最大流基础上的增加量,加上原来的最大流即为答案
!注意这里还有一个坑点就是如果ma==1,按第二问的解决方法应该也连接汇点,
但如果同时连接起点到汇点就无限了,
所以点1不能同时连接源点和汇点,同理点n也不能同时连接源点和汇点

code:

#include<bits/stdc++.h>
using namespace std;
const int maxm=1e3+5;
const int inf=1e9;
int head[maxm],nt[maxm<<5],to[maxm<<5],w[maxm<<5],cnt;
int d[maxm];
int m,n;
int s,t;
int maxflow;
//这题需要的数组:
int a[maxm];//存数
int dp[maxm];//dp数组
int ma;//最大长度
void init(){
    memset(head,-1,sizeof head);
    cnt=1;
}
void add(int x,int y,int z){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
}
bool bfs(){
    memset(d,0,sizeof d);
    queue<int>q;
    q.push(s);
    d[s]=1;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i!=-1;i=nt[i]){
            int v=to[i];
            if(!d[v]&&w[i]){
                d[v]=d[x]+1;
                if(v==t)return 1;
                q.push(v);
            }
        }
    }
    return 0;
}
int dfs(int x,int flow){
    if(x==t)return flow;
    int res=flow;
    for(int i=head[x];i!=-1;i=nt[i]){
        int v=to[i];
        if(w[i]&&d[v]==d[x]+1){
            int k=dfs(v,min(res,w[i]));
            if(k){
                w[i]-=k;
                w[i^1]+=k;
                res-=k;
            }else{
                d[v]=-1;
            }
            if(!res)break;
        }
    }
    return flow-res;
}
void dinic(){
    maxflow=0;
    while(bfs()){
        maxflow+=dfs(s,inf);
    }
}
signed main(){
    init();
    /*
        1.计算最长不下降子序列长度ma
        2.计算最多可以取出多少个长度为ma的不下降子序列
        3.如果允许多次使用x1和xn,最多可以取出多少长度为s的不下降子序列
    */
    scanf("%d",&n);
    for(int i=1;i<=n;i++){//input
        scanf("%d",&a[i]);
    }
    //下面处理第一问
    for(int i=1;i<=n;i++){//求最大不下降子序列长度
        dp[i]=1;
        for(int j=1;j<i;j++){
            if(a[j]<=a[i]){//不下降
                dp[i]=max(dp[i],dp[j]+1);
            }
        }
        ma=max(ma,dp[i]);
    }
    printf("%d\n",ma);//第一问
    //下面处理第二问
    /*
        每个点拆成入点i和出点i+n
        源点0
        汇点n+n+1
    */
    s=0,t=n+n+1;
    for(int i=1;i<=n;i++){//入点连接出点,容量为1,用来限制每个点只能用一次
        add(i,i+n,1);
        add(i+n,i,0);
    }
    for(int i=1;i<=n;i++){
        if(dp[i]==1){//源点连接起点入点
            add(s,i,1);
            add(i,s,0);
        }//这里不能是else if,因为可能ma==1,恶心人
        if(dp[i]==ma){//终点的出点连接汇点
            add(i+n,t,1);
            add(t,i+n,0);
        }
        for(int j=1;j<i;j++){
            if(dp[j]+1==dp[i]&&a[j]<=a[i]){
                add(j+n,i,1);
                add(i,j+n,0);
            }
        }
    }
    dinic();
    printf("%d\n",maxflow);//第二问
    //下面处理第三问
    int flow=maxflow;//记录一下之前的结果
    //处理x1和xn可以使用无限次
    add(1,1+n,inf);//修改入点到出点的边权
    add(n,n+n,inf);
    add(s,1,inf);//x1肯定是起点
    //注意不能判断dp[1]==ma的情况,如果这样就无限了
    if(dp[n]==ma){//如果xn是终点
        add(n+n,t,inf);
    }
    //
    dinic();
    flow+=maxflow;
    printf("%d\n",flow);//第三问
    return 0;
}
//https://www.luogu.com.cn/problem/P2766

7.试题库问题

思路:

二分图多重匹配问题
也基本是模板题

code:

#include<bits/stdc++.h>
using namespace std;
const int maxm=1e4+5;
const int inf=1e9;
int head[maxm],nt[maxm<<5],to[maxm<<5],w[maxm<<5],cnt;
int d[maxm];
int s,t;
int maxflow;
//这题要用到的:
int n,m,k;
vector<int>ans[25];
void init(){
    memset(head,-1,sizeof head);
    cnt=1;
}
void add(int x,int y,int z){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
}
bool bfs(){
    memset(d,0,sizeof d);
    queue<int>q;
    q.push(s);
    d[s]=1;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i!=-1;i=nt[i]){
            int v=to[i];
            if(!d[v]&&w[i]){
                d[v]=d[x]+1;
                if(v==t)return 1;
                q.push(v);
            }
        }
    }
    return 0;
}
int dfs(int x,int flow){
    if(x==t)return flow;
    int res=flow;
    for(int i=head[x];i!=-1;i=nt[i]){
        int v=to[i];
        if(w[i]&&d[v]==d[x]+1){
            int k=dfs(v,min(res,w[i]));
            if(k){
                w[i]-=k;
                w[i^1]+=k;
                res-=k;
            }else{
                d[v]=-1;
            }
            if(!res)break;
        }
    }
    return flow-res;
}
void dinic(){
    maxflow=0;
    while(bfs()){
        maxflow+=dfs(s,inf);
    }
}
signed main(){
    init();
    scanf("%d%d",&k,&n);
    s=0,t=k+n+1;
    for(int i=1;i<=n;i++){//每个物品只能用一次
        add(s,i,1);
        add(i,s,0);
    }
    for(int i=1;i<=k;i++){
        int x;
        scanf("%d",&x);
        m+=x;//m为需要的总数量
        //需要的每种类型连接汇点,容量为需要的数量
        add(i+n,t,x);
        add(t,i+n,0);
    }
    for(int i=1;i<=n;i++){
        int p;
        scanf("%d",&p);
        while(p--){
            int x;
            scanf("%d",&x);
            //物品i可以用于类型x
            add(i,x+n,1);
            add(x+n,i,0);
        }
    }
    dinic();
    if(maxflow!=m){//如果最大流不等于m,说明无解
        puts("No Solution!");
    }else{
        for(int x=1;x<=n;x++){
            for(int i=head[x];i!=-1;i=nt[i]){
                if(w[i^1]){//反向边有流说明是匹配边
                    if(to[i]==s||to[i]==t)continue;
                    ans[to[i]-n].push_back(x);
                    break;
                }
            }
        }
        for(int i=1;i<=k;i++){
            printf("%d:",i);
            for(int v:ans[i]){
                printf(" %d",v);
            }
            puts("");
        }
    }
    return 0;
}
//https://www.luogu.com.cn/problem/P2763

9.方格取数问题

思路:

二维坐标,相邻的不能选,所以可以把方格表进行黑白染色(按横纵坐标和的奇偶性),让黑白节点形成二分图。

建图:
源点连每一个黑点,容量为格子的值;每一个白点连汇点,容量为格子的值。
每一个黑色的点都连向与它相邻的白色的点(即互斥点相连),容量为inf。

最小割就是需要舍弃的最小和。
答案=总和-最小割

code:

#include <bits/stdc++.h>
using namespace std;
const int maxm=1e3+5;
const int inf=1e9;
int head[maxm],nt[maxm<<3],to[maxm<<3],w[maxm<<3],cnt;
int d[maxm];
int s,t;
int maxflow;
//这题要用到的:
int dir[4][2]={1,0,-1,0,0,1,0,-1};
int g[maxm][maxm];
int m,n;
int id(int i,int j){//二维转一维
    return (i-1)*n+j;
}
//
void init(){
    memset(head,-1,sizeof head);
    cnt=1;
}
void add(int x,int y,int z){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
}
bool bfs(){
    memset(d,0,sizeof d);
    queue<int>q;
    q.push(s);
    d[s]=1;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i!=-1;i=nt[i]){
            int v=to[i];
            if(!d[v]&&w[i]){
                d[v]=d[x]+1;
                if(v==t)return 1;
                q.push(v);
            }
        }
    }
    return 0;
}
int dfs(int x,int flow){
    if(x==t)return flow;
    int res=flow;
    for(int i=head[x];i!=-1;i=nt[i]){
        int v=to[i];
        if(w[i]&&d[v]==d[x]+1){
            int k=dfs(v,min(res,w[i]));
            if(k){
                w[i]-=k;
                w[i^1]+=k;
                res-=k;
            }else{
                d[v]=-1;
            }
            if(!res)break;
        }
    }
    return flow-res;
}
void dinic(){
    maxflow=0;
    while(bfs()){
        maxflow+=dfs(s,inf);
    }
}
signed main(){
    init();
    scanf("%d%d",&m,&n);
    s=0,t=m*n+1;
    int sum=0;
    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++){
            scanf("%d",&g[i][j]);
            sum+=g[i][j];
        }
    }
    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++){
            if((i+j)&1){//奇数连接源点
                add(s,id(i,j),g[i][j]);//权值为g[i][j]
                add(id(i,j),s,0);
                for(int k=0;k<4;k++){//向四个方向的排斥点连边
                    int x=i+dir[k][0];
                    int y=j+dir[k][1];
                    if(x<=0||x>m||y<=0||y>n)continue;
                    add(id(i,j),id(x,y),inf);
                    add(id(x,y),id(i,j),0);
                }
            }else{//偶数连接汇点
                add(id(i,j),t,g[i][j]);//权值为g[i][j]
                add(t,id(i,j),0);
            }
        }
    }
    dinic();
    printf("%d\n",sum-maxflow);
    return 0;
}
//https://www.luogu.com.cn/problem/P2774

10.餐巾计划问题(还没懂)

思路:

最小费用最大流

建图(摘自网络):
将一天拆成晚上和早上,每天晚上会收到脏餐巾(当天早上用完的餐巾,理解为从源点获得),每天早上又有干净的餐巾(购买、快洗店、慢洗店)。
1.从源点向每一天晚上连一条容量为当天所用餐巾x,费用为0的边,表示每天晚上从起点获得x条脏餐巾。
2.从每一天早上向汇点连一条容量为当天所用餐巾x,费用为0的边,每天白天,表示向汇点提供x条干净的餐巾,流满时表示第i天的餐巾够用 。
3.从每一天晚上向第二天晚上连一条流量为inf,费用为0的边,表示每天晚上可以将脏餐巾留到第二天晚上(注意不是早上,因为脏餐巾在早上不可以使用)。
4.从每一天晚上向这一天+快洗所用天数fa的那一天早上连一条流量为inf,费用为快洗所用钱数的边,表示每天晚上可以送去快洗部,在地i+fa天早上收到餐巾 。
5.同理,从每一天晚上向这一天+慢洗所用天数sl的那一天早上连一条流量为inf,费用为慢洗所用钱数的边,表示每天晚上可以送去慢洗部,在地i+sl天早上收到餐巾 。
6.从起点向每一天早上连一条流量为inf,费用为购买餐巾所用钱数的边,表示每天早上可以购买餐巾 。
注意3~6点需要做判断(即连向的边必须<=n)

ps:
费用和爆int,要开longlong

code:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxm=2e5+5;
const int inf=0x3f3f3f3f;
struct Node{
    int from,to,nt,cap,flow,cost;
}e[maxm];
int head[maxm];
int cnt;
int d[maxm];
int mark[maxm];
int pre[maxm];
int s,t;
ll mincost,maxflow;//总货物数量和总花费。
//这题要用到的:
int n;//天数
int p,fa,fac,sl,slc;
//p新餐巾费用,fa快洗天数,fac快洗费用,sl慢洗天数,slc慢洗费用
int a[maxm];//每天需用的餐巾数
//
void init(){
    cnt=-1;
    memset(head,-1,sizeof head);
}
void add(int a,int b,int c,int d){
    cnt++;
    e[cnt].nt=head[a];
    head[a]=cnt;
    e[cnt].from=a;
    e[cnt].to=b;
    e[cnt].cap=c;
    e[cnt].flow=0;
    e[cnt].cost=d;
    cnt++;
    e[cnt].nt=head[b];
    head[b]=cnt;
    e[cnt].from=b;
    e[cnt].to=a;
    e[cnt].cap=0;
    e[cnt].flow=0;
    e[cnt].cost=-d;
}
bool spfa(){
    memset(mark,0,sizeof mark);
    memset(d,inf,sizeof d);
    queue<int>q;
    q.push(s);
    d[s]=0;
    mark[s]=1;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        mark[x]=0;
        for(int i=head[x];i!=-1;i=e[i].nt){
            int v=e[i].to;
            if(e[i].cap>e[i].flow&&d[v]>d[x]+e[i].cost){
                d[v]=d[x]+e[i].cost;
                pre[v]=i;
                if(!mark[v]){
                    q.push(v);
                    mark[v]=1;
                }
            }
        }
    }
    return d[t]!=inf;
}
void ek(){
    mincost=maxflow=0;
    while(spfa()){
        int k=inf;
        for(int i=t;i!=s;i=e[pre[i]].from){
            k=min(k,e[pre[i]].cap-e[pre[i]].flow);
        }
        for(int i=t;i!=s;i=e[pre[i]].from){
            e[pre[i]].flow+=k;
            e[pre[i]^1].flow-=k;
        }
        maxflow+=k;
        mincost+=k*d[t];
    }
}
signed main(){
    init();
    scanf("%d",&n);
    s=0,t=n+n+1;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        add(s,i,a[i],0);//每天晚上从起点获得a[i]条脏餐巾
        add(i+n,t,a[i],0);//每天白天向汇点提供a[x]条干净餐巾
    }
    scanf("%d%d%d%d%d",&p,&fa,&fac,&sl,&slc);
//p新餐巾费用,fa快洗天数,fac快洗费用,sl慢洗天数,slc慢洗费用
    for(int i=1;i<=n;i++){
        if(i+1<=n){
            add(i,i+1,inf,0);//每天晚上可以把脏餐巾留到第二天
        }
        if(i+fa<=n){
            add(i,i+n+fa,inf,fac);//快洗
        }
        if(i+sl<=n){
            add(i,i+n+sl,inf,slc);//慢洗
        }
        add(s,i+n,inf,p);//直接买新的
    }
    ek();
    printf("%lld\n",mincost);
    return 0;
}
//https://www.luogu.com.cn/problem/P1251/
发布了378 篇原创文章 · 获赞 29 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_44178736/article/details/103357870