《网络流24题》(23/24)

主要是想把这些题干掉(除了某道机器人神题)


餐巾计划问题(费用流)

Luogu P1251 难度:省选/NOI-


标准最小费用最大流题

为明确表述,使用\(cost1/cost2/cost3\)表示购买花费、快洗花费、慢洗花费

使用\(day1/day2\)表示快洗天数和慢洗天数

建图应该主要表示的是“晚上->早上”的这一过程

所以对每天进行拆点,晚上置于源点侧,早上置于汇点侧

根据题意,发现旧餐巾的输出可以分为以下三种

1、不洗,转移到第二天晚上

2、快洗,转移到\(day1\)天后的早上

3、慢洗,转移到\(day2\)天后的早上

新餐巾转为旧餐巾,可以视为是从源点获取的

每天早上到晚上的使用过程可以表示为早上的点向汇点提供的流量

故建图方式如下:

1、源点向第\(i\)天晚上连边,流量\(r[i]\),费用\(0\),表示每天用完多了\(r[i]\)的旧餐巾

2、第\(i\)天早上向汇点连边,流量\(r[i]\),费用\(0\),表示这一天只需要使用\(r[i]\)条新餐巾

3、每天晚上向下一天晚上连边,流量为\(\infty\),费用\(0\),表示前一天用剩下的旧餐巾不处理,留到下一天(或者之后)一起处理

4、源点向每天早上连边,流量\(\infty\),费用\(cost1\),表示早上可以直接购买新餐巾

5、晚上向\(day1\)天后的早上连边,流量\(\infty\),费用\(cost2\),表示快洗

6、晚上向\(day2\)天后的早上连边,流量\(\infty\),费用\(cost3\),表示慢洗

建图跑普通最小费用最大流即可


Submit: 2020-08-01 18:25

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const ll LINF=0x3f3f3f3f3f3f3f3f;
const int maxn=4050;

struct MCMF {
    struct E {
        int from, to;
        ll cap, v;
        E() {}
        E(int f, int t, ll cap, ll v) : from(f), to(t), cap(cap), v(v) {}
    };
    int n, m, s, t;
    vector<E> edges;
    vector<int> G[maxn];
    bool inq[maxn];
    ll dis[maxn], pre[maxn], a[maxn];
    void init(int _n, int _s, int _t) {
        n = _n; s = _s; t = _t;
        for (int i = 0; i <= n; i++)
            G[i].clear();
        edges.clear();
        m = 0;
    }
    void add(int from, int to, ll cap, ll cost) {
        edges.emplace_back(from, to, cap, cost);
        edges.emplace_back(to, from, 0, -cost);
        G[from].push_back(m++);
        G[to].push_back(m++);
    }
    bool spfa() {
        for (int i = 0; i <= n; i++) {
            dis[i] = LINF;
            pre[i] = -1;
            inq[i] = false;
        }
        dis[s] = 0, a[s] = LINF, inq[s] = true;
        queue<int> Q; Q.push(s);
        while (!Q.empty()) {
            int u = Q.front(); Q.pop();
            inq[u] = false;
            for (int& idx: G[u]) {
                E& e = edges[idx];
                if (e.cap && dis[e.to] > dis[u] + e.v) {
                    dis[e.to] = dis[u] + e.v;
                    pre[e.to] = idx;
                    a[e.to] = min(a[u], e.cap);
                    if (!inq[e.to]) {
                        inq[e.to] = true;
                        Q.push(e.to);
                    }
                }
            }
        }
        return pre[t] != -1;
    }
    ll solve() {
        ll flow = 0, cost = 0;
        while (spfa()) {
            flow += a[t];
            cost += a[t] * dis[t];
            int u = t;
            while (u != s) {
                edges[pre[u]].cap -= a[t];
                edges[pre[u] ^ 1].cap += a[t];
                u = edges[pre[u]].from;
            }
        }
        return cost;
    }
}flow;

int N,r[2050];
int cost1,cost2,cost3,day1,day2;

void solve()
{
    cin>>N;
    for(int i=1;i<=N;i++)
        cin>>r[i];
    cin>>cost1>>day1>>cost2>>day2>>cost3;
    flow.init(2*N+2,2*N+1,2*N+2);
    for(int i=1;i<=N;i++)
    {
        flow.add(flow.s,i+N,r[i],0);           //源点向每天晚上连边
        flow.add(i,flow.t,r[i],0);             //每天早上向汇点连边
        if(i!=N)
            flow.add(i+N,i+N+1,LINF,0);        //每天晚上向下一天晚上连边
        flow.add(flow.s,i,LINF,cost1);         //源点向每天早上连边
        if(i+day1<=N)
            flow.add(i+N,i+day1,LINF,cost2);   //晚上向快洗f天后的早上连边
        if(i+day2<=N)
            flow.add(i+N,i+day2,LINF,cost3);   //晚上向慢洗s天后的早上连边
    }
    cout<<flow.solve()<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



家园 / 星际转移问题(最大流、并查集)

Luogu P2754 难度:省选/NOI-


将地球、月球以及太空站按照时间拆点

源点连接时间为\(1\)时的地球,时间为\(1\)时的月球连接汇点,流量为\(\infty\)

当时间为\(t\ (t\geq2)\)时,对于每个太空站以及地球,连接一条从\(t-1\)时刻的点到\(t\)时刻的点的边,流量为\(\infty\),表示这一秒内在这上面的人可以继续呆着不走

对于月球,则是连接一条从\(t\)时刻的点到\(t-1\)时刻的点(反向),流量为\(\infty\),以保证图的连通性(不管什么时候到月球都能够走到汇点)

对于每个太空船,连接一条\(t-1\)时刻所在位置到\(t\)时刻所在位置的边,容量为这艘太空船的容量,表示乘船转移的人数

每次加上当次跑出的最大流,并不断像上述那样增边,直到最大流之和大于等于地球上的总人数时退出


Submit: 2020-08-04 11:02

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=66666,maxm=2333333;

struct edge{
    int u,v,cap,flow;
    edge(){}
    edge(int u,int v,int cap,int flow):u(u),v(v),cap(cap),flow(flow){}
}eg[maxm<<1];
int tot,s,t,dis[maxn<<1],cur[maxn<<1];
vector<int> tab[maxn<<1];

void addedge(int u,int v,int cap){
    tab[u].push_back(tot);
    eg[tot++]=edge(u,v,cap,0);
    tab[v].push_back(tot);
    eg[tot++]=edge(v,u,0,0);
}
int bfs(){
    queue<int> q;
    q.push(s);
    memset(dis,INF,sizeof dis);
    dis[s]=0;
    while(!q.empty()){
        int h=q.front(),i;
        q.pop();
        for(i=0;i<tab[h].size();i++){
            edge &e=eg[tab[h][i]];
            if(e.cap>e.flow&&dis[e.v]==INF){
                dis[e.v]=dis[h]+1;
                q.push(e.v);
            }
        }
    }
    return dis[t]<INF;
}
int dfs(int x,int maxflow){
    if(x==t|maxflow==0)
        return maxflow;
    int flow=0,i,f;
    for(i=cur[x];i<tab[x].size();i++){
        cur[x]=i;
        edge &e=eg[tab[x][i]];
        if(dis[e.v]==dis[x]+1&&(f=dfs(e.v,min(maxflow,e.cap-e.flow)))>0){
            e.flow+=f;
            eg[tab[x][i]^1].flow-=f;
            flow+=f;
            maxflow-=f;
            if(maxflow==0)
                break;
        }
    }
    return flow;
}
int dinic(){
    int flow=0;
    while(bfs()){
        memset(cur,0,sizeof(cur));
        flow+=dfs(s,INF);
    }
    return flow;
}

int gp[20];
int fnd(int p)
{
    return p==gp[p]?p:gp[p]=fnd(gp[p]);
}

struct node
{
    int peo,cnt;
    vector<int> sta;
}ship[25];
int n,m,k;

void solve()
{
    int d;
    cin>>n>>m>>k;
    tot=0,s=1,t=2;
    for(int i=1;i<=n;i++)
        gp[i]=i;
    for(int i=1;i<=m;i++)
    {
        cin>>ship[i].peo>>ship[i].cnt;
        for(int j=1;j<=ship[i].cnt;j++)
        {
            cin>>d;
            d++; //太空站范围[2,n+1]
            if(d==0)
                d=n+2; //汇点置n+2
            ship[i].sta.push_back(d);
        }
        for(int j=1;j<ship[i].cnt;j++)
            gp[fnd(ship[i].sta[j-1])]=gp[fnd(ship[i].sta[j])];
    }
    if(fnd(gp[1])!=fnd(gp[n+2])) //地球与月球不连通
    {
        cout<<"0\n";
        return;
    }
    int ans=0,sumflow=0;
    addedge(s,3,INF);
    addedge(n+4,t,INF);
    while(true)
    {
        ans++;
        int base=(n+2)*ans+2; //可以当作是上一时刻的月球编号
        for(int i=1;i<=n+1;i++)
            addedge(base+i-(n+2),base+i,INF); //正向传递
        addedge(base+n+2,base,INF); //终点反向
        for(int i=1;i<=m;i++) //飞船由上一时刻的位置连接到下一时刻所在位置
            addedge(base-(n+2)+ship[i].sta[(ans-1)%ship[i].cnt],base+ship[i].sta[ans%ship[i].cnt],ship[i].peo);
        sumflow+=dinic();
        if(sumflow>=k) //如果人数达到要求即可退出
            break;
    }
    cout<<ans<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



飞行员配对方案问题(最大流)

Luogu P2756 难度:提高+/省选-


模板二分图最大匹配,可直接跑最大流


Submit: 2020-07-30 11:42

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;

const int maxn=666,maxm=233333;
struct edge{
    int u,v,cap,flow;
    edge(){}
    edge(int u,int v,int cap,int flow):u(u),v(v),cap(cap),flow(flow){}
}eg[maxm<<1];
int tot,s,t,dis[maxn<<1],cur[maxn<<1];
vector<int> tab[maxn<<1];

void init(int n,int s_,int t_){
    while(n)tab[n--].clear();
    tot=0,s=s_,t=t_;
}
void addedge(int u,int v,int cap){
    tab[u].push_back(tot);
    eg[tot++]=edge(u,v,cap,0);
    tab[v].push_back(tot);
    eg[tot++]=edge(v,u,0,0);
}
int bfs(){
    queue<int> q;
    q.push(s);
    memset(dis,INF,sizeof dis);
    dis[s]=0;
    while(!q.empty()){
        int h=q.front(),i;
        q.pop();
        for(i=0;i<tab[h].size();i++){
            edge &e=eg[tab[h][i]];
            if(e.cap>e.flow&&dis[e.v]==INF){
                dis[e.v]=dis[h]+1;
                q.push(e.v);
            }
        }
    }
    return dis[t]<INF;
}
int dfs(int x,int maxflow){
    if(x==t|maxflow==0)
        return maxflow;
    int flow=0,i,f;
    for(i=cur[x];i<tab[x].size();i++){
        cur[x]=i;
        edge &e=eg[tab[x][i]];
        if(dis[e.v]==dis[x]+1&&(f=dfs(e.v,min(maxflow,e.cap-e.flow)))>0){
            e.flow+=f;
            eg[tab[x][i]^1].flow-=f;
            flow+=f;
            maxflow-=f;
            if(maxflow==0)
                break;
        }
    }
    return flow;
}
int dinic(){
    int flow=0;
    while(bfs()){
        memset(cur,0,sizeof(cur));
        flow+=dfs(s,INF);
    }
    return flow;
}

void solve()
{
    int n,m,a,b;
    cin>>m>>n;
    init(t,n+1,n+2);
    while(cin>>a>>b)
    {
        if(a==-1&&b==-1)
            break;
        addedge(a,b,INF);
    }
    for(int i=1;i<=m;i++)
        addedge(s,i,1);
    for(int i=m+1;i<=n;i++)
        addedge(i,t,1);
    cout<<dinic()<<'\n';
    for(int i=0;i<tot;i+=2)
        if(eg[i].u!=s&&eg[i].v!=t)
        {
            if(eg[i^1].flow!=0) //反向边流量不为0说明选择了该边
                cout<<eg[i].u<<' '<<eg[i].v<<'\n';
        }
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



软件补丁问题(最短路、状态压缩)

Luogu P2761 难度:提高+/省选-


bug只有\(20\)种,所以可以状态压缩为二进制成\(2^{20}\)种状态表示

其后以起点为均为\(1\)的状态(\(2^n-1\)),终点为\(0\)跑最短路即可求出答案

精髓在于位运算


Submit: 2020-07-30 18:23

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxd=1<<21;

struct patch
{
    int b1,b2,f1,f2,t;
}ar[110];

int n,m,dis[maxd+10];
bool inq[maxd+10];

void spfa(int st) //从初始状态跑最短路
{
    memset(dis,INF,sizeof dis);
    dis[st]=0;
    queue<int> q;
    q.push(st);
    while(!q.empty())
    {
        int bef=q.front();
        q.pop();
        for(int i=1;i<=m;i++) //枚举去尝试使用各种补丁
        {
            if((bef&ar[i].b1)==ar[i].b1&&(bef&ar[i].b2)==0) //满足所述的补丁使用条件时
            {
                int aft=(bef|ar[i].f1|ar[i].f2)^ar[i].f1; //获取使用后状态
                if(dis[aft]>dis[bef]+ar[i].t)
                {
                    dis[aft]=dis[bef]+ar[i].t;
                    if(!inq[aft])
                    {
                        q.push(aft);
                        inq[aft]=true;
                    }
                }
            }
        }
        inq[bef]=false;
    }
}

void solve()
{
    string str;
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        cin>>ar[i].t;
        cin>>str;
        for(int j=0;j<n;j++)
        {
            if(str[j]=='+')
                ar[i].b1|=(1<<j);
            else if(str[j]=='-')
                ar[i].b2|=(1<<j);
        }
        cin>>str;
        for(int j=0;j<n;j++)
        {
            if(str[j]=='-')
                ar[i].f1|=(1<<j);
            else if(str[j]=='+')
                ar[i].f2|=(1<<j);
        }
    }
    spfa((1<<n)-1); //初始状态(n个1)
    if(dis[0]==INF)
        dis[0]=0;
    cout<<dis[0]<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



太空飞行计划问题(最小割)

Luogu P2762 难度:省选/NOI-


本题要将实验仪器分为两类点

源点向实验\(i\)连边,流量为\(p[i]\),表示赞助商支付的费用

仪器\(i\)向汇点连边,流量为\(c[i]\),表示使用这类仪器所需要的费用

实验向它所需要用到的仪器连边,流量为\(\infty\)

先将所有赞助商的费用求和\(sum\),然后跑最小割

在跑完最小割后对于整张图可以分成两种状态:与源点连接的边被割去,或与汇点连接的边被割去

与源点连接的边被割去,表示做这个实验会亏本(不如不做),所以将赞助商的费用拿来

与汇点连接的边被割去,表示做这个实验,最小割为花费的仪器费用

所以最终净收益为\(sum-maxflow\)

而对于最后选择的实验与仪器

实际上在跑\(dinic\)算法的最后一遍\(bfs\)中就已经将点进行分层

与源点相连的就是选中的了,直接判断\(dep\)即可(代码中初始化为\(INF\),所以不是\(INF\)的就是与源点相连的)


Submit: 2020-08-02 19:32

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=123,maxm=12345;

string str;
stringstream scin;
void readline()
{
    getline(cin,str);
    scin.clear();
    scin<<str;
}

struct edge{
    int u,v,cap,flow;
    edge(){}
    edge(int u,int v,int cap,int flow):u(u),v(v),cap(cap),flow(flow){}
}eg[maxm<<1];
int tot,s,t,dep[maxn<<1],cur[maxn<<1];
vector<int> tab[maxn<<1];

void init(int n,int s_,int t_){
    while(n)tab[n--].clear();
    tot=0,s=s_,t=t_;
}
void addedge(int u,int v,int cap){
    tab[u].push_back(tot);
    eg[tot++]=edge(u,v,cap,0);
    tab[v].push_back(tot);
    eg[tot++]=edge(v,u,0,0);
}
int bfs(){
    queue<int> q;
    q.push(s);
    memset(dep,INF,sizeof dep);
    dep[s]=0;
    while(!q.empty()){
        int h=q.front(),i;
        q.pop();
        for(i=0;i<tab[h].size();i++){
            edge &e=eg[tab[h][i]];
            if(e.cap>e.flow&&dep[e.v]==INF){
                dep[e.v]=dep[h]+1;
                q.push(e.v);
            }
        }
    }
    return dep[t]<INF;
}
int dfs(int x,int maxflow){
    if(x==t|maxflow==0)
        return maxflow;
    int flow=0,i,f;
    for(i=cur[x];i<tab[x].size();i++){
        cur[x]=i;
        edge &e=eg[tab[x][i]];
        if(dep[e.v]==dep[x]+1&&(f=dfs(e.v,min(maxflow,e.cap-e.flow)))>0){
            e.flow+=f;
            eg[tab[x][i]^1].flow-=f;
            flow+=f;
            maxflow-=f;
            if(maxflow==0)
                break;
        }
    }
    return flow;
}
int dinic(){
    int flow=0;
    while(bfs()){
        memset(cur,0,sizeof(cur));
        flow+=dfs(s,INF);
    }
    return flow;
}

int n,m,p[55],cost[55];
vector<int> tools[55];

void solve()
{
    readline();
    scin>>m>>n;
    init(n+m+2,n+m+1,n+m+2);
    int d,sum=0;
    for(int i=1;i<=m;i++)
    {
        readline();
        scin>>p[i];
        sum+=p[i];
        addedge(s,i,p[i]);
        while(scin>>d)
        {
            tools[i].push_back(d);
            addedge(i,m+d,INF);
        }
    }
    readline();
    for(int i=1;i<=n;i++)
    {
        scin>>cost[i];
        addedge(m+i,t,cost[i]);
    }
    sum-=dinic();
    for(int i=1;i<=m;i++)
        if(dep[i]!=INF)
            cout<<i<<' ';
    cout<<'\n';
    for(int i=1;i<=n;i++)
        if(dep[m+i]!=INF)
            cout<<i<<' ';
    cout<<'\n';
    cout<<sum<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



试题库问题(最大流)

Luogu P2763 难度:省选/NOI-


明显题目与类型之间应该存在一条流量为\(1\)的边表示选择或不选择

类型\(i\)与源点/汇点间应该存在一条流量为\(cnt[i]\)表示选择的题目数量

故建图方法之一如下:

  • 源点\(s\)向类型\(i\)连一条流量为\(cnt[i]\)的边

  • 类型\(i\)向可归类的题目\(j\)连一条流量为\(1\)的边

  • 题目\(j\)向汇点\(t\)连一条流量为\(1\)的边

跑最大流,如果最大流\(flow_{max}\)小于需要选择的题目数量总和\(sum_{cnt}\),则说明不存在任何一种归类方案

否则遍历每条正向边,如果边的类型是“从类型连向题目”的边,且此时该边的流量不为\(0\),则说明类型\(i\)选择了题目\(j\),储存输出即可


Submit: 2020-07-31 12:48

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=2222,maxm=100000;

struct edge{
    int u,v,cap,flow;
    edge(){}
    edge(int u,int v,int cap,int flow):u(u),v(v),cap(cap),flow(flow){}
}eg[maxm<<1];
int tot,s,t,dis[maxn<<1],cur[maxn<<1];
vector<int> tab[maxn<<1];

void init(int n,int s_,int t_){
    while(n)tab[n--].clear();
    tot=0,s=s_,t=t_;
}
void addedge(int u,int v,int cap){
    tab[u].push_back(tot);
    eg[tot++]=edge(u,v,cap,0);
    tab[v].push_back(tot);
    eg[tot++]=edge(v,u,0,0);
}
int bfs(){
    queue<int> q;
    q.push(s);
    memset(dis,INF,sizeof dis);
    dis[s]=0;
    while(!q.empty()){
        int h=q.front(),i;
        q.pop();
        for(i=0;i<tab[h].size();i++){
            edge &e=eg[tab[h][i]];
            if(e.cap>e.flow&&dis[e.v]==INF){
                dis[e.v]=dis[h]+1;
                q.push(e.v);
            }
        }
    }
    return dis[t]<INF;
}
int dfs(int x,int maxflow){
    if(x==t|maxflow==0)
        return maxflow;
    int flow=0,i,f;
    for(i=cur[x];i<tab[x].size();i++){
        cur[x]=i;
        edge &e=eg[tab[x][i]];
        if(dis[e.v]==dis[x]+1&&(f=dfs(e.v,min(maxflow,e.cap-e.flow)))>0){
            e.flow+=f;
            eg[tab[x][i]^1].flow-=f;
            flow+=f;
            maxflow-=f;
            if(maxflow==0)
                break;
        }
    }
    return flow;
}
int dinic(){
    int flow=0;
    while(bfs()){
        memset(cur,0,sizeof(cur));
        flow+=dfs(s,INF);
    }
    return flow;
}

int cnt[25];
void solve()
{
    int k,n,p,d,sum=0;
    cin>>k>>n;
    init(k+n+2,k+n+1,k+n+2);
    for(int i=1;i<=k;i++)
    {
        cin>>cnt[i];
        sum+=cnt[i];
        addedge(s,i+n,cnt[i]);
    }
    for(int i=1;i<=n;i++)
    {
        addedge(i,t,1);
        cin>>p;
        while(p--)
        {
            cin>>d;
            addedge(d+n,i,1);
        }
    }
    int mxflow=dinic();
    if(mxflow!=sum)
    {
        cout<<"No Solution!\n";
        return;
    }
    vector<int> ans[25];
    for(int i=0;i<tot;i+=2)
    {
        if(eg[i].flow&&eg[i].v<=n)
            ans[eg[i].u-n].push_back(eg[i].v);
    }
    for(int i=1;i<=k;i++)
    {
        cout<<i<<':';
        for(int it:ans[i])
            cout<<' '<<it;
        cout<<'\n';
    }
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



最小路径覆盖问题(最大流、并查集)

Luogu P2764 难度:省选/NOI-


将原图的点进行分层

源点连接第一层所有点,流量为\(1\)

第二层所有点连接汇点,流量为\(1\)

如果原图中某点对\((u,v)\)之间存在着边,则第一层的\(u\)向第二层的\(v\)连边,流量为\(1\)

直接跑一边最大流后,所有流量为\(0\)的边均为选择的边(即最大路径覆盖的边)

遍历所有正向边,寻找所有第一层连向第二层的且流量为\(0\)的边,利用并查集将其流出点合并到流入点的集合中

最后遍历所有点编号,寻找所有集合的父节点(\(i=group[i]\)),个数即为最小路径覆盖的路径数

从每个这样的父节点开始,通过深度优先搜索将所有第一层连向第二层的且流量为\(0\)的边一层层输出即可

(可以证明,\(n-maxflow=\)最小路径覆盖的路径数)


Submit: 2020-08-02 10:23

#include<bits/stdc++.h>
using namespace std;
const int maxn=333,maxm=23333;
const int INF=0x3f3f3f3f;

struct Edge
{
    int u,v,w;
}eg[maxm];
int head[maxn],nxt[maxm],dep[maxn],tot,s,t,mxn;

void addedge(int u,int v,int w)
{
    nxt[tot]=head[u];
    head[u]=tot;
    eg[tot++]=(Edge){u,v,w};
    nxt[tot]=head[v];
    head[v]=tot;
    eg[tot++]=(Edge){v,u,0};
}
void init(int n,int _s,int _t)
{
    mxn=n,tot=0,s=_s,t=_t;
    while(n>=0)
        head[n--]=-1;
}
int bfs()
{
    for(int i=1;i<=mxn;i++)
        dep[i]=-1;
    dep[s]=0;
    queue<int> q;
    q.push(s);
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=head[x];i!=-1;i=nxt[i])
        {
            if(eg[i].w&&dep[eg[i].v]==-1)
            {
                dep[eg[i].v]=dep[x]+1;
                q.push(eg[i].v);
            }
        }
    }
    return (dep[t]>=0);
}
int dfs(int p,int mn)
{
    if(p==t)
		return mn;
    int d,now=0;
    for(int i=head[p];i!=-1;i=nxt[i])
    {
        if(eg[i].w&&dep[eg[i].v]==dep[p]+1)
        {
            d=dfs(eg[i].v,min(mn-now,eg[i].w));
            now+=d;
            eg[i].w-=d;
            eg[i^1].w+=d;
            if(now==mn)break;
        }
    }
    return now;
}
int dinic()
{
    int res=0;
    while(bfs())
        res+=dfs(s,INF);
    return res;
}

int n,m;
int gp[maxn];

int fnd(int p)
{
    return p==gp[p]?p:gp[p]=fnd(gp[p]);
}

void PRINT(int p)
{
    cout<<p<<' ';
    for(int i=head[p];i!=-1;i=nxt[i])
        if(eg[i].w==0&&eg[i].v>n&&eg[i].v<s)
            PRINT(eg[i].v-n);
}

void solve()
{
    int u,v;
    cin>>n>>m;
    init(2*n+2,2*n+1,2*n+2);
    for(int i=1;i<=n;i++)
    {
        addedge(s,i,1);
        addedge(i+n,t,1);
    }
    for(int i=1;i<=m;i++)
    {
        cin>>u>>v;
        addedge(u,v+n,1);
    }
    dinic();
    for(int i=1;i<=n;i++)
        gp[i]=i;
    for(int i=0;i<tot;i+=2)
        if(eg[i].u<=n&&eg[i].v>n&&eg[i].v<s&&eg[i].w==0)
            gp[fnd(eg[i].v-n)]=fnd(eg[i].u); //将流量为0的合法的边合并,流出点向流入点合并
    int cnt=0;
    for(int i=1;i<=n;i++)
        if(fnd(i)==i) //从所有独立路径的起点开始
        {
            PRINT(i);
            cnt++;
            cout<<'\n';
        }
    cout<<cnt<<'\n'; //n-maxflow
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



魔术球问题(贪心)

Luogu P2765 难度:省选/NOI-


这题可以直接暴力+贪心

每次for一遍看看能不能放就完事儿了

这题要求相邻和为平方数,而平方数与递增数之间的特点导致可以这样直接贪心

但如果像某道汉诺塔题,要求相邻和为质数,那就不能这样贪心了

代码实现的话,简单STL应用题吧


Submit: 2020-07-31 14:53

#include<bits/stdc++.h>
using namespace std;

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    
    int n,m=0,id=1;
    set<int> st;
    stack<int> sk[60];
    
    cin>>n;
    
    for(int i=1;i<=100;i++)
        st.insert(i*i);
    
    for(;;id++)
    {
        bool flag=false;
        for(int i=1;i<=m;i++)
        {
            if(st.find(sk[i].top()+id)!=st.end())
            {
                sk[i].push(id);
                flag=true;
                break;
            }
        }
        if(flag)
            continue;
        if(m==n)
            break;
        sk[++m].push(id);
    }
    
    cout<<id-1<<'\n';
    
    stack<int> skk; //栈倒序输出
    for(int i=1;i<=n;i++)
    {
        while(!sk[i].empty())
        {
            skk.push(sk[i].top());
            sk[i].pop();
        }
        while(!skk.empty())
        {
            cout<<skk.top()<<' ';
            skk.pop();
        }
        cout<<'\n';
    }
    
    return 0;
}



最长不下降子序列问题(最大流、DP)

Luogu P2766 难度:省选/NOI-


本题共有三问

第一问为基础动态规划

第二问需要建立网络流

首先将每个数字拆成两个点,分置左右两侧

从所有不下降子序列的第一个数字入手,连一条从源点来的边,流量为\(1\)

从所有不下降子序列的最后一个数字入手,连一条去汇点的边,流量为\(1\)

所有不下降子序列相邻点之间也用流量为\(1\)的边连接

其后直接跑最大流,就能得出第二问答案

第三问只是在第二问的基础上加了两个条件,\(x_1\)可以无限用,所以连一条从源点来的流量为\(\infty\)的边,再连一条到自己拆出去的点的流量为\(\infty\)的边

\(x_n\)不一定是最长不下降子序列的终点,所以对其做出限制,只有在它是最长不下降子序列的终点时,才建立一条从自己连向拆点后的点的流量为\(\infty\)的边,再建一条拆后的点流向汇点的流量为\(\infty\)的边

在第二问的基础上再跑一次最大流,答案加上第二问的答案即是第三问的答案


Submit: 2020-07-31 18:02

#include<bits/stdc++.h>
using namespace std;
const int maxn=666,maxm=23333;
const int INF=0x3f3f3f3f;

struct edge{
    int u,v,cap,flow;
    edge(){}
    edge(int u,int v,int cap,int flow):u(u),v(v),cap(cap),flow(flow){}
}eg[maxm<<1];
int tot,s,t,dis[maxn<<1],cur[maxn<<1];
vector<int> tab[maxn<<1];

void init(int n,int s_,int t_){
    while(n)tab[n--].clear();
    tot=0,s=s_,t=t_;
}
void addedge(int u,int v,int cap){
    tab[u].push_back(tot);
    eg[tot++]=edge(u,v,cap,0);
    tab[v].push_back(tot);
    eg[tot++]=edge(v,u,0,0);
}
int bfs(){
    queue<int> q;
    q.push(s);
    memset(dis,INF,sizeof dis);
    dis[s]=0;
    while(!q.empty()){
        int h=q.front(),i;
        q.pop();
        for(i=0;i<tab[h].size();i++){
            edge &e=eg[tab[h][i]];
            if(e.cap>e.flow&&dis[e.v]==INF){
                dis[e.v]=dis[h]+1;
                q.push(e.v);
            }
        }
    }
    return dis[t]<INF;
}
int dfs(int x,int maxflow){
    if(x==t|maxflow==0)
        return maxflow;
    int flow=0,i,f;
    for(i=cur[x];i<tab[x].size();i++){
        cur[x]=i;
        edge &e=eg[tab[x][i]];
        if(dis[e.v]==dis[x]+1&&(f=dfs(e.v,min(maxflow,e.cap-e.flow)))>0){
            e.flow+=f;
            eg[tab[x][i]^1].flow-=f;
            flow+=f;
            maxflow-=f;
            if(maxflow==0)
                break;
        }
    }
    return flow;
}
int dinic(){
    int flow=0;
    while(bfs()){
        memset(cur,0,sizeof(cur));
        flow+=dfs(s,INF);
    }
    return flow;
}

int n,ar[555],dp[555];
int ans1=0,ans2=0,ans3=0;

void dealQue1()
{
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<i;j++)
            if(ar[j]<=ar[i])
                dp[i]=max(dp[i],dp[j]);
        dp[i]++;
        ans1=max(ans1,dp[i]);
    }
    cout<<ans1<<'\n';
}

void dealQue2()
{
    init(2*n+2,2*n+1,2*n+2);
    for(int i=1;i<=n;i++)
    {
        addedge(i,i+n,1);
        if(dp[i]==1) //为前i个数中最小的数,即不存在前节点
            addedge(s,i,1);
        if(dp[i]==ans1) //最长上升子序列的最后一个数值,即不再有后节点
            addedge(i,t,1);
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<i;j++)
            if(ar[j]<=ar[i]&&dp[j]+1==dp[i]) //同一上升子序列的相邻节点
                addedge(j+n,i,1);
    ans2=dinic();
    cout<<ans2<<'\n';
}

void dealQue3()
{
    ans3=ans2;
    addedge(s,1,INF);
    addedge(1,n+1,INF);
    if(dp[n]==ans1&&n!=1)
    {
        addedge(n,n+n,INF);
        addedge(n+n,t,INF);
    }
    ans3+=dinic();
    cout<<ans3<<'\n';
}

void solve()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>ar[i];
    dealQue1();
    dealQue2();
    dealQue3();
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



航空路线问题(费用流)

Luogu P2770 难度:省选/NOI-


由于经度互不相同,给定的城市顺序自西向东

所以可以直接用map顺序给定id,根据id大小判断城市的相对方向

又因为要在除了起点外各个点不重复经过且尽可能经过更多的点

所以明显是最大费用最大流问题(使用最小费用最大流,下文中所有费用应在代码中取反)

由于每个点只能经过一次(除了第一个点)

所以对每个点进行拆点,分为入点与出点,入点与出点之间连接一条流量为\(1\),费用为\(1\)的边(除了\(1\)号点与\(n\)号点)

对于\(1\)号点与\(n\)号点,应该在入点与出点之间连接一条流量为\(2\),费用为\(1\)的边()

由于可以自西向东一次,再自东向西,所以问题可以转化成求\(1\)号点到\(n\)号点的互不相交的两条边(点数和最大)

如果两个点之间存在直连航线,则从西边的点的出点东边的点的入点连接一条流量为\(1\)费用为\(0\)的边

跑费用流,求出最大流最大费用

判断最大流,如果最大流量为\(0\),说明\(1\)号点与\(n\)号点无法互相到达,输出无解

如果最大流量为\(1\),说明\(1\)号点到\(n\)号点后无法再返回,所以只能判断下是否存在一条航班直接连接\(1\)号点与\(n\)号点,存在的话直接输出\(1-n-1\)这一条即可,否则无解

如果最大流量为\(2\),则利用残量网络输出两条路径即可(一条顺序输出一条倒序输出)

\(1\)号点的出点开始dfs即可,第一遍需要开个vis数组记录某个点是否已经访问过(防止第二遍搜索重复搜索到)


Submit: 2020-08-05 11:27

#include<bits/stdc++.h>
using namespace std;
const int maxn=233;

struct MCMF {
    struct E {
        int from, to, cap, v;
        E() {}
        E(int f, int t, int cap, int v) : from(f), to(t), cap(cap), v(v) {}
    };
    int n, m, s, t;
    vector<E> edges;
    vector<int> G[maxn];
    bool inq[maxn];
    int dis[maxn], pre[maxn], a[maxn];
    void init(int _n, int _s, int _t) {
        n = _n; s = _s; t = _t;
        for (int i = 0; i <= n; i++)
            G[i].clear();
        edges.clear();
        m = 0;
    }
    void add(int from, int to, int cap, int cost) {
        edges.emplace_back(from, to, cap, cost);
        edges.emplace_back(to, from, 0, -cost);
        G[from].push_back(m++);
        G[to].push_back(m++);
    }
    bool spfa() {
        for (int i = 0; i <= n; i++) {
            dis[i] = 1e9;
            pre[i] = -1;
            inq[i] = false;
        }
        dis[s] = 0, a[s] = 1e9, inq[s] = true;
        queue<int> Q; Q.push(s);
        while (!Q.empty()) {
            int u = Q.front(); Q.pop();
            inq[u] = false;
            for (int& idx: G[u]) {
                E& e = edges[idx];
                if (e.cap && dis[e.to] > dis[u] + e.v) {
                    dis[e.to] = dis[u] + e.v;
                    pre[e.to] = idx;
                    a[e.to] = min(a[u], e.cap);
                    if (!inq[e.to]) {
                        inq[e.to] = true;
                        Q.push(e.to);
                    }
                }
            }
        }
        return pre[t] != -1;
    }
    pair<int,int> solve() {
        int flow = 0, cost = 0;
        while (spfa()) {
            flow += a[t];
            cost += a[t] * dis[t];
            int u = t;
            while (u != s) {
                edges[pre[u]].cap -= a[t];
                edges[pre[u] ^ 1].cap += a[t];
                u = edges[pre[u]].from;
            }
        }
        return pair<int,int>(flow,cost);
    }
}f;

int n,m,cnt=0;
map<string,int> mp;
string str[111];
bool vis[111];

void dfs1(int p)
{
    vis[p-n]=true;
    cout<<str[p-n]<<'\n';
    for(int id:f.G[p])
    {
        if(f.edges[id].to<=n&&f.edges[id].cap==0)
        {
            dfs1(f.edges[id].to+n);
            break;
        }
    }
}

void dfs2(int p)
{
    for(int id:f.G[p])
    {
        if(f.edges[id].to<=n&&f.edges[id].cap==0&&!vis[f.edges[id].to])
        {
            dfs2(f.edges[id].to+n);
            break;
        }
    }
    cout<<str[p-n]<<'\n';
}

void solve()
{
    cin>>n>>m;
    f.init(n*2,1,n*2);
    for(int i=1;i<=n;i++)
    {
        cin>>str[i];
        mp[str[i]]=++cnt;
        if(i==1||i==n)
            f.add(i,i+n,2,-1);
        else
            f.add(i,i+n,1,-1);
    }
    string str1,str2;
    bool flag=false;
    int a,b;
    for(int i=1;i<=m;i++)
    {
        cin>>str1>>str2;
        a=mp[str1];
        b=mp[str2];
        if(a>b)
            swap(a,b);
        if(a==1&&b==n)
            flag=true;
        f.add(a+n,b,1,0);
    }
    pair<int,int> result=f.solve();
    if(result.first==2)
    {
        cout<<-result.second-2<<'\n';
        dfs1(1+n);
        dfs2(1+n);
    }
    else if(result.first==1&&flag)
        cout<<"2\n"<<str[1]<<'\n'<<str[n]<<'\n'<<str[1]<<'\n';
    else
        cout<<"No Solution!\n";
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



方格取数问题(最小割)

Luogu P2774 难度:省选/NOI-


反向考虑该题,我们使用最小割来计算出最少应该去掉多少数字使得留下的数字满足题意且和最大

首先可以想到,行列和奇偶性相同的位置一定不是相邻的,所以可以根据行列和的奇偶性将整张图的数字分成两个集合

源点连行列和为奇数的点,流量为该点对应的数字

行列和为偶数的点连汇点,流量为该点对应的数字

又因为相邻的点奇偶性一定不同(差值为\(1\)

所以当我们找到一个行列和为奇数的点时,可以让其向相邻的点连一条流量为\(\infty\)的边

最后跑一遍最小割,得到的结果即为最少应该去掉的数字之和

取原来全图的数字之和减去便是本题答案


Submit: 2020-07-31 18:52

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=22222,maxm=666666;

struct edge{
    int u,v,cap,flow;
    edge(){}
    edge(int u,int v,int cap,int flow):u(u),v(v),cap(cap),flow(flow){}
}eg[maxm<<1];
int tot,s,t,dis[maxn<<1],cur[maxn<<1];
vector<int> tab[maxn<<1];

void init(int n,int s_,int t_){
    while(n)tab[n--].clear();
    tot=0,s=s_,t=t_;
}
void addedge(int u,int v,int cap){
    tab[u].push_back(tot);
    eg[tot++]=edge(u,v,cap,0);
    tab[v].push_back(tot);
    eg[tot++]=edge(v,u,0,0);
}
int bfs(){
    queue<int> q;
    q.push(s);
    memset(dis,INF,sizeof dis);
    dis[s]=0;
    while(!q.empty()){
        int h=q.front(),i;
        q.pop();
        for(i=0;i<tab[h].size();i++){
            edge &e=eg[tab[h][i]];
            if(e.cap>e.flow&&dis[e.v]==INF){
                dis[e.v]=dis[h]+1;
                q.push(e.v);
            }
        }
    }
    return dis[t]<INF;
}
int dfs(int x,int maxflow){
    if(x==t|maxflow==0)
        return maxflow;
    int flow=0,i,f;
    for(i=cur[x];i<tab[x].size();i++){
        cur[x]=i;
        edge &e=eg[tab[x][i]];
        if(dis[e.v]==dis[x]+1&&(f=dfs(e.v,min(maxflow,e.cap-e.flow)))>0){
            e.flow+=f;
            eg[tab[x][i]^1].flow-=f;
            flow+=f;
            maxflow-=f;
            if(maxflow==0)
                break;
        }
    }
    return flow;
}
int dinic(){
    int flow=0;
    while(bfs()){
        memset(cur,0,sizeof(cur));
        flow+=dfs(s,INF);
    }
    return flow;
}

int n,m;

inline int id(int x,int y)
{
    return (x-1)*m+y;
}

void solve()
{
    int d,sum=0;
    cin>>n>>m;
    init(0,n*m+1,n*m+2);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            cin>>d;
            sum+=d;
            if((i+j)&1)
            {
                addedge(s,id(i,j),d);
                if(i!=1)
                    addedge(id(i,j),id(i-1,j),INF);
                if(j!=1)
                    addedge(id(i,j),id(i,j-1),INF);
                if(i!=n)
                    addedge(id(i,j),id(i+1,j),INF);
                if(j!=m)
                    addedge(id(i,j),id(i,j+1),INF);
            }
            else
                addedge(id(i,j),t,d);
        }
    cout<<sum-dinic()<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



@机器人路径规划问题

Luogu P2775 难度:NOI/NOI+/CTSC


这什么题目啊害人不浅啊不会啊太难了啊

有兴趣的可以参考这篇论文机器人路径规划问题(TMP1R)题解


Submit: NaN




圆桌问题(最大流)

Luogu P3254 难度:省选/NOI-


直接建图,将公司和桌子作为点

源点向每个公司连一条边,流量为公司派出的人数\(r[i]\)

每个桌子向汇点连一条边,流量为桌子可容纳的人数\(c[i]\)

每个公司向每个桌子连一条边(共\(n*m\)条边),流量为\(1\),表示每个桌子只能去一人

最后可以遍历每个公司,取正向的流量为\(0\)的边表示选中,用\(vector\)存答案即可

或者也可以遍历每个桌子,取反向的流量\(0\)的边表示选中


Submit: 2020-08-02 11:15

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=666,maxm=66666;

struct MaximumFlow
{
    struct Edge
    {
        int u,v,w;
    }eg[maxm<<1];
    int head[maxn<<1],nxt[maxm<<1],dep[maxn<<1],tot,s,t,mxn;
    
    void init(int n,int _s,int _t)
    {
        mxn=n,tot=0,s=_s,t=_t;
        while(n>=0)
            head[n--]=-1;
    }
    void add(int u,int v,int w)
    {
        nxt[tot]=head[u];
        head[u]=tot;
        eg[tot++]=(Edge){u,v,w};
        nxt[tot]=head[v];
        head[v]=tot;
        eg[tot++]=(Edge){v,u,0};
    }
    int bfs()
    {
        for(int i=1;i<=mxn;i++)
            dep[i]=-1;
        dep[s]=0;
        queue<int> q;
        q.push(s);
        while(!q.empty())
        {
            int x=q.front();
            q.pop();
            for(int i=head[x];i!=-1;i=nxt[i])
            {
                if(eg[i].w&&dep[eg[i].v]==-1)
                {
                    dep[eg[i].v]=dep[x]+1;
                    q.push(eg[i].v);
                }
            }
        }
        return (dep[t]>=0);
    }
    int dfs(int p,int mn)
    {
        if(p==t)
            return mn;
        int d,now=0;
        for(int i=head[p];i!=-1;i=nxt[i])
        {
            if(eg[i].w&&dep[eg[i].v]==dep[p]+1)
            {
                d=dfs(eg[i].v,min(mn-now,eg[i].w));
                now+=d;
                eg[i].w-=d;
                eg[i^1].w+=d;
                if(now==mn)break;
            }
        }
        return now;
    }
    int dinic()
    {
        int res=0;
        while(bfs())
            res+=dfs(s,INF);
        return res;
    }
}f;

int r[maxn],c[maxn];
vector<int> ans[maxn];

void solve()
{
    int m,n,sum=0;
    cin>>m>>n;
    for(int i=1;i<=m;i++)
        cin>>r[i],sum+=r[i];
    for(int i=1;i<=n;i++)
        cin>>c[i];
    f.init(n+m+2,n+m+1,n+m+2);
    for(int i=1;i<=m;i++)
    {
        f.add(f.s,i,r[i]);
        for(int j=1;j<=n;j++)
            f.add(i,j+m,1);
    }
    for(int i=1;i<=n;i++)
        f.add(i+m,f.t,c[i]);
    if(f.dinic()!=sum) //最大流不等于总人数
    {
        cout<<"0\n";
        return;
    }
    cout<<"1\n";
    for(int i=1;i<=n;i++) //这里遍历的是桌子,反向边
    {
        for(int j=f.head[i+m];j!=-1;j=f.nxt[j])
        {
            if(f.eg[j].v<=m&&f.eg[j].w!=0) //反向边不为0
                ans[f.eg[j].v].push_back(i);
        }
    }
    for(int i=1;i<=m;i++)
    {
        for(int it:ans[i])
            cout<<it<<' ';
        cout<<'\n';
    }
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



骑士共存问题(最小割)

Luogu P3355 难度:省选/NOI-


可以发现奇偶性不同的点不会互相影响

所以将图分为奇偶进行建图

将源点与所有奇数点相连,流量为\(1\)

将所有偶数点与汇点项链,流量为\(1\)

对于所有奇数点,如果他能攻击到某些偶数点格子,则让它连向该格子,流量为\(\infty\)

如果建图时遇到障碍物直接跳过

最后跑一遍最小割,最小割的值则表明最少的不能放置骑士的格子数

让所有能够放置骑士的格子数减去最小割,即为答案


Submit: 2020-08-02 12:45

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=44444,maxm=2333333;

struct edge{
    int u,v,cap,flow;
    edge(){}
    edge(int u,int v,int cap,int flow):u(u),v(v),cap(cap),flow(flow){}
}eg[maxm<<1];
int tot,s,t,dis[maxn<<1],cur[maxn<<1];
vector<int> tab[maxn<<1];

void init(int n,int s_,int t_){
    while(n)tab[n--].clear();
    tot=0,s=s_,t=t_;
}
void addedge(int u,int v,int cap){
    tab[u].push_back(tot);
    eg[tot++]=edge(u,v,cap,0);
    tab[v].push_back(tot);
    eg[tot++]=edge(v,u,0,0);
}
int bfs(){
    queue<int> q;
    q.push(s);
    memset(dis,INF,sizeof dis);
    dis[s]=0;
    while(!q.empty()){
        int h=q.front(),i;
        q.pop();
        for(i=0;i<tab[h].size();i++){
            edge &e=eg[tab[h][i]];
            if(e.cap>e.flow&&dis[e.v]==INF){
                dis[e.v]=dis[h]+1;
                q.push(e.v);
            }
        }
    }
    return dis[t]<INF;
}
int dfs(int x,int maxflow){
    if(x==t|maxflow==0)
        return maxflow;
    int flow=0,i,f;
    for(i=cur[x];i<tab[x].size();i++){
        cur[x]=i;
        edge &e=eg[tab[x][i]];
        if(dis[e.v]==dis[x]+1&&(f=dfs(e.v,min(maxflow,e.cap-e.flow)))>0){
            e.flow+=f;
            eg[tab[x][i]^1].flow-=f;
            flow+=f;
            maxflow-=f;
            if(maxflow==0)
                break;
        }
    }
    return flow;
}
int dinic(){
    int flow=0;
    while(bfs()){
        memset(cur,0,sizeof(cur));
        flow+=dfs(s,INF);
    }
    return flow;
}

bool bar[222][222];
int n;

inline int id(int x,int y)
{
    return (x-1)*n+y;
}

void solve()
{
    int m,x,y;
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        cin>>x>>y;
        bar[x][y]=true;
    }
    init(n*n+2,n*n+1,n*n+2);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            if(bar[i][j])
                continue;
            if((i+j)&1)
                addedge(s,id(i,j),1);
            else
                addedge(id(i,j),t,1);
        }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(!bar[i][j]&&(i+j)&1)
            {
                if(i>=3&&j>=2&&!bar[i-2][j-1])
                    addedge(id(i,j),id(i-2,j-1),INF);
                if(i>=3&&j<n&&!bar[i-2][j+1])
                    addedge(id(i,j),id(i-2,j+1),INF);
                if(i>=2&&j>=3&&!bar[i-1][j-2])
                    addedge(id(i,j),id(i-1,j-2),INF);
                if(i>=2&&j<n-1&&!bar[i-1][j+2])
                    addedge(id(i,j),id(i-1,j+2),INF);
                if(i<n&&j>=3&&!bar[i+1][j-2])
                    addedge(id(i,j),id(i+1,j-2),INF);
                if(i<n&&j<n-1&&!bar[i+1][j+2])
                    addedge(id(i,j),id(i+1,j+2),INF);
                if(i<n-1&&j>=2&&!bar[i+2][j-1])
                    addedge(id(i,j),id(i+2,j-1),INF);
                if(i<n-1&&j<n&&!bar[i+2][j+1])
                    addedge(id(i,j),id(i+2,j+1),INF);
            }
    cout<<n*n-m-dinic()<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



火星探险问题(费用流)

Luogu P3356 难度:省选/NOI-


请不要理所当然地把m写成n还找不出错(…

由于包含石子搜集,所以明显是一道最大费用最大流题目(下文中费用取反)

由于石子收集一次后就没了,所以需要对图进行拆点,每个点拆成一个入点\(A_{(i,j)}\)和一个出点\(B_{(i,j)}\)

如果某个点上有障碍,则对应的\(A\)\(B\)不连边

如果某个点自由通行,则连接一条\(A_{(i,j)}\)\(B_{(i,j)}\)的流量为\(\infty\)费用为\(0\)的边

如果某个点有石子,则在自由通行的基础上再连接一条\(A_{(i,j)}\)\(B_{(i,j)}\)的流量为\(1\)费用为\(1\)的边,表示只能走一次并收集一个石子

图只能往南与往东走,所以每次让\(B_{(i,j)}\)\(A_{(i,j+1)}\)以及\(A_{(i+1,j)}\)(如果存在)连接一条流量为\(\infty\)费用为\(0\)的边

(可以不需要判断相邻点是否有障碍,因为如果有障碍入点与出点不连通,当然加了判断可以稍微加速搜索)

最后,让源点\(S\)\(A_{(1,1)}\)连接一条流量为\(\infty\)费用为\(0\)的边

\(B_{(n,m)}\)向汇点\(T\)连接一条流量为\(\infty\)费用为\(0\)的边

跑费用流后取出最大流\(maxflow\),表示最多能走的火星车数量

最后从\((1,1)\)开始搜索\(maxflow\)次,每次搜索到某个点 \((i,j)\)时,遍历\(B_{(i,j)}\)的相邻边

如果某条相邻边指向相邻点,并且其反向边的流量不为\(0\),说明这条路还能走

让反向边流量减\(1\)标记,判断往相邻点走是往东还是往南,输出后继续搜索即可


Submit: 2020-08-05 15:19

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=6666;

struct MCMF {
    struct E {
        int from, to, cap, v;
        E() {}
        E(int f, int t, int cap, int v) : from(f), to(t), cap(cap), v(v) {}
    };
    int n, m, s, t;
    vector<E> edges;
    vector<int> G[maxn];
    bool inq[maxn];
    int dis[maxn], pre[maxn], a[maxn];
    void init(int _n, int _s, int _t) {
        n = _n; s = _s; t = _t;
        for (int i = 0; i <= n; i++)
            G[i].clear();
        edges.clear();
        m = 0;
    }
    void add(int from, int to, int cap, int cost) {
        edges.emplace_back(from, to, cap, cost);
        edges.emplace_back(to, from, 0, -cost);
        G[from].push_back(m++);
        G[to].push_back(m++);
    }
    bool spfa() {
        for (int i = 0; i <= n; i++) {
            dis[i] = INF;
            pre[i] = -1;
            inq[i] = false;
        }
        dis[s] = 0, a[s] = INF, inq[s] = true;
        queue<int> Q; Q.push(s);
        while (!Q.empty()) {
            int u = Q.front(); Q.pop();
            inq[u] = false;
            for (int& idx: G[u]) {
                E& e = edges[idx];
                if (e.cap && dis[e.to] > dis[u] + e.v) {
                    dis[e.to] = dis[u] + e.v;
                    pre[e.to] = idx;
                    a[e.to] = min(a[u], e.cap);
                    if (!inq[e.to]) {
                        inq[e.to] = true;
                        Q.push(e.to);
                    }
                }
            }
        }
        return pre[t] != -1;
    }
    int solve() {
        int flow = 0, cost = 0;
        while (spfa()) {
            flow += a[t];
            cost += a[t] * dis[t];
            int u = t;
            while (u != s) {
                edges[pre[u]].cap -= a[t];
                edges[pre[u] ^ 1].cap += a[t];
                u = edges[pre[u]].from;
            }
        }
        return flow; //只要最大流
    }
}f;

int cnt,n,m,mp[40][40],base;

inline int id(int x,int y)
{
    return (x-1)*m+y;
}

void dfs(int which,int x,int y)
{
    if(x==n&&y==m) //到终点可直接结束
        return;
    int pos=id(x,y);
    for(int nxt:f.G[pos+base]) //在出点中寻找路径
    {
        if(f.edges[nxt^1].cap&&f.edges[nxt].to!=pos) //指向相邻点且反向边流量不为0时
        {
            f.edges[nxt^1].cap--; //每一次搜索让流量-1
            if(f.edges[nxt].to==id(x,y+1))
            {
                cout<<which<<' '<<1<<'\n';
                dfs(which,x,y+1);
            }
            else
            {
                cout<<which<<' '<<0<<'\n';
                dfs(which,x+1,y);
            }
            return;
        }
    }
}

void solve()
{
    cin>>cnt>>m>>n;
    base=n*m;
    f.init(base*2+2,base*2+1,base*2+2);
    f.add(f.s,id(1,1),cnt,0);
    f.add(id(n,m)+base,f.t,INF,0);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            cin>>mp[i][j];
            if(mp[i][j]!=1)
                f.add(id(i,j),id(i,j)+base,INF,0);
            if(mp[i][j]==2)
                f.add(id(i,j),id(i,j)+base,1,-1);
        }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            if(i!=n)
                f.add(id(i,j)+base,id(i+1,j),INF,0);
            if(j!=m)
                f.add(id(i,j)+base,id(i,j+1),INF,0);
        }
    int maxflow=f.solve(); //跑最小费用最大流获取最大流,即为可行的月球车方案数
    for(int i=1;i<=maxflow;i++)
        dfs(i,1,1);
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



最长k可重线段集问题(费用流)

Luogu P3357 难度:省选/NOI-


基本思路与下一题相同(最长k可重区间集应该算是简化版)

由于线段集是对于二维平面里的线段,而条件又仅仅是对于任何一个数\(p\)\(x=p\)与选择的线段集交点不多于\(k\)

明显可以直接考虑线段集在\(x\)轴上的映射,将其转换到一维然后像下一题一样考虑

但是会发现,有可能会有线段的\(x_1=x_2\),即垂直与\(x\)

p16_1

又由于线段两点是不包括的,所以直接沿用下一题的思路不可行(\(82\)分警告!)

此时我们可以将坐标系按\(x\)轴方向扩展一倍,即对于所有的\(x_i:=x_i\times2\)

对于垂直于\(x\)轴的线段,我们只需要让它的\(x_2+1\)就能在\(x\)轴上得到它的投影,进而直接判断即可

p16_2

(别着急做,\(91\)分警告)

加入我们有下面两段从定义上不相交的线段

p16_3

\(x\)轴扩大两边后,垂直于\(x\)轴的线段\(x_2=x_2+1\),于是图就会变成

p16_4

发现这样做会让原本不相交的直线相交

所以,如果某条直线不与\(x\)轴垂直,那么让它的\(x_1:=x_1+1\)以防止出错

p16_5

其余部分直接照抄下一题的即可


Submit: 2020-08-05 19:01

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll LINF=0x3f3f3f3f3f3f3f3f;
const int maxn=1050;

struct MCMF {
    struct E {
        int from, to;
        ll cap, v;
        E() {}
        E(int f, int t, ll cap, ll v) : from(f), to(t), cap(cap), v(v) {}
    };
    int n, m, s, t;
    vector<E> edges;
    vector<int> G[maxn];
    bool inq[maxn];
    int pre[maxn];
    ll dis[maxn],a[maxn];
    void init(int _n, int _s, int _t) {
        n = _n; s = _s; t = _t;
        for (int i = 0; i <= n; i++)
            G[i].clear();
        edges.clear();
        m = 0;
    }
    void add(int from, int to, ll cap, ll cost) {
        edges.emplace_back(from, to, cap, cost);
        edges.emplace_back(to, from, 0, -cost);
        G[from].push_back(m++);
        G[to].push_back(m++);
    }
    bool spfa() {
        for (int i = 0; i <= n; i++) {
            dis[i] = LINF;
            pre[i] = -1;
            inq[i] = false;
        }
        dis[s] = 0, a[s] = LINF, inq[s] = true;
        queue<int> Q; Q.push(s);
        while (!Q.empty()) {
            int u = Q.front(); Q.pop();
            inq[u] = false;
            for (int& idx: G[u]) {
                E& e = edges[idx];
                if (e.cap && dis[e.to] > dis[u] + e.v) {
                    dis[e.to] = dis[u] + e.v;
                    pre[e.to] = idx;
                    a[e.to] = min(a[u], e.cap);
                    if (!inq[e.to]) {
                        inq[e.to] = true;
                        Q.push(e.to);
                    }
                }
            }
        }
        return pre[t] != -1;
    }
    ll solve() {
        ll flow = 0, cost = 0;
        while (spfa()) {
            flow += a[t];
            cost += a[t] * dis[t];
            int u = t;
            while (u != s) {
                edges[pre[u]].cap -= a[t];
                edges[pre[u] ^ 1].cap += a[t];
                u = edges[pre[u]].from;
            }
        }
        return cost;
    }
}f;

ll X1[555],X2[555],Y1[555],Y2[555];

void solve()
{
    int n,k;
    cin>>n>>k;
    f.init(n*2+3,n*2+1,n*2+3);
    f.add(n*2+1,n*2+2,k,0);
    for(int i=1;i<=n;i++)
    {
        cin>>X1[i]>>Y1[i]>>X2[i]>>Y2[i];
        f.add(n*2+2,i,1,0);
        f.add(i,i+n,1,-((ll)hypot(fabs(X1[i]-X2[i]),fabs(Y1[i]-Y2[i]))));
        f.add(i+n,f.t,1,0);
        X1[i]<<=1;
        X2[i]<<=1;
        if(X1[i]==X2[i])
            X2[i]++;
        else
            X1[i]++;
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            if(i==j)
                continue;
            if(X2[i]<=X1[j])
                f.add(i+n,j,LINF,0);
        }
    cout<<-f.solve()<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



最长k可重区间集问题(费用流)

Luogu P3358 难度:省选/NOI-


题目要求某个点最多被\(k\)个开区间覆盖

如果从点考虑,由于判断的是区间,所以很难入手

但如果我们从区间入手,判断两段区间是否存在重叠部分

如果两段区间不存在重叠部分,说明这两段区间都可以选择且互不影响

所以我们可以让所有互不影响的区间串在一起,表示选择其中一个就可以选择另一个

如果再以区间长度作为选择一段区间的费用,这题就变成了最大费用最大流

将每段区间拆成两个点\(A_i\)\(B_i\),用一条流量为\(1\)费用为\(Length(i)\)的边连接,表示这段区间只能选择一次,选择后对答案的贡献即为长度

将源点拆点,连接一条源点\(S\)到源点拆点\(S_2\)的流量为\(k\)费用为\(0\)的边,表示我们最多能选择图中\(k\)条链保证满足题意

其后让\(S_2\)连接所有区间的入点\(A_i\),表示所有区间都可能成为选择的链的第一段区间

让所有区间的出点\(B_i\)连接汇点\(T\),表示所有区间都可能成为选择的链的最后一段区间

最后\(O(n^2)\)枚举两段区间\(i,j\)

如果两段区间满足\(R_i\leq L_j\),则表示没有交点,且区间\(i\)在区间\(j\)的左侧

于是连接一条\(B_i\)\(A_j\)的流量为\(1\)费用为\(0\)的边,表示两段区间可以同时选择

最后跑一遍费用流求出最大费用就是选择区间长度和的最大值了


Submit: 2020-08-05 16:39

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=1050;

struct MCMF {
    struct E {
        int from, to, cap, v;
        E() {}
        E(int f, int t, int cap, int v) : from(f), to(t), cap(cap), v(v) {}
    };
    int n, m, s, t;
    vector<E> edges;
    vector<int> G[maxn];
    bool inq[maxn];
    int dis[maxn], pre[maxn], a[maxn];
    void init(int _n, int _s, int _t) {
        n = _n; s = _s; t = _t;
        for (int i = 0; i <= n; i++)
            G[i].clear();
        edges.clear();
        m = 0;
    }
    void add(int from, int to, int cap, int cost) {
        edges.emplace_back(from, to, cap, cost);
        edges.emplace_back(to, from, 0, -cost);
        G[from].push_back(m++);
        G[to].push_back(m++);
    }
    bool spfa() {
        for (int i = 0; i <= n; i++) {
            dis[i] = INF;
            pre[i] = -1;
            inq[i] = false;
        }
        dis[s] = 0, a[s] = INF, inq[s] = true;
        queue<int> Q; Q.push(s);
        while (!Q.empty()) {
            int u = Q.front(); Q.pop();
            inq[u] = false;
            for (int& idx: G[u]) {
                E& e = edges[idx];
                if (e.cap && dis[e.to] > dis[u] + e.v) {
                    dis[e.to] = dis[u] + e.v;
                    pre[e.to] = idx;
                    a[e.to] = min(a[u], e.cap);
                    if (!inq[e.to]) {
                        inq[e.to] = true;
                        Q.push(e.to);
                    }
                }
            }
        }
        return pre[t] != -1;
    }
    int solve() {
        int flow = 0, cost = 0;
        while (spfa()) {
            flow += a[t];
            cost += a[t] * dis[t];
            int u = t;
            while (u != s) {
                edges[pre[u]].cap -= a[t];
                edges[pre[u] ^ 1].cap += a[t];
                u = edges[pre[u]].from;
            }
        }
        return cost;
    }
}f;

int l[555],r[555];

void solve()
{
    int n,k;
    cin>>n>>k;
    f.init(n*2+3,n*2+1,n*2+3);
    f.add(n*2+1,n*2+2,k,0);
    for(int i=1;i<=n;i++)
    {
        cin>>l[i]>>r[i];
        f.add(n*2+2,i,1,0);
        f.add(i,i+n,1,-(r[i]-l[i]));
        f.add(i+n,f.t,1,0);
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            if(i==j)
                continue;
            if(r[i]<=l[j])
                f.add(i+n,j,1,0);
        }
    cout<<-f.solve()<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



汽车加油行驶问题(费用流/最短路)

Luogu P4009 难度:省选/NOI-


按油量进行分层,每走一步就表示从某一层走到了下一层中的相邻点

可以肯定的是,汽车不会重复地经过相同的状态(以相同的油量走过相同的边),所以可以让整张图的边流量固定为\(1\),仅考虑费用即可

设定层数范围为\([0,k]\),第\(0\)层表示满油

起点固定为\((1,1)\),连接一条源点到第\(0\)\((1,1)\)的费用为\(0\)的边(初始必定满油)

终点固定为\((n,n)\),但油量不固定,故将每一层的\((n,n)\)向汇点连接一条费用为\(0\)的边

开始考虑移动,假设当前在第\(u\)\((i,j)\)的位置,以三元组\((u,i,j)\)表示

走过一段路可以视为油量消耗\(1\),假设往\((i+1,j)\)移动,则需要连接一条\((u,i,j)\)\((u+1,i+1,j)\)的费用为\(0\)的边

假设往\((i-1,j)\)移动,由于坐标变小需要花费\(b\),则需要连接一条\((u,i,j)\)\((u+1,i-1,j)\)的费用为\(b\)的边

如果当前点\((i,j)\)是加油站,由于题目要求强制加油(强制消费),故此时不能像上面那样直接连接其他点

而是需要先将所有\((u,i,j),u\geq1\)连向\((0,i,j)\)费用为\(a\),表示强制加油

再从\((0,i,j)\)向第一层的相邻点像上述连边

对于每个点\((i,j)\),让\((k,i,j)\)\((0,i,j)\)连一条费用为\(a+c\)的边,表示走到该点后没油了无路可走,但可以增设油库并再次加油

建图完毕后直接跑费用流

※可以直接用最短路做,下面展示的是费用流做法


Submit: 2020-08-04 20:44

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=123456;

struct MCMF {
    struct E {
        int from, to, cap, v;
        E() {}
        E(int f, int t, int cap, int v) : from(f), to(t), cap(cap), v(v) {}
    };
    int n, m, s, t;
    vector<E> edges;
    vector<int> G[maxn];
    bool inq[maxn];
    int dis[maxn], pre[maxn], a[maxn];
    void init(int _n, int _s, int _t) {
        n = _n; s = _s; t = _t;
        for (int i = 0; i <= n; i++)
            G[i].clear();
        edges.clear();
        m = 0;
    }
    void add(int from, int to, int cap, int cost) {
        edges.emplace_back(from, to, cap, cost);
        edges.emplace_back(to, from, 0, -cost);
        G[from].push_back(m++);
        G[to].push_back(m++);
    }
    bool spfa() {
        for (int i = 0; i <= n; i++) {
            dis[i] = INF;
            pre[i] = -1;
            inq[i] = false;
        }
        dis[s] = 0, a[s] = INF, inq[s] = true;
        queue<int> Q; Q.push(s);
        while (!Q.empty()) {
            int u = Q.front(); Q.pop();
            inq[u] = false;
            for (int& idx: G[u]) {
                E& e = edges[idx];
                if (e.cap && dis[e.to] > dis[u] + e.v) {
                    dis[e.to] = dis[u] + e.v;
                    pre[e.to] = idx;
                    a[e.to] = min(a[u], e.cap);
                    if (!inq[e.to]) {
                        inq[e.to] = true;
                        Q.push(e.to);
                    }
                }
            }
        }
        return pre[t] != -1;
    }
    int solve() {
        int flow = 0, cost = 0;
        while (spfa()) {
            flow += a[t];
            cost += a[t] * dis[t];
            int u = t;
            while (u != s) {
                edges[pre[u]].cap -= a[t];
                edges[pre[u] ^ 1].cap += a[t];
                u = edges[pre[u]].from;
            }
        }
        return cost;
    }
}f;

int n,k,a,b,c;

inline int id(int x,int y)
{
    return (x-1)*n+y;
}

void solve()
{
    cin>>n>>k>>a>>b>>c;
    int base=n*n;
    f.init(base*(k+1)+2,base*(k+1)+1,base*(k+1)+2);
    f.add(f.s,id(1,1),1,0);
    for(int i=0;i<=k;i++)
        f.add(id(n,n)+i*base,f.t,1,0);
    for(int d,i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            cin>>d;
            if(d)
            {
                for(int u=1;u<=k;u++)
                    f.add(id(i,j)+u*base,id(i,j),1,a);
                if(i+1<=n)
                    f.add(id(i,j),id(i+1,j)+base,INF,0);
                if(j+1<=n)
                    f.add(id(i,j),id(i,j+1)+base,INF,0);
                if(i-1>0)
                    f.add(id(i,j),id(i-1,j)+base,INF,b);
                if(j-1>0)
                    f.add(id(i,j),id(i,j-1)+base,INF,b);
            }
            else
            {
                for(int u=0;u<k;u++)
                {
                    if(i+1<=n)
                        f.add(id(i,j)+u*base,id(i+1,j)+(u+1)*base,INF,0);
                    if(j+1<=n)
                        f.add(id(i,j)+u*base,id(i,j+1)+(u+1)*base,INF,0);
                    if(i-1>0)
                        f.add(id(i,j)+u*base,id(i-1,j)+(u+1)*base,INF,b);
                    if(j-1>0)
                        f.add(id(i,j)+u*base,id(i,j-1)+(u+1)*base,INF,b);
                }
            }
            f.add(id(i,j)+base*k,id(i,j),1,a+c);
        }
    cout<<f.solve()<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



孤岛营救问题(BFS、状态压缩)

Luogu P4011 难度:提高+/省选-


带多条件的普通搜索

可以将钥匙种类转化成二进制表示

开四维数组\(mp[x_i][y_i][x_j][y_j]\)储存\((x_i,y_i)\)\((x_j,y_j)\)之间的道路性质,不能通过为\(-1\),能直接通过为\(0\),其余则为门的种类

开三维数组\(vis[x][y][status]\)储存走到\((x,y)\)且钥匙持有状态为\(status\)的状态是否已经访问过,相当于防止了死循环出现

开二维数组\(key[x][y]\)储存\((x,y)\)所拥有的钥匙种类

于是便是普通bfs搜索,每次先判断下一点是否合法(\(1≤i≤n,\ 1≤j≤m\)),再检查能否直接通过或不能通过或暂时不能通过(没有钥匙,即进行按位与运算结果为\(0\)),最后检查是否已经访问过这一状态(\(vis\)数组)

对于搜索过程的状态,开一个结构体\(\{x,y,step,k\}\)储存当前位置为\((x,y)\),步数为\(step\),钥匙持有状态为\(k\)的状态


Submit: 2020-07-30 20:48

#include<bits/stdc++.h>
using namespace std;

struct node
{
    int x,y,step,k;
};

int n,m,mp[11][11][11][11],key[11][11];
int dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
bool vis[11][11][1<<15];

inline bool prim(int x,int y)
{
    return x>0&&y>0&&x<=n&&y<=m;
}

int bfs()
{
    queue<node> q;
    q.push(node{1,1,0,0}); //初始状态
    vis[1][1][0]=true;
    while(!q.empty())
    {
        node nd=q.front();
        q.pop();
        for(int i=0;i<4;i++)
        {
            int px=nd.x+dx[i],py=nd.y+dy[i];
            if(prim(px,py))
            {
                if(mp[nd.x][nd.y][px][py]==-1)
                    continue; //无法走过
                if(mp[nd.x][nd.y][px][py]>0&&(nd.k&(1<<mp[nd.x][nd.y][px][py]))==0)
                    continue; //没有钥匙
                if(!vis[px][py][nd.k|key[px][py]]) //这里直接将下一格的钥匙拿来即可
                {
                    if(px==n&&py==m)
                        return nd.step+1; //走到目标点,输出前一状态的步数+1即可
                    vis[px][py][nd.k|key[px][py]]=true;
                    q.push(node{px,py,nd.step+1,nd.k|key[px][py]});
                }
            }
        }
    }
    return -1; //无解
}

void solve()
{
    int p,k,g,x1,x2,y1,y2,s,x,y,q;
    cin>>n>>m>>p>>k;
    while(k--)
    {
        cin>>x1>>y1>>x2>>y2>>g;
        if(g==0) //让0表示互通的状态
            g=-1;
        mp[x1][y1][x2][y2]=mp[x2][y2][x1][y1]=g;
    }
    cin>>s;
    while(s--)
    {
        cin>>x>>y>>q;
        key[x][y]|=1<<q;
    }
    cout<<bfs()<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



深海机器人问题(费用流)

Luogu P4012 难度:省选/NOI-


如题意所示,建立超级源点与超级汇点

超级源点连接所有机器人的起始点,流量为起始点的机器人数量,费用为\(0\)

所有机器人的终点连接超级汇点,流量为终点的机器人数量,费用为\(0\)

由于机器人只能往上或往右走,所以对于每个点连接其上方与右边的点(如果存在)

连接一条流量为\(1\)费用为边权的边,表示第一次流经可以增加的费用

连接一条流量为\(\infty\)费用为0的边,表示之后经过不会增加费用但还能保证经过

求最大费用,即全图费用取负,输出时也取负


Submit: 2020-08-01 11:44

#include<bits/stdc++.h>
using namespace std;

const int INF=0x3f3f3f3f;
const int maxn=1050;

struct MCMF {
    struct E {
        int from, to, cap, v;
        E() {}
        E(int f, int t, int cap, int v) : from(f), to(t), cap(cap), v(v) {}
    };
    int n, m, s, t;
    vector<E> edges;
    vector<int> G[maxn];
    bool inq[maxn];
    int dis[maxn], pre[maxn], a[maxn];
    void init(int _n, int _s, int _t) {
        n = _n; s = _s; t = _t;
        for (int i = 0; i <= n; i++)
            G[i].clear();
        edges.clear();
        m = 0;
    }
    void add(int from, int to, int cap, int cost) {
        edges.emplace_back(from, to, cap, cost);
        edges.emplace_back(to, from, 0, -cost);
        G[from].push_back(m++);
        G[to].push_back(m++);
    }
    bool spfa() {
        for (int i = 0; i <= n; i++) {
            dis[i] = 1e9;
            pre[i] = -1;
            inq[i] = false;
        }
        dis[s] = 0, a[s] = 1e9, inq[s] = true;
        queue<int> Q; Q.push(s);
        while (!Q.empty()) {
            int u = Q.front(); Q.pop();
            inq[u] = false;
            for (int& idx: G[u]) {
                E& e = edges[idx];
                if (e.cap && dis[e.to] > dis[u] + e.v) {
                    dis[e.to] = dis[u] + e.v;
                    pre[e.to] = idx;
                    a[e.to] = min(a[u], e.cap);
                    if (!inq[e.to]) {
                        inq[e.to] = true;
                        Q.push(e.to);
                    }
                }
            }
        }
        return pre[t] != -1;
    }
    int solve() {
        int flow = 0, cost = 0;
        while (spfa()) {
            flow += a[t];
            cost += a[t] * dis[t];
            int u = t;
            while (u != s) {
                edges[pre[u]].cap -= a[t];
                edges[pre[u] ^ 1].cap += a[t];
                u = edges[pre[u]].from;
            }
        }
        return cost;
    }
}f;

int Q,P;
int mp[20][20][20][20];

inline int id(int x,int y)
{
    return x*(Q+1)+y;
}

void solve()
{
    int a,b,d,k,x,y;
    cin>>a>>b>>P>>Q;
    f.init((P+1)*(Q+1)+2,(P+1)*(Q+1)+1,(P+1)*(Q+1)+2);
    for(int i=0;i<=P;i++)
        for(int j=0;j<Q;j++)
        {
            cin>>d;
            f.add(id(i,j),id(i,j+1),1,-d);
            f.add(id(i,j),id(i,j+1),INF,0);
        }
    for(int j=0;j<=Q;j++)
        for(int i=0;i<P;i++)
        {
            cin>>d;
            f.add(id(i,j),id(i+1,j),1,-d);
            f.add(id(i,j),id(i+1,j),INF,0);
        }
    for(int i=1;i<=a;i++)
    {
        cin>>k>>x>>y;
        f.add(f.s,id(x,y),k,0);
    }
    for(int i=1;i<=b;i++)
    {
        cin>>k>>x>>y;
        f.add(id(x,y),f.t,k,0);
    }
    cout<<-f.solve()<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



数字梯形问题(费用流)

Luogu P4013 难度:省选/NOI-


本题分为三小问,难度依次递减

第一问每个数字只能经过一次,所以要对每个点进行拆点

源点向第一行的数字建立一条流量为\(1\)花费为\(0\)的边

每行数字与自己拆点后的点之间连一条流量为\(1\)花费为数字大小的边

每行拆点后的点与相邻的下一行的两个点之间连一条流量任意(\(\geq1\))花费为\(0\)的边(流过拆点后不会出现重边故不需要对流量做限制)

如此建图,最后一行拆点后的点向汇点连一条流量为\(1\)花费为\(0\)的边

最后跑一遍最小费用最大流即可

第二问可以走重复点但不能重复边

所以不需要进行拆点,直接让点之间连接即可

源点连向第一层的点流量为\(1\)花费为\(0\)

\(i\)层的点流向第\(i+1\)层的点流量为\(1\)花费为第\(i\)层点的点值

\(n\)层的点流向汇点流量为\(\infty\)花费为第\(n\)层点的点值(因为最后一层点可以重复,所以设置流量为\(\infty\)

最后跑一遍最小费用最大流即可

第三问可以重复点也可以重复边

所以像第二问一样建图,除了源点流出的边流量保持\(1\)以外,其余边流量均设置为\(\infty\)

最后跑一遍最小费用最大流即可


Submit: 2020-07-31 20:52

#include<bits/stdc++.h>
using namespace std;
const int maxn=1610;
const int INF=0x3f3f3f3f;
 
struct MCMF {
    struct E {
        int from, to, cap, v;
        E() {}
        E(int f, int t, int cap, int v) : from(f), to(t), cap(cap), v(v) {}
    };
    int n, m, s, t;
    vector<E> edges;
    vector<int> G[maxn];
    bool inq[maxn];
    int dis[maxn], pre[maxn], a[maxn];
    void init(int _n, int _s, int _t) {
        n = _n; s = _s; t = _t;
        for (int i = 0; i <= n; i++)
            G[i].clear();
        edges.clear();
        m = 0;
    }
    void add(int from, int to, int cap, int cost) {
        edges.emplace_back(from, to, cap, cost);
        edges.emplace_back(to, from, 0, -cost);
        G[from].push_back(m++);
        G[to].push_back(m++);
    }
    bool spfa() {
        for (int i = 0; i <= n; i++) {
            dis[i] = INF;
            pre[i] = -1;
            inq[i] = false;
        }
        dis[s] = 0, a[s] = INF, inq[s] = true;
        queue<int> Q; Q.push(s);
        while (!Q.empty()) {
            int u = Q.front(); Q.pop();
            inq[u] = false;
            for (int& idx: G[u]) {
                E& e = edges[idx];
                if (e.cap && dis[e.to] > dis[u] + e.v) {
                    dis[e.to] = dis[u] + e.v;
                    pre[e.to] = idx;
                    a[e.to] = min(a[u], e.cap);
                    if (!inq[e.to]) {
                        inq[e.to] = true;
                        Q.push(e.to);
                    }
                }
            }
        }
        return pre[t] != -1;
    }
    int solve() {
        int flow = 0, cost = 0;
        while (spfa()) {
            flow += a[t];
            cost += a[t] * dis[t];
            int u = t;
            while (u != s) {
                edges[pre[u]].cap -= a[t];
                edges[pre[u] ^ 1].cap += a[t];
                u = edges[pre[u]].from;
            }
        }
        return cost;
    }
}f1,f2,f3;

int m,n;
int ar[55][55];

inline int id(int x,int y)
{
    return (x-1)*40+y;
}

void dealQue1()
{
    f1.init(1602,1601,1602);
    for(int i=1;i<=m;i++) //处理源点
        f1.add(1601,id(1,i),1,0);
    for(int i=1;i<n;i++)
        for(int j=1;j<=m+i-1;j++)
        {
            f1.add(id(i,j),id(i,j)+800,1,-ar[i][j]);
            f1.add(id(i,j)+800,id(i+1,j),1,0);
            f1.add(id(i,j)+800,id(i+1,j+1),1,0);
        }
    for(int i=1;i<=m+n-1;i++) //单独处理最后一行+汇点
    {
        f1.add(id(n,i),id(n,i)+800,1,-ar[n][i]);
        f1.add(id(n,i)+800,1602,1,0);
    }
    cout<<-f1.solve()<<'\n';
}

void dealQue2()
{
    f2.init(1602,1601,1602);
    for(int i=1;i<=m;i++) //处理源点
        f2.add(1601,id(1,i),1,0);
    for(int i=1;i<n;i++)
        for(int j=1;j<=m+i-1;j++)
        {
            f2.add(id(i,j),id(i+1,j),1,-ar[i][j]);
            f2.add(id(i,j),id(i+1,j+1),1,-ar[i][j]);
        }
    for(int i=1;i<=m+n-1;i++) //单独处理汇点
    {
        f2.add(id(n,i),1602,INF,-ar[n][i]);
    }
    cout<<-f2.solve()<<'\n';
}

void dealQue3()
{
    f3.init(1602,1601,1602);
    for(int i=1;i<=m;i++) //处理源点
        f3.add(1601,id(1,i),1,0);
    for(int i=1;i<n;i++)
        for(int j=1;j<=m+i-1;j++)
        {
            f3.add(id(i,j),id(i+1,j),INF,-ar[i][j]);
            f3.add(id(i,j),id(i+1,j+1),INF,-ar[i][j]);
        }
    for(int i=1;i<=m+n-1;i++) //单独处理汇点
    {
        f3.add(id(n,i),1602,INF,-ar[n][i]);
    }
    cout<<-f3.solve()<<'\n';
}

void solve()
{
    cin>>m>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m+i-1;j++)
            cin>>ar[i][j];
    dealQue1();
    dealQue2();
    dealQue3();
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



分配问题(费用流)

Luogu P4014 难度:省选/NOI-


最小费用最大流模板

因为每个工人都要做一个工件,所以只要把费用设置为\(1\)直接跑就行

如果求的是最小流,设置正数边后直接跑即可

如果求的是最大流,设置负数边后直接跑并且答案取反


Submit: 2020-07-31 19:27

#include<bits/stdc++.h>
using namespace std;
const int maxn=222;
 
struct MCMF {
    struct E {
        int from, to, cap, v;
        E() {}
        E(int f, int t, int cap, int v) : from(f), to(t), cap(cap), v(v) {}
    };
    int n, m, s, t;
    vector<E> edges;
    vector<int> G[maxn];
    bool inq[maxn];
    int dis[maxn], pre[maxn], a[maxn];
    void init(int _n, int _s, int _t) {
        n = _n; s = _s; t = _t;
        for (int i = 0; i <= n; i++)
            G[i].clear();
        edges.clear();
        m = 0;
    }
    void add(int from, int to, int cap, int cost) {
        edges.emplace_back(from, to, cap, cost);
        edges.emplace_back(to, from, 0, -cost);
        G[from].push_back(m++);
        G[to].push_back(m++);
    }
    bool spfa() {
        for (int i = 0; i <= n; i++) {
            dis[i] = 1e9;
            pre[i] = -1;
            inq[i] = false;
        }
        dis[s] = 0, a[s] = 1e9, inq[s] = true;
        queue<int> Q; Q.push(s);
        while (!Q.empty()) {
            int u = Q.front(); Q.pop();
            inq[u] = false;
            for (int& idx: G[u]) {
                E& e = edges[idx];
                if (e.cap && dis[e.to] > dis[u] + e.v) {
                    dis[e.to] = dis[u] + e.v;
                    pre[e.to] = idx;
                    a[e.to] = min(a[u], e.cap);
                    if (!inq[e.to]) {
                        inq[e.to] = true;
                        Q.push(e.to);
                    }
                }
            }
        }
        return pre[t] != -1;
    }
    int solve() {
        int flow = 0, cost = 0;
        while (spfa()) {
            flow += a[t];
            cost += a[t] * dis[t];
            int u = t;
            while (u != s) {
                edges[pre[u]].cap -= a[t];
                edges[pre[u] ^ 1].cap += a[t];
                u = edges[pre[u]].from;
            }
        }
        return cost;
    }
}f1,f2;

void solve()
{
    int n,d;
    cin>>n;
    f1.init(n*2+2,n*2+1,n*2+2);
    f2.init(n*2+2,n*2+1,n*2+2);
    for(int i=1;i<=n;i++)
    {
        f1.add(f1.s,i,1,0);
        f1.add(n+i,f1.t,1,0);
        f2.add(f2.s,i,1,0);
        f2.add(n+i,f2.t,1,0);
        for(int j=1;j<=n;j++)
        {
            cin>>d;
            f1.add(i,n+j,1,d);
            f2.add(i,n+j,1,-d);
        }
    }
    cout<<f1.solve()<<'\n'<<-f2.solve()<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



运输问题(费用流)

Luogu P4015 难度:省选/NOI-


明显的费用流问题,直接建立费用流模型

由于仓库与商店存在需求限制,故要对其进行拆点

仓库与商店的两点之间限制流量为其货物储存量,花费为\(0\)

连接源点与所有仓库,流量为\(\infty\),花费为\(0\)

连接所有商店与汇点,流量为\(\infty\),花费为\(0\)

每对仓库与商店之间连一条流量为\(\infty\),花费为题目给定花费的边(最大花费为负边权)

直接跑费用流即可


Submit: 2020-08-04 19:49

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=666;

struct MCMF {
    struct E {
        int from, to, cap, v;
        E() {}
        E(int f, int t, int cap, int v) : from(f), to(t), cap(cap), v(v) {}
    };
    int n, m, s, t;
    vector<E> edges;
    vector<int> G[maxn];
    bool inq[maxn];
    int dis[maxn], pre[maxn], a[maxn];
    void init(int _n, int _s, int _t) {
        n = _n; s = _s; t = _t;
        for (int i = 0; i <= n; i++)
            G[i].clear();
        edges.clear();
        m = 0;
    }
    void add(int from, int to, int cap, int cost) {
        edges.emplace_back(from, to, cap, cost);
        edges.emplace_back(to, from, 0, -cost);
        G[from].push_back(m++);
        G[to].push_back(m++);
    }
    bool spfa() {
        for (int i = 0; i <= n; i++) {
            dis[i] = 1e9;
            pre[i] = -1;
            inq[i] = false;
        }
        dis[s] = 0, a[s] = 1e9, inq[s] = true;
        queue<int> Q; Q.push(s);
        while (!Q.empty()) {
            int u = Q.front(); Q.pop();
            inq[u] = false;
            for (int& idx: G[u]) {
                E& e = edges[idx];
                if (e.cap && dis[e.to] > dis[u] + e.v) {
                    dis[e.to] = dis[u] + e.v;
                    pre[e.to] = idx;
                    a[e.to] = min(a[u], e.cap);
                    if (!inq[e.to]) {
                        inq[e.to] = true;
                        Q.push(e.to);
                    }
                }
            }
        }
        return pre[t] != -1;
    }
    int solve() {
        int flow = 0, cost = 0;
        while (spfa()) {
            flow += a[t];
            cost += a[t] * dis[t];
            int u = t;
            while (u != s) {
                edges[pre[u]].cap -= a[t];
                edges[pre[u] ^ 1].cap += a[t];
                u = edges[pre[u]].from;
            }
        }
        return cost;
    }
}mxf,mnf;

void solve()
{
    int m,n,d;
    cin>>m>>n;
    mxf.init(2*(m+n)+2,2*(m+n)+1,2*(m+n)+2);
    mnf.init(2*(m+n)+2,2*(m+n)+1,2*(m+n)+2);
    for(int i=1;i<=m;i++)
    {
        cin>>d;
        mxf.add(mxf.s,i,INF,0);
        mxf.add(i,m+i,d,0);
        mnf.add(mnf.s,i,INF,0);
        mnf.add(i,m+i,d,0);
    }
    for(int i=1;i<=n;i++)
    {
        cin>>d;
        mxf.add(2*m+n+i,mxf.t,INF,0);
        mxf.add(2*m+i,2*m+n+i,d,0);
        mnf.add(2*m+n+i,mnf.t,INF,0);
        mnf.add(2*m+i,2*m+n+i,d,0);
    }
    for(int i=m+1;i<=m*2;i++)
        for(int j=m*2+1;j<=m*2+n;j++)
        {
            cin>>d;
            mnf.add(i,j,INF,d);
            mxf.add(i,j,INF,-d);
        }
    cout<<mnf.solve()<<'\n'<<-mxf.solve()<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



负载平衡问题(贪心)

Luogu P4016 难度:提高+/省选-


这啥?

枚举不需要交换的相邻两人,\(O(n)\)枚举\(O(n)\)判断即可


Submit: 2020-07-31 10:03

#include<bits/stdc++.h>
using namespace std;

int n,cnt[110],ave;

int deal(int st)
{
    int res=0,tmp=0,d;
    for(int i=st,j=1;j<n;i=i%n+1,j++) //循环,判断n-1次
    {
        d=tmp+cnt[i]-ave;
        res+=abs(d);
        tmp=d;
    }
    return res;
}
void solve()
{
    int sum=0,ans=INT_MAX;
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>cnt[i],sum+=cnt[i];
    ave=sum/n;
    for(int i=1;i<=n;i++) //枚举i与i+1无需交换
        ans=min(ans,deal(i));
    cout<<ans<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}

posted @ 2020-08-05 20:11  StelaYuri  阅读(248)  评论(1编辑  收藏  举报