Codevs3567 宮廷守衛

Codevs3567 宮廷守衛

Position:


List

Description

  從前有一個王國,這個王國的城堡是一個矩形,被分爲M×N個方格。一些方格是牆,而另一些是空地。這個王國的國王在城堡裏設了一些陷阱,每個陷阱佔據一塊空地。
   一天,國王決定在城堡里布置守衛,他希望安排儘量多的守衛。守衛們都是經過嚴格訓練的,所以一旦他們發現同行或同列中有人的話,他們立即向那人射擊。因此,國王希望能夠合理地佈置守衛,使他們互相之間不能看見,這樣他們就不可能互相射擊了。守衛們只能被佈置在空地上,不能被佈置在陷阱或牆上,且一塊空地只能佈置一個守衛。如果兩個守衛在同一行或同一列,並且他們之間沒有牆的話,他們就能互相看見。(守衛就像象棋裏的車一樣)
  你的任務是寫一個程序,根據給定的城堡,計算最多可佈置多少個守衛,並設計出佈置的方案。

Input

第一行兩個整數M和N(1≤M,N≤200),表示城堡的規模。
接下來M行N列的整數,描述的是城堡的地形。第i行j列的數用ai,j表示。
ai,j=0,表示方格[i,j]是一塊空地;
ai,j=1,表示方格[i,j]是一個陷阱;
ai,j=2,表示方格[i,j]是牆。

Output

第一行一個整數K,表示最多可佈置K個守衛。
此後K行,每行兩個整數xi和yi,描述一個守衛的位置。
(若有多解,請輸出字典序最小的那一種)

Sample Input

3 4
2 0 0 0
2 2 2 1
0 1 0 2

Sample Output

2
1 2
3 1
explain

HINT

1≤M,N≤200

這個題範圍小一點跑暴力還是可以的,拿來練手,當搜索練習題很好,其中有很多優化,剪枝,可以拿到50分。

剪枝

  1. 記錄當前搜索到的點(x,y),放了之後,預處理連續一段不能選的。見Code→lef數組
  2. 如果上面這列填了,那麼就不填了。見Code→f數組。
  3. 並且要用down記錄(x,y)選了,往下哪一段不能選。見Code→dow數組。
  4. 每掃過一行,看f數組要不要更新。見Code→b數組,並且當前要用d記錄。
  5. 對於橫着每一段,選擇一個,那麼搜索順序呢?sort每個地方選了,下面有多少個不能選,從小到大搜,可以保證下面選的方案更多。見Code→c數組
  6. 沒選完一個(x,y),記錄之後有多少個空地還可以選。如果加上還小於當前ans,就可以return了。見Code→MA數組,記錄(x,y)x這排y列之後有多少空地(0)。
  7. 搜索當然可以卡時啦。見if(clock()>CLOCKS_PER_SEC*0.963)pri();

Code

// <guards.cpp> - Fri Sep 23 08:09:06 2016
// This file is made by YJinpeng,created by XuYike's black technology automatically.
// Copyright (C) 2016 ChangJun High School, Inc.
// I don't know what this program is.

#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <ctime>
#define MOD 1000000007
#define INF 1e9
using namespace std;
typedef long long LL;
const int MAXN=210;
const int MAXM=40010;
inline int max(int &x,int &y) {return x>y?x:y;}
inline int min(int &x,int &y) {return x<y?x:y;}
inline int gi() {
    register int w=0,q=0;register char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')q=1,ch=getchar();
    while(ch>='0'&&ch<='9')w=w*10+ch-'0',ch=getchar();
    return q?-w:w;
}
int ans,n,m;bool f[MAXN];int b[MAXN];
int a[MAXN][MAXN],lef[MAXN][MAXN],dow[MAXN][MAXN],MA[MAXN][MAXN];
struct gg{int x,y;}g[MAXM],as[MAXM];
struct node{
    int p,w;
    bool operator < (node a)const{return w<a.w;}
};
void pri(){
    printf("%d\n",ans);
    for(int i=1;i<=ans;i++)printf("%d %d\n",as[i].x,as[i].y);
    exit(0);
}
inline void work(register int x,register int y,register int nu){
    if(clock()>CLOCKS_PER_SEC*0.963)pri();
    if(x==n&&y>m){
        if(nu>ans){ans=nu;for(int i=1;i<=nu;i++)as[i]=g[i];}
        return;
    }
    if(y>m){
        int d[MAXN];
        for(int i=1;i<=m;i++){if(x==b[i])f[i]=0;d[i]=b[i];}
        work(x+1,1,nu);
        for(int i=1;i<=m;i++){b[i]=d[i];if(x==b[i])f[i]=1;}
        return;
    }
    int k=MA[x][y];
    for(int i=x+1;i<=n;i++)k+=MA[i][1];
    if(nu+k<=ans)return;
    int tot=0;node c[MAXN];
    for(int i=y;i<lef[x][y];i++){
        if(f[i]||a[x][i])continue;
        c[++tot]=(node){i,dow[x][i]};
    }
    if(!tot){work(x,lef[x][y]+1,nu);return;}
    sort(c+1,c+1+tot);
    for(int i=1;i<=tot;i++){
        f[c[i].p]=1,b[c[i].p]=x+c[i].w;
        g[nu+1]=(gg){x,c[i].p};
        work(x,lef[x][y]+1,nu+1);
        f[c[i].p]=(bool)(b[c[i].p]=0);
    }
}
int main()
{
    freopen("guards.in","r",stdin);
    freopen("guards.out","w",stdout);
    n=gi(),m=gi();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)a[i][j]=gi();
    for(int i=1;i<=n;i++)lef[i][m+1]=m+1;
    for(int i=1;i<=n;i++)
        for(int j=m;j>=1;j--){
            if(a[i][j]==2){lef[i][j]=j;MA[i][j]=MA[i][j+1];continue;}
            lef[i][j]=lef[i][j+1];
            if(a[i][j]==1&&lef[i][j]==j+1){MA[i][j]=MA[i][j+1];continue;}
            MA[i][j]=MA[i][lef[i][j]+1]+1;
        }
    for(int i=1;i<=m;i++)
        for(int j=n;j>=1;j--){
            if(a[j][i]==2){dow[j][i]=0;continue;}
            dow[j][i]=dow[j+1][i]+1;
        }
    memset(f,false,sizeof(f));
    work(1,1,0);pri();
    return 0;
}

Solution

這道題是二分圖的經典模型。
最關鍵的地方在與建圖,我們把橫着的一條(兩邊爲2),只能選一個的抽出來。把豎着的一條(兩邊爲2),只能選一個的抽出來。發現選擇一個點放守衛,即爲橫的和豎着的交點,進行連邊,跑二分圖保證了,橫的豎的一條每條最多用一次。問題就解決了。
點數最多n^2,還達不到,n^2/2,因爲一個點的形成兩邊都要是牆,並且陷阱也不能連邊。

Code

// <guards.cpp> - Fri Sep 23 08:09:06 2016
// This file is made by YJinpeng,created by XuYike's black technology automatically.
// Copyright (C) 2016 ChangJun High School, Inc.
// I don't know what this program is.

#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <ctime>
#define MOD 1000000007
#define INF 1e9
using namespace std;
typedef long long LL;
const int MAXN=210;
const int MAXM=40010;
inline int max(int &x,int &y) {return x>y?x:y;}
inline int min(int &x,int &y) {return x<y?x:y;}
inline int gi() {
    register int w=0,q=0;register char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')q=1,ch=getchar();
    while(ch>='0'&&ch<='9')w=w*10+ch-'0',ch=getchar();
    return q?-w:w;
}
int match[MAXM],X[MAXM],Y[MAXM];
int a[MAXN][MAXN],x[MAXN][MAXN],y[MAXN][MAXN];
vector<int>b[MAXM];int f[MAXM],cnt;
inline void add(int u,int v){b[u].push_back(v);}
bool dfs(int x){
    if(f[x]==cnt)return 0;
    int num=b[x].size();f[x]=cnt;
    for(int i=0;i<num;i++){
        int nex=b[x][i];
        if(match[nex]==-1||dfs(match[nex])){
            match[x]=nex;match[nex]=x;/*f[x]=0;*/return 1;
        }
    }//f[x]=0這句害死人,因爲下一次又會再次調用dfs
    return 0;
}
int main()
{
    freopen("guards.in","r",stdin);
    freopen("guards.out","w",stdout);
    int n=gi(),m=gi(),t1=-1,t2=-1,ans=0;
    for(int i=1;i<=n;i++)a[i][0]=2;
    for(int i=1;i<=m;i++)a[0][i]=2;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            a[i][j]=gi();
            if(a[i][j-1]==2)++t1,X[t1]=i;
            x[i][j]=t1;
        }
    for(int j=1;j<=m;j++)
        for(int i=1;i<=n;i++){
            if(a[i-1][j]==2)++t2,Y[t2]=j;
            y[i][j]=t2;
        }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            if(a[i][j])continue;
            add(x[i][j],y[i][j]+t1+1);
        }
    memset(f,false,sizeof(f));
    for(int i=0;i<=t1+t2+1;i++)match[i]=-1;
    for(int i=0;i<=t1;i++)
        if(match[i]==-1){
            cnt++;if(dfs(i))++ans;
            //memset(f,0,sizeof(f));這個不好複雜度高
        }
    printf("%d\n",ans);
    for(int i=0;i<=t1;i++)
        if(match[i]!=-1)printf("%d %d\n",X[i],Y[match[i]-t1-1]);
    return 0;
}

Compare

Codevs提交不了(沒有SPJ)WA
我在這裏發一個pascal的spj

Program Compare;

{ 本程序爲A+B的比較程序,本程序僅作爲比較程序的樣例。 }

var
  Inf, Ouf, Std, Log: String;

procedure GetParams;
begin
  Inf := Paramstr(1); { 標準輸入文件 }
  Ouf := Paramstr(2); { 選手輸出文件 }
  Std := Paramstr(3); { 標準輸出文件 }
  Log := Paramstr(5); { 結果文件 }
end;

procedure WriteLog(Score: integer; Description: String);
{ 將比較結果寫入結果文件中,其中Score爲選手的得分百分比,Description爲註釋。 }
begin
  assign(output, Log); rewrite(output);
  //writeln(Score*10, ' ', Description);
  writeln(Score*10);
  close(output);
  Halt;
end;

var
  a:array[1..200,1..200]of longint;
  x,y:array[1..40000]of longint;
  m,n: Longint; { A與B的值 }
  Sum: Longint; { A+B的標準答案 }
  Ans: Longint; { 選手的輸出 }

procedure InfRead;
{ 從輸入文件中輸入A與B的值。 }
var
  i,j:longint;
begin
  {$i-}
  assign(input, Inf); reset(input);
  readln(m,n);
  for i:=1 to m do
     for j:=1 to n do
         read(a[i,j]);
  close(input);
  {$i+}
  if IOResult <> 0 then WriteLog(0, '輸入文件錯誤!');
end;

procedure StdRead;
{ 從標準輸出中輸入A與B的和 }
begin
  {$i-}
  assign(input, Std); reset(input);
  read(Sum);
  close(input);
  {$i+}
  if IOResult <> 0 then begin
    WriteLog(0, '標準輸出文件錯誤!');
    halt;
  end;
end;

procedure OufRead;
{ 從輸出中輸入選手的答案 }
var
  i:longint;
begin
  {$i-}
  assign(input, Ouf); reset(input);
  readln(Ans);
  if Ans<Sum then begin writelog(0,'守衛放置不是最優的'); close(input); halt; end;
  for i:=1 to ans do
      begin
         readln(x[i],y[i]);
         a[x[i],y[i]]:=3;
      end;
  close(input);
  {$i+}
  if IOResult <> 0 then WriteLog(0, '輸出文件錯誤!');
  if ans=sum then writelog(1, '正確')
  else writelog(0, '錯誤');
end;

procedure Check;
{ 比較選手的解與標準答案 }
var
  i,j,k:longint;
begin
    for i:=1 to ans do
       begin
           j:=x[i];k:=y[i];
           if a[j,k]=1 then begin writelog(0,'踩在陷阱上'); halt; end;
           j:=x[i]+1;
           while (a[j,k]<>2)and(j<=n) do
             begin
                 if a[j,k]=3 then begin writelog(0,'有互相攻擊的一對士兵'); halt; end;
                 inc(j);
             end;
           j:=x[i]-1;
           while (a[j,k]<>2)and(j>0) do
             begin
                 if a[j,k]=3 then begin writelog(0,'有互相攻擊的一對士兵'); halt; end;
                 dec(j);
             end;
           j:=x[i];k:=y[i]+1;
           while (a[j,k]<>2)and(k<=m) do
             begin
                 if a[j,k]=3 then begin  writelog(0,'有互相攻擊的一對士兵'); halt; end;
                 inc(k);
             end;
           k:=y[i]-1;
           while (a[j,k]<>2)and(k>0) do
             begin
                 if a[j,k]=3 then begin writelog(0,'有互相攻擊的一對士兵'); halt; end;
                 dec(k);
             end;
       end;
    writelog(1,'正確');
end;

begin
  GetParams;
  InfRead;
  StdRead;
  OufRead;
//  Check;
end.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章