P1035 棋盤覆蓋 (二分圖匹配、最大網路流)
描述
給出一張n*n(n<=100)的國際象棋棋盤,其中被刪除了一些點,問可以使用多少1*2的多米諾骨牌進行掩蓋。輸入格式
第一行為n,m(表示有m個刪除的格子)第二行到m+1行為x,y,分別表示刪除格子所在的位置
x為第x行
y為第y列
輸出格式
一個數,即最大覆蓋格數二分圖最大匹配
二維的棋盤可以轉換為一維來表示,比如8*8棋盤上的點(6,5),轉換為相應的一維id=44,點(x,y)轉換公式為id=(x-1)*n+y-1 。
首先給用bfs或者dfs給棋盤染色,相鄰的點染成不同的顏色,忽略被刪除的點,dfs會爆棧,這裡使用bfs。
特別注意例如以下情況,若bfs或dfs只以找到的第一個未刪除的點為起點進行,則無法將如圖“中間”的點進行正確染色導致匹配數不正確。
OO OOOO
OX XXXO
OX OOXO
OX OOXO
OX XXXO
OO OOOO
-------------------------------------------------------------------------------------------------------------------------------------------------------------
1.使用HopcroftKarp演算法,遍歷棋盤,給顏色不同且相鄰的點新增一條邊,所求就是最大匹配數。O((E + V)
sqrt(V))
------------------------------------------------------------------------------------------------------------------------------------------
package cn.UnitTest; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Iterator; import java.util.Scanner; import java.util.Stack; public class Main { public static void main(String[] args) { cover1 cov=new cover1(); System.out.println(cov.getmaxmatch()); } } class cover1 { private int n; private int m; private boolean[] visited; private boolean[] isdelete; private boolean[] color; private HopcroftKarp h; private ArrayDeque<storexy> deque=new ArrayDeque<>(); public cover1() { Scanner cin=new Scanner(System.in); n=cin.nextInt(); m=cin.nextInt(); isdelete=new boolean[n*n]; visited=new boolean[n*n]; color=new boolean[n*n]; for(int i=0;i<m;i++) { int x=cin.nextInt(); int y=cin.nextInt(); isdelete[toid(x, y)]=true; } L1: for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { int id=toid(i, j); if(!isdelete[id]) { color[id]=true; deque.add(new storexy(i, j)); break L1; } } while(!deque.isEmpty()) { storexy xy=deque.pollFirst(); bfs(xy.getx(),xy.gety()); } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { int id=toid(i, j); if(!isdelete[id]&&!visited[id]) { color[id]=true; deque.add(new storexy(i, j)); } } while(!deque.isEmpty()) { storexy xy=deque.pollFirst(); bfs(xy.getx(),xy.gety()); } Graph G=new Graph(n*n); visited=new boolean[n*n]; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { if(!isdelete[toid(i, j)]&&color[toid(i, j)]==true) { int[] xoff={-1,1,0,0}; int[] yoff={0,0,1,-1}; for(int k=0;k<4;k++) { int xx=i+xoff[k]; int yy=j+yoff[k]; if(xx>=1&&xx<=n&&yy>=1&&yy<=n) { int id=toid(xx, yy); if(!isdelete[id]&&color[id]==false) G.addEdge(toid(i, j), id); } } } } h=new HopcroftKarp(G); } private int toid(int x,int y) { return (x-1)*n+y-1; } private void bfs(int x,int y) { visited[toid(x, y)]=true; boolean colornow=color[toid(x, y)]; int[] xoff={-1,1,0,0}; int[] yoff={0,0,1,-1}; for(int i=0;i<4;i++) { int xx=x+xoff[i]; int yy=y+yoff[i]; if(xx>=1&&xx<=n&&yy>=1&&yy<=n&&!isdelete[toid(xx, yy)]&&!visited[toid(xx, yy)]) { color[toid(xx, yy)]=!colornow; storexy xy=new storexy(xx, yy); if(!deque.contains(xy)) deque.add(xy); } } } public int getmaxmatch() { return h.size(); } } class storexy { private int x; private int y; public storexy(int x,int y) { this.x=x; this.y=y; } @Override public boolean equals(Object obj) { // TODO Auto-generated method stub return (x==((storexy)obj).getx()&&y==((storexy)obj).gety()); } public int getx() { return x; } public int gety() { return y; } } class HopcroftKarp { private static final int UNMATCHED = -1; private final int V; private BipartiteX bipartition; private int cardinality; private int[] mate; private boolean[] inMinVertexCover; private boolean[] marked; private int[] distTo; public HopcroftKarp(Graph G) { bipartition = new BipartiteX(G); if (!bipartition.isBipartite()) { throw new IllegalArgumentException("graph is not bipartite"); } this.V = G.V(); mate = new int[V]; for (int v = 0; v < V; v++) mate[v] = UNMATCHED; while (hasAugmentingPath(G)) { @SuppressWarnings("unchecked") Iterator<Integer>[] adj = (Iterator<Integer>[]) new Iterator[G.V()]; for (int v = 0; v < G.V(); v++) adj[v] = G.adj(v).iterator(); for (int s = 0; s < V; s++) { if (isMatched(s) || !bipartition.color(s)) continue; Stack<Integer> path = new Stack<Integer>(); path.push(s); while (!path.isEmpty()) { int v = path.peek(); if (!adj[v].hasNext()) path.pop(); else { int w = adj[v].next(); if (!isLevelGraphEdge(v, w)) continue; path.push(w); if (!isMatched(w)) { while (!path.isEmpty()) { int x = path.pop(); int y = path.pop(); mate[x] = y; mate[y] = x; } cardinality++; } } } } } inMinVertexCover = new boolean[V]; for (int v = 0; v < V; v++) { if (bipartition.color(v) && !marked[v]) inMinVertexCover[v] = true; if (!bipartition.color(v) && marked[v]) inMinVertexCover[v] = true; } } private boolean isLevelGraphEdge(int v, int w) { return (distTo[w] == distTo[v] + 1) && isResidualGraphEdge(v, w); } private boolean isResidualGraphEdge(int v, int w) { if ((mate[v] != w) && bipartition.color(v)) return true; if ((mate[v] == w) && !bipartition.color(v)) return true; return false; } private boolean hasAugmentingPath(Graph G) { marked = new boolean[V]; distTo = new int[V]; for (int v = 0; v < V; v++) distTo[v] = Integer.MAX_VALUE; ArrayDeque<Integer> queue = new ArrayDeque<Integer>(); for (int v = 0; v < V; v++) { if (bipartition.color(v) && !isMatched(v)) { queue.add(v); marked[v] = true; distTo[v] = 0; } } boolean hasAugmentingPath = false; while (!queue.isEmpty()) { int v = queue.pollFirst(); for (int w : G.adj(v)) { if (isResidualGraphEdge(v, w)) { if (!marked[w]) { distTo[w] = distTo[v] + 1; marked[w] = true; if (!isMatched(w)) hasAugmentingPath = true; if (!hasAugmentingPath) queue.add(w); } } } } return hasAugmentingPath; } public int mate(int v) { validate(v); return mate[v]; } public boolean isMatched(int v) { validate(v); return mate[v] != UNMATCHED; } public int size() { return cardinality; } public boolean isPerfect() { return cardinality * 2 == V; } public boolean inMinVertexCover(int v) { validate(v); return inMinVertexCover[v]; } private void validate(int v) { if (v < 0 || v >= V) throw new IndexOutOfBoundsException("vertex " + v + " is not between 0 and " + (V - 1)); } } class BipartiteX { private static final boolean WHITE = false; private boolean isBipartite; private boolean[] color; private boolean[] marked; private int[] edgeTo; private ArrayDeque<Integer> cycle; public BipartiteX(Graph G) { isBipartite = true; color = new boolean[G.V()]; marked = new boolean[G.V()]; edgeTo = new int[G.V()]; for (int v = 0; v < G.V() && isBipartite; v++) { if (!marked[v]) { bfs(G, v); } } assert check(G); } private void bfs(Graph G, int s) { ArrayDeque<Integer> q = new ArrayDeque<Integer>(); color[s] = WHITE; marked[s] = true; q.add(s); while (!q.isEmpty()) { int v = q.pollFirst(); for (int w : G.adj(v)) { if (!marked[w]) { marked[w] = true; edgeTo[w] = v; color[w] = !color[v]; q.add(w); } else if (color[w] == color[v]) { isBipartite = false; cycle = new ArrayDeque<>(); Stack<Integer> stack = new Stack<Integer>(); int x = v, y = w; while (x != y) { stack.push(x); cycle.add(y); x = edgeTo[x]; y = edgeTo[y]; } stack.push(x); while (!stack.isEmpty()) cycle.add(stack.pop()); cycle.add(w); return; } } } } public boolean isBipartite() { return isBipartite; } public boolean color(int v) { if (!isBipartite) throw new UnsupportedOperationException("Graph is not bipartite"); return color[v]; } public Iterable<Integer> oddCycle() { return cycle; } private boolean check(Graph G) { if (isBipartite) { for (int v = 0; v < G.V(); v++) { for (int w : G.adj(v)) { if (color[v] == color[w]) { System.err .printf("edge %d-%d with %d and %d in same side of bipartition\n", v, w, v, w); return false; } } } } else { int first = -1, last = -1; for (int v : oddCycle()) { if (first == -1) first = v; last = v; } if (first != last) { System.err.printf("cycle begins with %d and ends with %d\n", first, last); return false; } } return true; } } class Graph { private final int V; private int E; private ArrayList<Integer>[] adj; @SuppressWarnings("unchecked") public Graph(int V) { this.V = V; this.E = 0; adj = (ArrayList<Integer>[]) new ArrayList[V]; for (int v = 0; v < V; v++) adj[v] = new ArrayList<>(); } public Graph(Scanner cin) { this(cin.nextInt()); int E = cin.nextInt(); for (int i = 0; i < E; i++) { int v = cin.nextInt(); int w = cin.nextInt(); addEdge(v, w); } } public void addEdge(int v, int w) { adj[v].add(w); adj[w].add(v); this.E++; } public int V() { return V; } public int E() { return E; } public ArrayList<Integer> adj(int v) { return adj[v]; } public int degree(int v) { return adj[v].size(); } }
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2.使用最大網路流法,人為地新增起點和終點,選擇黑色頂點,如果有與其相鄰的白色頂點,則新增一條權重為1的由黑色頂點指向白色的有向邊,若黑色頂點數有m個,白色頂點數有K個,則新增起點指向黑色頂點權重為1的m條有向邊,新增白色頂點指向終點的權重為1的K條有向邊,使用Ford演算法求起點到終點的最大網路流量。O(VE^2)
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Scanner;
public class Main
{
public static void main(String[] args)
{
cover1 cov=new cover1();
System.out.println(cov.getmaxmatch());
}
}
class cover1
{
private int n;
private int m;
private boolean[] visited;
private boolean[] isdelete;
private boolean[] color;
private FordFulkerson fordfulkerson;
private ArrayDeque<storexy> deque=new ArrayDeque<>();
public cover1()
{
Scanner cin=new Scanner(System.in);
n=cin.nextInt();
m=cin.nextInt();
isdelete=new boolean[n*n];
visited=new boolean[n*n];
color=new boolean[n*n];
for(int i=0;i<m;i++)
{
int x=cin.nextInt();
int y=cin.nextInt();
isdelete[toid(x, y)]=true;
}
L1: for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
int id=toid(i, j);
if(!isdelete[id])
{
color[id]=true;
deque.add(new storexy(i, j));
// bfs(i, j);
break L1;
}
}
while(!deque.isEmpty())
{
storexy xy=deque.pollFirst();
bfs(xy.getx(),xy.gety());
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
int id=toid(i, j);
if(!isdelete[id]&&!visited[id])
{
color[id]=true;
deque.add(new storexy(i, j));
}
}
while(!deque.isEmpty())
{
storexy xy=deque.pollFirst();
bfs(xy.getx(),xy.gety());
}
FlowNetwork flownetwork=new FlowNetwork(n*n+2);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(!isdelete[toid(i, j)]&&color[toid(i, j)]==true)
{
flownetwork.addEdge(new FlowEdge(n*n, toid(i, j), 1.0));
int[] xoff={-1,1,0,0};
int[] yoff={0,0,1,-1};
for(int k=0;k<4;k++)
{
int xx=i+xoff[k];
int yy=j+yoff[k];
if(xx>=1&&xx<=n&&yy>=1&&yy<=n)
{
int id=toid(xx, yy);
if(!isdelete[id]&&color[id]==false)
flownetwork.addEdge(new FlowEdge(toid(i, j), id, 1.0));
}
}
}
else if(!isdelete[toid(i, j)]&&color[toid(i, j)]==false)
flownetwork.addEdge(new FlowEdge(toid(i, j), n*n+1, 1.0));
}
fordfulkerson=new FordFulkerson(flownetwork, n*n, n*n+1);
// TODO Auto-generated constructor stub
}
private int toid(int x,int y)
{
return (x-1)*n+y-1;
}
private void bfs(int x,int y)
{
visited[toid(x, y)]=true;
boolean colornow=color[toid(x, y)];
int[] xoff={-1,1,0,0};
int[] yoff={0,0,1,-1};
for(int i=0;i<4;i++)
{
int xx=x+xoff[i];
int yy=y+yoff[i];
if(xx>=1&&xx<=n&&yy>=1&&yy<=n&&!isdelete[toid(xx, yy)]&&!visited[toid(xx, yy)])
{
color[toid(xx, yy)]=!colornow;
storexy xy=new storexy(xx, yy);
if(!deque.contains(xy))
deque.add(xy);
// bfs(xx, yy);
}
}
}
public int getmaxmatch()
{
return (int) fordfulkerson.value();
}
}
class FlowEdge
{
private final int v;
private final int w;
private final double capacity;
private double flow;
public FlowEdge(int v,int w,double capacity)
{
this.v=v;
this.w=w;
this.capacity=capacity;
this.flow=0.0;
// TODO Auto-generated constructor stub
}
public int from()
{
return v;
}
public int to()
{
return w;
}
public double capacity()
{
return capacity;
}
public double flow()
{
return flow;
}
public int other(int vertex)
{
if(vertex==v)
return w;
if(vertex==w)
return v;
throw new RuntimeException("Inconsistant Edge");
}
public double residualCapacityTo(int vertex)
{
if(vertex==v)
return flow;
else if(vertex==w)
return capacity-flow;
else {
throw new RuntimeException("Inconsistant Edge");
}
}
public void addResidualFlowTo(int vertex,double delta)
{
if(vertex==v)
flow-=delta;
else if(vertex==w)
flow+=delta;
}
public String toString()
{
return String.format("%d->%d %.2f %.2f", v,w,capacity,flow);
}
}
class FlowNetwork
{
private final int V;
private int E;
private ArrayList<FlowEdge>[] adj;
public FlowNetwork(int v)
{
this.V=v;
this.E=0;
adj=new ArrayList[V];
for(int i=0;i<v;i++)
adj[i]=new ArrayList<>();
// TODO Auto-generated constructor stub
}
public FlowNetwork(Scanner cin)
{
this(cin.nextInt());
this.E=cin.nextInt();
for(int i=0;i<E;i++)
{
int v=cin.nextInt();
int w=cin.nextInt();
double capacity=cin.nextDouble();
addEdge(new FlowEdge(v, w, capacity));
}
// TODO Auto-generated constructor stub
}
public void addEdge(FlowEdge e)
{
int v=e.from();
int w=e.to();
adj[v].add(e);
adj[w].add(e);
}
public int V()
{
return V;
}
public int E()
{
return E;
}
public Iterable<FlowEdge> adj(int v)
{
return adj[v];
}
public Iterable<FlowEdge> edges()
{
ArrayList<FlowEdge> list=new ArrayList<>();
for(int v=0;v<V;v++)
for(FlowEdge e:adj(v))
if(e.to()!=v)
list.add(e);
return list;
}
}
class FordFulkerson
{
private boolean[] marked;
private FlowEdge[] edgeTo;
private double value;
public FordFulkerson(FlowNetwork G,int s,int t)
{
while(hasAugmentingPath(G, s, t))
{
double bottle=Double.POSITIVE_INFINITY;
for(int v=t;v!=s;v=edgeTo[v].other(v))
bottle=Math.min(bottle, edgeTo[v].residualCapacityTo(v));
for(int v=t;v!=s;v=edgeTo[v].other(v))
edgeTo[v].addResidualFlowTo(v, bottle);
value+=bottle;
}
// TODO Auto-generated constructor stub
}
public double value()
{
return value;
}
public boolean inCut(int v)
{
return marked[v];
}
private boolean hasAugmentingPath(FlowNetwork G,int s,int t)
{
marked=new boolean[G.V()];
edgeTo=new FlowEdge[G.V()];
ArrayDeque<Integer> q=new ArrayDeque<>();
marked[s]=true;
q.add(s);
while(!q.isEmpty())
{
int v=q.pollFirst();
for(FlowEdge e:G.adj(v))
{
int w=e.other(v);
if(e.residualCapacityTo(w)>0&&!marked[w])
{
edgeTo[w]=e;
marked[w]=true;
q.add(w);
}
}
}
return marked[t];
}
}
class storexy
{
private int x;
private int y;
public storexy(int x,int y)
{
this.x=x;
this.y=y;
// TODO Auto-generated constructor stub
}
@Override
public boolean equals(Object obj)
{
// TODO Auto-generated method stub
return (x==((storexy)obj).getx()&&y==((storexy)obj).gety());
}
public int getx()
{
return x;
}
public int gety()
{
return y;
}
}