1. 程式人生 > >1084___Square Destroyer——IDA* || DLX重複覆蓋

1084___Square Destroyer——IDA* || DLX重複覆蓋

題目大意:

     給你nnn*n的由火柴組成的正方形圖案n5(n≤5), 並對每個火柴進行編號,已經幫你刪除了 kk 個火柴棒,請問最少還要刪除幾根火柴棒,使得由火柴組成的圖形沒有一個完整的正方形,正方形的邊長可以為12......1、2......

解題思路:

    首先我們先算出,一共有2n(n+1)2*n*(n+1)根火柴,12+22++n2=n(n+1)(2n+1)/61^2+2^2+…+n^2=n(n+1)(2n+1)/6個正方形,那麼每個正方形只要有一個火柴缺失,這個正方形就不完整,繼而轉化為了DLX重複覆蓋模型
    每個火柴為每一行,每個正方形為每列,要求使用最少的火柴重複覆蓋每個正方形,明顯套個模板就行
    只看上面的思路的話你可能會認為這道題是道水題,但發現一實現起來並不是那麼簡單.

...........,因為本題最難的地方並不是讓你想到DLX,而是怎麼去建圖(個人感覺)
    要注意的是他已經幫你除掉一些火柴了,那麼這些火柴所涉及到的正方形永遠都不完整,所以我們一開始並不著急去建圖,先將需要連的點用markmark記錄下來,然後去看他已經刪除了哪些火柴,然後對這些火柴涉及到的列 jj 進行以下操作:

dlx.L[dlx.R[j]] = dlx.L[j];
dlx.R[dlx.L[j]] = dlx.R[j];
dlx.R[j] = dlx.L[j] = 0;

    這樣這列就永遠刪除了,最後就將markmark記錄的需要連的邊連上就好,然而這並沒有我我想像的那麼簡單

,具體看下

程式碼思路:

    怎麼處理2n(n+1)2*n*(n+1)根火柴與n(n+1)(2n+1)/6n(n+1)(2n+1)/6個正方形之間的邊呢???
    這裡我是依次處理邊長為12......n1、2......n的正方形,設正方形的邊長為 sisi,其每行就有nsi+1n-si+1根火柴,每列也有nsi+1n-si+1根火柴,同時每條邊也會有 sisi 根火柴
    觀察每個正方形的每條邊的火柴數量規律(先算邊長為 11 的),發現每個正方形的上邊和下邊滿足同一個規律,左邊和右邊滿足一個規律,用這兩個規律即可建圖,具體程式碼如下:

	int c=1;
	for(int si=1; si<=n; si++) {
		for(int i=1; i<=n-si+1; i++) {
			for(int j=1; j<=n-si+1; j++) {
				for(int k=0; k<si; k++) {
					mark[(i-1)*(2*n+1)+j+k][c]=true;	// 上 
					mark[(i-1+si)*(2*n+1)+j+k][c]=true;	// 下 
					mark[i*n+(i-1)*(n+1)+j+k*(2*n+1)][c]=true;	// 左 
					mark[i*n+(i-1)*(n+1)+j+k*(2*n+1)+si][c]=true;	// 右 
				}
				c++;
			}
		}
	}

    最後一個點!!!
    在前面我們對已經刪除火柴的邊所涉及到的正方形(即DLX中的列)進行刪除時,記錄下來這些列,在後來的加邊過程中不要加這些邊(這很重要,不然你會錯)

核心:熟悉的使用覆蓋這一技巧,同時對建圖有熟練地操作

本題在沒注意資料範圍而調了很長時間,我真是個傻B。。。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

#define N 50010
#define M 1010
struct DancingLink {
	int n,s,ansd;//列數 節點總數
	int S[M],A[M],H[M];//S[]該列節點總數  A[]答案  H[]行首指標
	int L[N],R[N],U[N],D[N]; //L[],R[],U[],D[] 上下左右
	int X[M],C[M],vis[M];//X[] C[] 行列編號
	void init(int n) { //初始化
		this->n=n;
		for(int i=0; i<=n; i++)
			U[i]=i,D[i]=i,L[i]=i-1,R[i]=i+1;
		R[n]=0,L[0]=n;s=n+1;
		memset(S,0,sizeof(S));
		memset(H,-1,sizeof(H));
	}
	void DelCol(int c) { //刪除列
		for(int i=D[c]; i!=c; i=D[i])
			L[R[i]]=L[i],R[L[i]]=R[i];
	}
	void ResCol(int c) { //恢復列
		for(int i=U[c]; i!=c; i=U[i])
			L[R[i]]=R[L[i]]=i;
	}
	void AddNode(int r,int c) { //新增節點
		++S[c],C[++s]=c,X[s]=r;
		D[s]=D[c],U[D[c]]=s,U[s]=c,D[c]=s;
		if(H[r]<0) H[r]=L[s]=R[s]=s;//行首節點
		else  R[s]=R[H[r]],L[R[H[r]]]=s,L[s]=H[r],R[H[r]]=s;
	}
	int f() {
		int ret=0;
		memset(vis,0,sizeof(vis));
		for(int i=R[0]; i; i=R[i])
			if(!vis[i]) {
				ret++;vis[i]=1;
				for(int j=D[i]; j!=i; j=D[j])
					for(int k=R[j]; k!=j; k=R[k])
						vis[C[k]]=1;
			}
		return ret;
	}
	void dfs(int d) { //深度,深搜遍歷
		if(!R[0]) {
			ansd=min(d,ansd);
			return;
		}
		if(d+f()>=ansd) return;
		int c=R[0];
		for(int i=R[0]; i; i=R[i]) if(S[i]<S[c]) c=i;
		for(int i=D[c]; i!=c; i=D[i]) {
			DelCol(i);
			for(int j=R[i]; j!=i; j=R[j]) DelCol(j);
			dfs(d+1);
			for(int j=L[i]; j!=i; j=L[j]) ResCol(j);
			ResCol(i);
		}
	}

} dlx;

int main() {
	bool visx[80];
	bool mark[80][80];
	int n,m,cas,q;
	scanf("%d", &cas);
	while(cas--) {
		scanf("%d%d",&n,&q);
		int row=2*n*(n+1), col=0;
		for(int i=1; i<=n; i++) col += i*i;
		dlx.init(col);
		
		memset(mark,0,sizeof(mark));
		memset(visx,0,sizeof(visx));
		int c=1,tmp;
		for(int si=1; si<=n; si++) {
			for(int i=1; i<=n-si+1; i++) {
				for(int j=1; j<=n-si+1; j++) {
					for(int k=0; k<si; k++) {
						mark[(i-1)*(2*n+1)+j+k][c]=true;	// 上 
						mark[(i-1+si)*(2*n+1)+j+k][c]=true;	// 下 
						mark[i*n+(i-1)*(n+1)+j+k*(2*n+1)][c]=true;	// 左 
						mark[i*n+(i-1)*(n+1)+j+k*(2*n+1)+si][c]=true;	// 右 
					}
					c++;
				}
			}
		}
		for(int i=0; i<q; i++){
			scanf("%d", &tmp);
			for(int j=1; j<=col; j++){
				if(mark[tmp][j] && !visx[j]){
					visx[j] = 1;
					dlx.L[dlx.R[j]] = dlx.L[j];
					dlx.R[dlx.L[j]] = dlx.R[j];
					dlx.R[j] = dlx.L[j] = 0;
				}
			}
		}
		for(int i=1; i<=row; i++)
            for(int j=1; j<=col; j++)
                if(mark[i][j] && !visx[j])
                	dlx.AddNode(i,j);
		
		dlx.ansd = 100000;
		dlx.dfs(0);
		printf("%d\n", dlx.ansd);
	}
}