题意:一块N*N大的蛋糕M次讯问,每次切走一个矩形,问每次切的矩形被分成了多少块(1 <= N <= 1000, 1 <= M <= 10000)。
解法:显然是求某个区域内的连通分量数目,由于这个区域内的元素在访问后会被切除,因此每个元素只被访问一次,因此可以通过dfs或bfs求得某个区域内的连通分量的个数,均摊复杂度。
但是每次寻找遍历的起始点(即区域内未被切走的蛋糕)复杂度为O(N^2),因此需要优化,最坏情况是怎么产生的呢?当某个区域内有连续的已经切走的蛋糕,那我们在寻找未切走的蛋糕时需要逐一判断,产生复杂度,因此如果遇到连续的切走的区间,如果能够在短时间内跳过它,那么复杂度就可以得到控制。类似于supermarket的那个题目,查询未被占用的最左元素,我们用并查集维护。对于每一行维护一个并查集,f[x]代表x开始连续的被切走的蛋糕的最右边的那块,当某块蛋糕杯切走且它右边相邻的也被切走,就将他合并到右边的元素,这样每次查询寻找遍历起点的复杂度就变为O(N)。
注:dfs会爆栈,显然应该使用bfs
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.StreamTokenizer; public class CutingCake2843 { class Union { int f[]; Union(int n) { f= new int[n+10]; for (int i = 1; i <= n; i++) f[i] = i; } int find(int x) { if (x != f[x]) f[x] = find(f[x]); return f[x]; } } class node{ int x,y; node(int x,int y){ this.x=x; this.y=y; } } node stack[]=new node[1010*1010]; boolean vis[][]=new boolean[1010][1010]; int minx, miny, maxx, maxy, n,top; boolean test(int x,int y){ if (x <minx|| x>maxx||y<miny||y>maxy) return false; if (vis[x][y]) return false; stack[top++]=new node(x,y); vis[x][y]=true; return true; } void bfs(int x, int y) { top=0; stack[top++]=new node(x,y); while(top!=0){ x=stack[--top].x; y=stack[top].y; vis[x][y] = true; if(vis[x][y+1]) set[x].f[y]=y+1; if(vis[x][y-1]) set[x].f[y-1]=y; test(x,y+1); test(x,y-1); test(x-1,y); test(x+1,y); } } StreamTokenizer in = new StreamTokenizer(new BufferedReader( new InputStreamReader(System.in))); int nextInt() throws IOException { in.nextToken(); return (int) in.nval; } Union set[] = new Union[1010]; void run() throws IOException { n = nextInt(); int m = nextInt(); for (int i = 1; i <= n; i++) set[i] = new Union(n); while (m-- > 0) { minx = nextInt(); miny = nextInt(); maxx = nextInt(); maxy = nextInt(); int res = 0; for (int i = minx; i <= maxx; i++) for (int j = miny; j <= maxy;j = set[i].find(j)+1) if (!vis[i][j]) { bfs(i, j); res++; } System.out.println(res); } } public static void main(String[] args) throws IOException { new CutingCake2843().run(); } }