**
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