1. 程式人生 > >codevs 1922 騎士共存問題||二分圖||最大獨立集||二分圖匹配||Dinic與匈牙利演算法的討論||網路流

codevs 1922 騎士共存問題||二分圖||最大獨立集||二分圖匹配||Dinic與匈牙利演算法的討論||網路流

**

1922 騎士共存問題

**
**

題目描述 Description

**
在一個n*n個方格的國際象棋棋盤上,馬(騎士)可以攻擊的棋盤方格如圖所示。棋盤
上某些方格設定了障礙,騎士不得進入。

對於給定的n*n個方格的國際象棋棋盤和障礙標誌,計算棋盤上最多可以放置多少個騎
士,使得它們彼此互不攻擊。

**

輸入描述 Input Description

**
第一行有2 個正整數n 和m (1<=n<=200, 0<=m < n^2),
分別表示棋盤的大小和障礙數。接下來的m 行給出障礙的位置。每行2 個正整數,表示障礙的方格座標。

**

輸出描述 Output Description

**
將計算出的共存騎士數輸出

**

樣例輸入 Sample Input

**
3 2

1 1

3 3

**

樣例輸出 Sample Output

**
5

**

資料範圍及提示 Data Size & Hint

**
詳見試題

**

Solution

**

對二分圖的簡單證明:

我們可以先畫一張圖 任意選一個格點記為 1 與他能攻擊的格點記為2 然後不斷拓展下去 我們最後會發現:1和2不會重複出現==>是兩個不相交的集合

類似醬~~
1 2 1 2 1 2
2 1 2 1 2 1
1 2 1 2 1 2
2 1 2 1 2 1
1 2 1 2 1 2
2 1 2 1 2 1

原因我們可以簡單證明 設橫向絕對值為2縱向絕對值為1 的走法走了 x次  縱向為1 橫向為2 的走法走了y次。只有x 與 y 同奇(同偶)才可以使得起點到重點的delta x 與 delta y 同奇(同偶)

反亦反之.

以下摘自某神題解。

Solution_ID:296

【問題分析】

二分圖最大獨立集,轉化為二分圖最大匹配,從而用最大流解決。

【建模方法】

首先把棋盤黑白染色,使相鄰格子顏色不同。把所有可用的黑色格子看做二分圖X集合中頂點,可用的白色格子看做Y集合頂點。建立附加源S匯T,從S向X集合中每個頂點連線一條容量為1的有向邊,從Y集合中每個頂點向T連線一條容量為1的有向邊。從每個可用的黑色格子向騎士一步能攻擊到的可用的白色格子連線一條容量為無窮大的有向邊。求出網路最大流,要求的結果就是可用格子的數量減去最大流量。

【建模分析】

用網路流的方法解決棋盤上的問題,一般都要對棋盤黑白染色,使之成為一個二分圖。放盡可能多的不能互相攻擊的騎士,就是一個二分圖最大獨立集問題。有關二分圖最大獨立集問題,更多討論見《最小割模型在資訊學競賽中的應用》作者胡伯濤。

該題規模比較大,需要用效率較高的網路最大流演算法解決。

二分圖最大獨立集求法證明:

二分圖的最大獨立集

如果一個圖是二分圖,那麼它的最大獨立集就是多項式時間可以解決的問題了 |最大獨立集| = |V|-|最大匹配數|
證明:
設最大獨立集數為U,最大匹配數為M,M覆蓋的頂點集合為EM。
為了證明|U|=|V|-|M|,我們分兩步證明|U|<=|V|-|M|和|U|>=|V|-|M|
1 先證明 |U|<=|V|-|M|
M中的兩個端點是連線的,所有M中必有一個點不在|U|集合中,所以|M|<=|V|-|U|
2 再證明|U|>=|V|-|M|
假設(x,y)屬於M
首先我們知道一定有|U|>=|V|-|EM|,那麼我們將M集合中的一個端點放入U中可以嗎?
假設存在(a,x),(b,y),(a,b)不在EM集合中
如果(a,b)連線,則有一個更大的匹配存在,矛盾
如果(a,b)不連線,a->x->y->b有一個新的增廣路,因此有一個更大的匹配,矛盾
所以我們可以瞭解到取M中的一個端點放入U中肯定不會和U中的任何一個點相連,所以|U|>=|V|-|EM|+|M|=|V|-|M|
所以,|U|=|V|-|M|

讓我們來試一下Dinic匈牙利的區別

先貼一份TLE掉2個點的程式碼…有一些地方可以優化…明天再試…

**

Code

**

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn=210;

struct data{int to,next;}e[maxn*maxn*4];
int mat[maxn*maxn],n,m,mark[maxn][maxn],tot,head[maxn*maxn],cnt,ans;
void ins(int u,int v){cnt++;e[cnt].to=v;e[cnt].next=head[u];head[u]=cnt;}
int xx[8]={2,2,-2,-2,1,1,-1,-1},yy[8]={1,-1,1,-1,2,-2,2,-2};
bool used[maxn*maxn];
bool dfs(int x)
{
    for(int i=head[x];i;i=e[i].next)
        if(!used[e[i].to])
        {
            used[e[i].to]=1;
            if(!mat[e[i].to]||dfs(mat[e[i].to]))
            {
                mat[e[i].to]=x;
                return true;
            }
        }
    return false;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)mark[i][j]=++tot;
    for(int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        mark[x][y]=0;
    }
    for(int i=1;i<=n;i++)
        for(int j=(i%2==0?2:1);j<=n;j+=2)
        {
            if(mark[i][j])
            for(int k=0;k<8;k++)
            {
                int nx=i+xx[k],ny=j+yy[k];
                if(nx<1||nx>n||ny<1||ny>n||!mark[nx][ny])continue;
                ins(mark[i][j],mark[nx][ny]);
            }
        }
    for(int i=1;i<=n;i++)
        for(int j=(i%2==0?2:1);j<=n;j+=2)
            if(mark[i][j])
            {
                memset(used,0,sizeof(used));
                if(dfs(mark[i][j]))
                    ans++;
            }
    printf("%d",tot-ans-m);
    return 0;
}
測試點#kni0.in 結果:AC 記憶體使用量: 256kB 時間使用量: 1ms 
測試點#kni1.in 結果:AC 記憶體使用量: 256kB 時間使用量: 1ms 
測試點#kni10.in 結果:TLE 記憶體使用量: 1516kB 時間使用量: 2000ms 
測試點#kni2.in 結果:AC 記憶體使用量: 256kB 時間使用量: 1ms 
測試點#kni3.in 結果:AC 記憶體使用量: 256kB 時間使用量: 1ms 
測試點#kni4.in 結果:AC 記憶體使用量: 256kB 時間使用量: 1ms 
測試點#kni5.in 結果:AC 記憶體使用量: 364kB 時間使用量: 1ms 
測試點#kni6.in 結果:AC 記憶體使用量: 360kB 時間使用量: 1ms 
測試點#kni7.in 結果:AC 記憶體使用量: 364kB 時間使用量: 4ms 
測試點#kni8.in 結果:AC 記憶體使用量: 1128kB 時間使用量: 193ms 
測試點#kni9.in 結果:TLE 記憶體使用量: 2156kB 時間使用量: 2000ms 

Updata… 並沒有什麼卵用…

去試一下Dinic…

我趙日天不服….我葉良辰不服…為什麼第一個題解就可以過…他分明用vector存的…為什麼比我大鏈式前向星快!

Update

想爆粗口奈何…

A掉惹QAQ…

起初按著題解的存邊方式存的…然後舒緩心情(我不會告訴你們是在玩 Flappy bird的TAT)發現…鏈式前向星的存法是反著的!!!
然後就沒有然後了QAQ…
現在再去試一下Dinic…



Dinic有種棄療的感覺QAQ…母雞為何WA了…
頓時想呵呵自己一臉…插反邊的時候容量給插成w了….TAT….

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

const int maxn=201,S=maxn*maxn-2,T=maxn*maxn-1,inf=(1<<30);
struct data{int to,w,next;}e[maxn*maxn*10];
int head[maxn*maxn],cnt=1,mark[maxn][maxn],n,m,h[maxn*maxn],tot;
void ins(int u,int v,int w){cnt++;e[cnt].to=v;e[cnt].next=head[u];head[u]=cnt;e[cnt].w=w;}
void insert(int u,int v,int w){ins(u,v,w);ins(v,u,0);}
int xx[8]={-1, -2, -1, -2, 2, 2, 1, 1},yy[8]={2, 1, -2, -1, -1, 1, -2, 2};
bool bfs(){
    memset(h,-1,sizeof(h));
    h[S]=0;
    queue<int> q;
    q.push(S);
    while(!q.empty())
    {
        int x=q.front();q.pop();
        for(int i=head[x];i;i=e[i].next)
            if(e[i].w&&h[e[i].to]<0)
            {
                q.push(e[i].to);
                h[e[i].to]=h[x]+1;
            }
    }
    if(h[T]==-1)return 0;
    return 1;
}
int dfs(int x,int f){
    if(x==T)return f;
    int w,used=0;
    for(int i=head[x];i;i=e[i].next)
    if(e[i].w&&h[e[i].to]==h[x]+1)
    {
        w=f-used;
        w=dfs(e[i].to,min(w,e[i].w));
        e[i].w-=w;
        e[i^1].w+=w;
        used+=w;
        if(used==f)return f;
    }
    if(!used)h[x]=-1;
    return used;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            mark[i][j]=++tot;
    for(int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        mark[x][y]=0;
    }
    for(int i=1;i<=n;i++)
        for(int j=(i%2==0?2:1);j<=n;j+=2)
        {
            insert(S,mark[i][j],1);
            if(mark[i][j])
            for(int k=0;k<8;k++)
            {
                int nx=i+xx[k],ny=j+yy[k];
                if(nx<1||nx>n||ny<1||ny>n||!mark[nx][ny])continue;
                insert(mark[i][j],mark[nx][ny],1);
            }
        }
    for(int i=1;i<=n;i++)
        for(int j=((i%2==0)?1:2);j<=n;j+=2)
            if(mark[i][j])
            insert(mark[i][j],T,1);
    int ans=0;
    while(bfs())ans+=dfs(S,inf);
    printf("%d\n",n*n-m-ans);
    return 0;
}
測試點#kni0.in  結果:AC    記憶體使用量:  360kB     時間使用量:  0ms     
測試點#kni1.in  結果:AC    記憶體使用量:  364kB     時間使用量:  1ms     
測試點#kni10.in  結果:AC    記憶體使用量:  3692kB     時間使用量:  291ms     
測試點#kni2.in  結果:AC    記憶體使用量:  364kB     時間使用量:  1ms     
測試點#kni3.in  結果:AC    記憶體使用量:  364kB     時間使用量:  1ms     
測試點#kni4.in  結果:AC    記憶體使用量:  360kB     時間使用量:  0ms     
測試點#kni5.in  結果:AC    記憶體使用量:  488kB     時間使用量:  1ms     
測試點#kni6.in  結果:AC    記憶體使用量:  492kB     時間使用量:  0ms     
測試點#kni7.in  結果:AC    記憶體使用量:  620kB     時間使用量:  4ms     
測試點#kni8.in  結果:AC    記憶體使用量:  2152kB     時間使用量:  88ms     
測試點#kni9.in  結果:AC    記憶體使用量:  5484kB     時間使用量:  163ms 

Dinic的確要比匈牙利演算法快得多…

嗶嗶嗶= =【這裡之前錯掉了# #】
更正一下 匈牙利的複雜度是O(nm)【實在抱歉- -】[這裡感謝 @凱樂報 指正]
而且記得貌似當邊的容量為1的時候dinic的時間複雜度會下降,但是找不到了…QAQ

Update: Dinic在這裡的複雜度應該是O(m*sqrt(n))

對了-補一下Hungry的程式碼,對比一下

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn=210;

struct data{int to,next;}e[maxn*maxn*4];
int mat[maxn*maxn],n,m,mark[maxn][maxn],tot,head[maxn*maxn],cnt,ans,map[maxn*maxn];
void ins(int u,int v){cnt++;e[cnt].to=v;e[cnt].next=head[u];head[u]=cnt;}
int xx[8]={-1, -2, -1, -2, 2, 2, 1, 1},yy[8]={2, 1, -2, -1, -1, 1, -2, 2};
bool used[maxn*maxn];
bool dfs(int x)
{
    for(int i=head[x];i;i=e[i].next)
        if(!used[e[i].to])
        {
            used[e[i].to]=1;
            if(!mat[e[i].to]||dfs(mat[e[i].to]))
            {
                mat[e[i].to]=x;
                return true;
            }
        }
    return false;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)mark[i][j]=++tot;
    for(int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        mark[x][y]=0;
    }
    for(int i=1;i<=n;i++)
        for(int j=(i%2==0?2:1);j<=n;j+=2)
        {
            if(mark[i][j])
            for(int k=0;k<8;k++)
            {
                int nx=i+xx[k],ny=j+yy[k];
                if(nx<1||nx>n||ny<1||ny>n||!mark[nx][ny])continue;
                ins(mark[i][j],mark[nx][ny]);
            }
        }
    cnt=tot;
    tot=0;
    for(int i=1;i<=n;i++)
        for(int j=(i%2==0?2:1);j<=n;j+=2)
            if(mark[i][j])
            {
                memset(used,0,n*n);
                if(dfs(mark[i][j]))
                    ans++;
            }
    printf("%d",cnt-ans-m);
    return 0;
}
執行結果
測試點#kni0.in 結果:AC 記憶體使用量: 256kB 時間使用量: 1ms 
測試點#kni1.in 結果:AC 記憶體使用量: 256kB 時間使用量: 1ms 
測試點#kni10.in 結果:AC 記憶體使用量: 1516kB 時間使用量: 683ms 
測試點#kni2.in 結果:AC 記憶體使用量: 256kB 時間使用量: 1ms 
測試點#kni3.in 結果:AC 記憶體使用量: 256kB 時間使用量: 0ms 
測試點#kni4.in 結果:AC 記憶體使用量: 256kB 時間使用量: 0ms 
測試點#kni5.in 結果:AC 記憶體使用量: 256kB 時間使用量: 1ms 
測試點#kni6.in 結果:AC 記憶體使用量: 256kB 時間使用量: 1ms 
測試點#kni7.in 結果:AC 記憶體使用量: 364kB 時間使用量: 0ms 
測試點#kni8.in 結果:AC 記憶體使用量: 1004kB 時間使用量: 68ms 
測試點#kni9.in 結果:AC 記憶體使用量: 2540kB 時間使用量: 100ms