1. 程式人生 > >洛谷P2774 方格取數問題 BZOJ 1143祭祀river【二分圖最大獨立集】

洛谷P2774 方格取數問題 BZOJ 1143祭祀river【二分圖最大獨立集】

網絡 方格取數 == 二分圖最大獨立集 ostream pro 就會 最大流 HR

講解前首先引入兩個概念

二分圖最小點覆蓋集

定義:

在二分圖中求出一個最小點集
使得圖中任意一條邊至少有一個端點在點集內

解法:

對二分圖進行最大匹配
最大匹配數就是二分圖的最小點覆蓋集包含的點數
***************************************

二分圖最大獨立集

定義:

對於一張無向圖
求出一個點數最大的點集
使得點集中任意兩點沒有邊相連
這是圖的最大獨立集

而二分圖的最大獨立集就是字面意思

解法:

對於一個有n個結點的二分圖
它的最大獨立集包含的點數就是n-最大匹配數
************************************************

二分圖最大點權獨立集

定義

要求點權和最大的二分圖獨立集

解法:

超源向所有x部的點連邊,容量為該點點權
y部所有點向超匯連邊,容量為該點點權
對於二分圖原有的邊,容量為inf
然後跑最大流即可


洛谷 P2774 方格取數問題

傳送門

題目描述

在一個有 m*n 個方格的棋盤中,每個方格中有一個正整數。現要從方格中取數,使任意 2 個數所在方格沒有公共邊,且取出的數的總和最大。試設計一個滿足要求的取數算法。對於給定的方格棋盤,按照取數要求編程找出總和最大的數。

輸入格式:

第 1 行有 2 個正整數 m 和 n,分別表示棋盤的行數和列數。接下來的 m 行,每行有 n 個正整數,表示棋盤方格中的數。

輸出格式:

程序運行結束時,將取數的最大總和輸出

輸入樣例

3 3
1 2 3
3 2 3
2 3 1

輸出樣例

11

說明

m,n<=100
***********************************

分析

這題要求所取的方格不相鄰
那麽對每個格子黑白染色
我們由黑點向所有與他相鄰的白點連邊
就構成了一個二分圖

到這裏不難發現題目要求的就是二分圖的最大點權獨立集
點權!!!是點權!!!

所以這裏我們就用上述方法解決
所有黑點向超匯連邊,容量為該點點權
超源向所有白點連邊,容量為該點點權
對於上面所述用於構成二分圖的邊,容量為inf
然後跑最大流就好


#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<‘0‘||ss>‘9‘){if(ss==‘-‘)f=-1;ss=getchar();}
    while(ss>=‘0‘&&ss<=‘9‘){x=x*10+ss-‘0‘;ss=getchar();}
    return f*x;
}

const int inf=1e9;
int n,m;
int s=0,t;
int col[110][110];
struct node{int v,f,nxt;}E[1000010];
int head[100010],tot=1;;
int lev[100010];
int addx[]={0,0,1,-1},addy[]={1,-1,0,0};
int sum,ans;

void add(int u,int v,int f)
{
    E[++tot].nxt=head[u];
    E[tot].f=f;
    E[tot].v=v;
    head[u]=tot;
}

bool bfs()
{
    memset(lev,-1,sizeof(lev)); lev[s]=0;
    queue<int> q; q.push(s);
    
    while(!q.empty())
    {
        int u=q.front(); q.pop();
        for(int i=head[u];i;i=E[i].nxt)
        {
            int v=E[i].v;
            if(lev[v]==-1&&E[i].f)
            {
                lev[v]=lev[u]+1;
                if(v==t) return true;
                q.push(v);
            }
        }
    }
    return false;
}

int dfs(int u,int cap)
{
    if(u==t) return cap;
    int flow=cap;
    
    for(int i=head[u];i;i=E[i].nxt)
    {
        int v=E[i].v;
        if(lev[v]==lev[u]+1&&E[i].f>0&&flow)
        {
            int f=dfs(v,min(E[i].f,flow));
            flow-=f;
            E[i].f-=f;
            E[i^1].f+=f;
        }
    }
    return cap-flow;
}

int main()
{
    n=read();m=read();
    t=n*m+1;
    
    for(int i=1;i<=n;i++)//黑白染色,1為黑,0為白
    for(int j=1;j<=m;j++)
    {
        if( (i%2 && j%2) || (i%2==0 && j%2==0)) col[i][j]=1;
        else col[i][j]=0;
    }
    
    for(int i=1;i<=n;i++)//構圖
    {
        for(int j=1;j<=m;j++)
        {
            int x=read(); sum+=x;
            int num=m*(i-1)+j;//計算該點的編號
            if(col[i][j]) add( num,t,x ),add( t,num,0 );//黑點向超匯連邊
            else
            {
                add(s,num,x);add(num,s,0);//超源向白點連邊
                for(int k=0;k<4;k++)//原二分圖中的邊
                {
                    int nx=i+addx[k],ny=j+addy[k];
                    if(nx>0&&nx<=n &&ny>0&&ny<=m)
                    add(num,m*(nx-1)+ny,inf),add(m*(nx-1)+ny,num,0);
                }
            }
        }
    }
    
    while(bfs())//最大流
    ans+=dfs(s,inf);
    
    cout<<sum-ans;
    return 0;
}

BZOJ 1143 [CTSC2008]祭祀river

傳送門

Description

  在遙遠的東方,有一個神秘的民族,自稱Y族。他們世代居住在水面上,奉龍王為神。每逢重大慶典, Y族都
會在水面上舉辦盛大的祭祀活動。我們可以把Y族居住地水系看成一個由岔口和河道組成的網絡。每條河道連接著
兩個岔口,並且水在河道內按照一個固定的方向流動。顯然,水系中不會有環流

  由於人數眾多的原因,Y族的祭祀活動會在多個岔口上同時舉行。出於對龍王的尊重,這些祭祀地點的選擇必
須非常慎重。準確地說,Y族人認為,如果水流可以從一個祭祀點流到另外一個祭祀點,那麽祭祀就會失去它神聖
的意義。族長希望在保持祭祀神聖性的基礎上,選擇盡可能多的祭祀的地點。

Input

第一行包含兩個用空格隔開的整數N、M,分別表示岔口和河道的數目,岔口從1到N編號。
接下來M行,每行包含兩個用空格隔開的整數u、v,
描述一條連接岔口u和岔口v的河道,水流方向為自u向v。
N≤100M≤1000

Output

第一行包含一個整數K,表示最多能選取的祭祀點的個數。

Sample Input

4 4
1 2
3 4
3 2
4 2

Sample Output

2

【樣例說明】

在樣例給出的水系中,不存在一種方法能夠選擇三個或者三個以上的祭祀點。包含兩個祭祀點的測試點的方案有兩種:

選擇岔口1與岔口3(如樣例輸出第二行),選擇岔口1與岔口4。

水流可以從任意岔口流至岔口2。如果在岔口2建立祭祀點,那麽任意其他岔口都不能建立祭祀點

但是在最優的一種祭祀點的選取方案中我們可以建立兩個祭祀點,所以岔口2不能建立祭祀點。對於其他岔口

至少存在一個最優方案選擇該岔口為祭祀點,所以輸出為1011。
****************************

分析:

簡化題目意思
就是選取一個點集,其中任意兩點不在一條鏈上
並使得點數最大

這不就是圖的最大獨立集嘛!!!
然而要怎麽處理兩點是否在一條鏈上呢
傳遞閉包+floyd的思想

讀入時對於一條u到v的邊,使map[u][v]=1
再利用floyd將連通性傳遞

for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
map[i][j]|=(map[i][k]&map[k][j]);

傳遞完後直接在圖上跑二分圖最大匹配就好了


#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
 
int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<‘0‘||ss>‘9‘){if(ss==‘-‘)f=-1;ss=getchar();}
    while(ss>=‘0‘&&ss<=‘9‘){x=x*10+ss-‘0‘;ss=getchar();}
    return f*x;
}
 
const int maxn=110;
int n,m;
int map[maxn][maxn];
bool vis[maxn];
int match[maxn];
int ans;
 
bool dfs(int u)
{
    for(int v=1;v<=n;v++)
    {
        if(!map[u][v]||vis[v]) continue;
        vis[v]=true;
        if(!match[v]||dfs(match[v]))
        {
            match[v]=u;
            return true;
        }
    }
    return false;
}
 
int main()
{
    n=read();m=read();
    for(int i=1;i<=m;++i)
    {
        int u=read(),v=read();
        map[u][v]=1;
    }
    for(int k=1;k<=n;k++)//floyd實現傳遞閉包
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
    map[i][j]|=(map[i][k]&map[k][j]);
     
    for(int i=1;i<=n;i++)
    {
        memset(vis,false,sizeof(vis));
        if(dfs(i))ans++;
    }
     
    cout<<n-ans;
    return 0;
}
?

洛谷P2774 方格取數問題 BZOJ 1143祭祀river【二分圖最大獨立集】