1. 程式人生 > >「P4313」文理分科 解題報告

「P4313」文理分科 解題報告

題面

大意:\(n \times m\) 矩陣上的點,分成兩部分,每個點分配後會有一個分數,上下左右以及中間 的五個點分到一起會有額外的分數,可以是0,求分配後最大的分數

思路:

網路流最小割

網路流的模板並不是很難,難在建邊

而對於這道題,其實是網路流中的一種標準模型吧

一個點分到A或B兩部分中,不同的點組合在一起有加分的情況

用網路流的最小割做

就應該先把問題轉化一下:

最優的答案當然是所有分值都加在一起

但是,一個點不可能同時出現在兩部分,所以導致了一些不可能的情況

現在我們的目標就是去掉儘量少的分數(邊權),使一個點只能出現在一個部分

這就非常符合最小割了

可以一開始把每個點拆成兩個點,一個和超級源點S,另一個和超級匯點T相連

在題目中的實際意義就是這個同學是學文還是學理,邊權為 他/她 的滿意度,記得在這兩個點之間連上一條邊權為INF的邊,因為同一點是不能分割的!

然後處理組合,

對於每個組合,我們新建一個結點,然後把它當做一個普通的節點處理

RT:

1和2是兩個普通點,為了表示1和2的組合關係,就引入結點3當做 偽1結點 和 偽2結點,與\(1'\)\(2'\)連邊

\(3'\) 做同樣的處理

這幅圖和上面的圖是等價的,但是從意義上來看,上面的圖更能說明 \(3\)\(3'\) 的意義

建圖之後跑一遍Dinic就好了

由於我們求的是最小割

所以結果應該是Ans——所有情況的滿意度總和,減去Maxflow——至少刪去多少邊,才能滿足一個同學只能選一科

Warming:新建結點並連邊的時候,不要忘記與當前的點相連

Code:

#include<bits/stdc++.h>
#define INF 0x7f7f7f7f
#define M 200010
#define N 40010
#define Mn 110
using namespace std;
int Cx[4]={1,0,0,-1};//上下左右
int Cy[4]={0,1,-1,0};
struct node{
    int to,cap;
    int nxt;
    node(int a,int b):to(a),cap(b){ }
    node(){ }
}b[M<<1];
int head[N],deep[N];
int n,m,S,T,t=1,Ans,Maxflow,Max;//Ans累計滿意度之和
bool p[Mn][Mn];
int read()
{
    int s=0;
    char c=getchar();
    while(!isdigit(c))
        c=getchar();
    while(isdigit(c))
    {
        s=(s<<1)+(s<<3)+c-'0';
        c=getchar();
    }
    return s;
}
void add(int x,int y,int cap)
{
    b[++t]=node(y,cap);
    b[t].nxt=head[x];
    head[x]=t;
    b[++t]=node(x,0);
    b[t].nxt=head[y];
    head[y]=t;
    return;
}
int Cag(int x,int y)//二維轉化為一維
{
    return (x-1)*m+y;
}
bool BFS()
{
    int i,cur;
    int to,cap;
    queue<int>p;
    memset(deep,0,sizeof(deep));
    deep[S]=1;p.push(S);
    while(!p.empty())
    {
        cur=p.front();p.pop();
        for(i=head[cur];i;i=b[i].nxt)
        {
            to=b[i].to;cap=b[i].cap;
            if(cap&&!deep[to])
            {
                deep[to]=deep[cur]+1;
                p.push(to);
                if(to==T)
                    return 1;
            }
        }
    }
    return 0;
}
int Dinic(int k,int flow)
{
    if(k==T)
        return flow;
    int i,to,cap,res,rest=flow;
    for(i=head[k];i&&rest;i=b[i].nxt)
    {
        to=b[i].to;cap=b[i].cap;
        if(cap&&deep[to]==deep[k]+1)
        {
            res=Dinic(to,min(rest,cap));
            if(!res)
                deep[to]=0;
            b[i].cap-=res;
            b[i^1].cap+=res;
            rest-=res;
        }
    }
    return flow-rest;
}
int main()
{
    int i,j,k;
    int to,cap,flow;
    int Tx,Ty;
    n=read();m=read();
    Max=n*m;T=4*Max+1;
    for(i=1;i<=n;i++)
        p[i][0]=p[i][m+1]=1;
    for(i=1;i<=m;i++)
        p[0][i]=p[n+1][i]=1;
    for(i=1;i<=n;i++)
        for(j=1;j<=m;j++)
        {
            Ans+=cap=read();
            add(S,Cag(i,j),cap);//選文科
            add(Cag(i,j),Cag(i,j)+Max,INF);//自己的兩個結點相連
        }
    for(i=1;i<=n;i++)
        for(j=1;j<=m;j++)
        {
            Ans+=cap=read();
            add(Cag(i,j)+Max,T,cap);//選理科
        }
    for(i=1;i<=n;i++)
        for(j=1;j<=m;j++)
        {
            Ans+=cap=read();
            to=Cag(i,j)+Max+Max;
            add(S,to,cap);
            add(to,Cag(i,j),INF);//別忘了自己
            for(k=0;k<4;k++)//組合
            {
                Tx=i+Cx[k];Ty=j+Cy[k];
                if(!p[Tx][Ty])
                    add(to,Cag(Tx,Ty),INF);
            }
        }
    for(i=1;i<=n;i++)
        for(j=1;j<=m;j++)
        {
            Ans+=cap=read();
            to=Cag(i,j)+Max+Max+Max;
            add(to,T,cap);
            add(Cag(i,j)+Max,to,INF);
            for(k=0;k<4;k++)
            {
                Tx=i+Cx[k];Ty=j+Cy[k];
                if(!p[Tx][Ty])
                    add(Cag(Tx,Ty)+Max,to,INF);
            }
        }
    while(BFS())
        while((flow=Dinic(S,INF)))
            Maxflow+=flow;
    printf("%d",Ans-Maxflow);//最後還是挺容易的
    return 0;
}

推薦題目:

Luogu P2057 [SHOI2007]善意的投票