Dancing Link专题
一些链接:
http://www.cnblogs.com/-sunshine/p/3358922.html
http://www.cnblogs.com/grenet/p/3145800.html
1、hust 1017 Exact cover (Dancing Links 模板题)
题意:n*m的单位矩阵。现在要选一些行,使得这些行的集合中每列只出现一个1.
思路:裸的精确覆盖问题。刷一遍模板。
#include <iostream>
#include <stdio.h>
#include <string.h>
//精确覆盖问题的定义:给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1
const int MN = ;//最大行数
const int MM = ;//最大列数
const int MNN = 1e5 + + MM; //最大点数 struct DLX
{
int n, m, si;//n行数m列数si目前有的节点数
//十字链表组成部分
int U[MNN], D[MNN], L[MNN], R[MNN], Row[MNN], Col[MNN];
//第i个结点的U向上指针D下L左R右,所在位置Row行Col列
int H[MN], S[MM]; //记录行的选择情况和列的覆盖情况
int ansd, ans[MN];
void init(int _n, int _m) //初始化空表
{
n = _n;
m = _m;
for (int i = ; i <= m; i++) //初始化第一横行(表头)
{
S[i] = ;
U[i] = D[i] = i; //目前纵向的链是空的
L[i] = i - ;
R[i] = i + ; //横向的连起来
}
R[m] = ; L[] = m;
si = m; //目前用了前0~m个结点
for (int i = ; i <= n; i++)
H[i] = -;
}
void link(int r, int c) //插入点(r,c)
{
++S[Col[++si] = c]; //si++;Col[si]=c;S[c]++;
Row[si] = r;//si该结点的行数为r
D[si] = D[c];//向下指向c的下面的第一个结点
U[D[c]] = si;//c的下面的第一个结点的上面为si
U[si] = c;//si的上面为列指针
D[c] = si;//列指针指向的第一个该列中的元素设为si
if (H[r]<)//如果第r行没有元素
H[r] = L[si] = R[si] = si;
else
{
R[si] = R[H[r]];//si的右边为行指针所指的右边第一个元素
L[R[H[r]]] = si;//行指针所指的右边第一个元素的左侧为si
L[si] = H[r];//si的左侧为行指针
R[H[r]] = si;//行指针的右侧为si
}
}
void remove(int c) //列表中删掉c列
{
L[R[c]] = L[c];//表头操作 //c列头指针的右边的元素的左侧指向c列头指针左边的元素
R[L[c]] = R[c];//c列头指针的左边的元素的右侧指向c列头指针右边的元素
for (int i = D[c]; i != c; i = D[i])//遍历该列的所有元素
for (int j = R[i]; j != i; j = R[j])
{//对于该列的某个元素所在的行进行遍历
U[D[j]] = U[j];//把该元素从其所在列中除去
D[U[j]] = D[j];
--S[Col[j]];//该元素所在的列数目减一
}
}
void resume(int c) //恢复c列
{
for (int i = U[c]; i != c; i = U[i])//枚举该列元素
for (int j = L[i]; j != i; j = L[j])//枚举该列元素所在的行
++S[Col[U[D[j]] = D[U[j]] = j]];//D[U[j]]=j;U[D[j]]=j;S[Col[j]]++;
L[R[c]] = R[L[c]] = c;//c列头指针左右相连
}
bool dance(int d) //选取了d行
{
if (R[] == )//全部覆盖了
{
//全覆盖了之后的操作
ansd = d;
return ;
}
int c = R[];//表头结点指向的第一个列
for (int i = R[]; i != ; i = R[i])//枚举列头指针
if (S[i]<S[c])//找到列中元素个数最少的
c = i;
remove(c);//将该列删去
for (int i = D[c]; i != c; i = D[i])
{//枚举该列的元素
ans[d] = Row[i];//记录该列元素的行
for (int j = R[i]; j != i; j = R[j])
remove(Col[j]);//将该列的某个元素的行上的元素所在的列都删去
if (dance(d + ))
return ;
for (int j = L[i]; j != i; j = L[j])
resume(Col[j]);
}
resume(c);
return ;
}
}dlx; int main()
{
int n, m;
while (scanf("%d%d", &n, &m) != EOF)
{
dlx.init(n, m);
for (int i = ; i <= n; i++)
{//共n列
int k;
scanf("%d", &k);//每列中含1的个数
while (k--)
{
int cc;
scanf("%d", &cc);//输入其所在的列
dlx.link(i, cc);//链接
}
}
dlx.ansd = -;
if (dlx.dance())
{
printf("%d", dlx.ansd);
for (int i = ; i<dlx.ansd; i++)
printf(" %d", dlx.ans[i]);
printf("\n");
}
else
printf("NO\n");
}
return ;
}
2、ZOJ 3209 Treasure Map
题意:给出一些矩形,问最少需要多少个矩形可以把指定的一块区域覆盖。
思路:把每个矩形块看成行,把指定区域分成1*1的单元格,所有的单元格看成列。
#include <iostream>
#include <stdio.h>
#include <string.h>
#include<algorithm>
//精确覆盖问题的定义:给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1
const int MN = ;//最大行数
const int MM = ;//最大列数
const int MNN = 1e5 + + MM; //最大点数 struct DLX
{
int n, m, si;//n行数m列数si目前有的节点数
//十字链表组成部分
int U[MNN], D[MNN], L[MNN], R[MNN], Row[MNN], Col[MNN];
//第i个结点的U向上指针D下L左R右,所在位置Row行Col列
int H[MN], S[MM]; //记录行的选择情况和列的覆盖情况
int ansd, ans[MN];
void init(int _n, int _m) //初始化空表
{
n = _n;
m = _m;
for (int i = ; i <= m; i++) //初始化第一横行(表头)
{
S[i] = ;
U[i] = D[i] = i; //目前纵向的链是空的
L[i] = i - ;
R[i] = i + ; //横向的连起来
}
R[m] = ; L[] = m;
si = m; //目前用了前0~m个结点
for (int i = ; i <= n; i++)
H[i] = -;
}
void link(int r, int c) //插入点(r,c)
{
++S[Col[++si] = c]; //si++;Col[si]=c;S[c]++;
Row[si] = r;//si该结点的行数为r
D[si] = D[c];//向下指向c的下面的第一个结点
U[D[c]] = si;//c的下面的第一个结点的上面为si
U[si] = c;//si的上面为列指针
D[c] = si;//列指针指向的第一个该列中的元素设为si
if (H[r]<)//如果第r行没有元素
H[r] = L[si] = R[si] = si;
else
{
R[si] = R[H[r]];//si的右边为行指针所指的右边第一个元素
L[R[H[r]]] = si;//行指针所指的右边第一个元素的左侧为si
L[si] = H[r];//si的左侧为行指针
R[H[r]] = si;//行指针的右侧为si
}
}
void remove(int c) //列表中删掉c列
{
L[R[c]] = L[c];//表头操作 //c列头指针的右边的元素的左侧指向c列头指针左边的元素
R[L[c]] = R[c];//c列头指针的左边的元素的右侧指向c列头指针右边的元素
for (int i = D[c]; i != c; i = D[i])//遍历该列的所有元素
for (int j = R[i]; j != i; j = R[j])
{//对于该列的某个元素所在的行进行遍历
U[D[j]] = U[j];//把该元素从其所在列中除去
D[U[j]] = D[j];
--S[Col[j]];//该元素所在的列数目减一
}
}
void resume(int c) //恢复c列
{
for (int i = U[c]; i != c; i = U[i])//枚举该列元素
for (int j = L[i]; j != i; j = L[j])//枚举该列元素所在的行
++S[Col[U[D[j]] = D[U[j]] = j]];//D[U[j]]=j;U[D[j]]=j;S[Col[j]]++;
L[R[c]] = R[L[c]] = c;//c列头指针左右相连
}
bool dance(int d) //选取了d行
{
if (ansd != - && ansd < d)return ;
if (R[] == )//全部覆盖了
{
//全覆盖了之后的操作
if(ansd==-)ansd = d;
else if (d < ansd) ansd = d;
return ;
}
int c = R[];//表头结点指向的第一个列
for (int i = R[]; i != ; i = R[i])//枚举列头指针
if (S[i]<S[c])//找到列中元素个数最少的
c = i;
remove(c);//将该列删去
for (int i = D[c]; i != c; i = D[i])
{//枚举该列的元素
ans[d] = Row[i];//记录该列元素的行
for (int j = R[i]; j != i; j = R[j])
remove(Col[j]);//将该列的某个元素的行上的元素所在的列都删去
(dance(d + ));
for (int j = L[i]; j != i; j = L[j])
resume(Col[j]);
}
resume(c);
return ;
}
}dlx; int main()
{
int n, m,p;
int t;
scanf("%d", &t);
while (t--)
{
scanf("%d%d%d", &n, &m, &p);
dlx.init(p, n*m);//将块当成行,所有的单元格看成列
for (int pp = ; pp <= p; pp++)
{
int x1, x2, y1, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
for (int i = x1 + ; i <= x2; i++)
{
for (int j = y1 + ; j <= y2; j++)
{
dlx.link(pp, (i - )*m + j);
}
}
}
dlx.ansd = -;
dlx.dance();
printf("%d\n", dlx.ansd);
}
return ;
}
3、HDU 2295 Radar
题意:给出n个城市,给出m个雷达(都是坐标),要求在使用不超过k个雷达的情况下,并且保证覆盖所有的城市,使得每个雷达的覆盖半径最小。
思路:DLK重复覆盖模板。
#include <iostream>
#include <stdio.h>
#include <string.h>
//重复覆盖:找到一些行,使得这些行的集合中每列至少有一个1
const int MN = ;//最大行数
const int MM = ;//最大列数
const int MNN = MM*MN+MM+MN+; //最大点数 struct DLX
{
int n, m, si;//n行数m列数si目前有的节点数
//十字链表组成部分
int U[MNN], D[MNN], L[MNN], R[MNN], Row[MNN], Col[MNN];
//第i个结点的U向上指针D下L左R右,所在位置Row行Col列
int H[MN], S[MM]; //记录行的选择情况和列的覆盖情况
int ansd, ans[MN];
void init(int _n, int _m) //初始化空表
{
n = _n;
m = _m;
for (int i = ; i <= m; i++) //初始化第一横行(表头)
{
S[i] = ;
U[i] = D[i] = i; //目前纵向的链是空的
L[i] = i - ;
R[i] = i + ; //横向的连起来
}
R[m] = ; L[] = m;
si = m; //目前用了前0~m个结点
for (int i = ; i <= n; i++)
H[i] = -;
}
void link(int r, int c) //插入点(r,c)
{
++S[Col[++si] = c]; //si++;Col[si]=c;S[c]++;
Row[si] = r;//si该结点的行数为r
D[si] = D[c];//向下指向c的下面的第一个结点
U[D[c]] = si;//c的下面的第一个结点的上面为si
U[si] = c;//si的上面为列指针
D[c] = si;//列指针指向的第一个该列中的元素设为si
if (H[r]<)//如果第r行没有元素
H[r] = L[si] = R[si] = si;
else
{
R[si] = R[H[r]];//si的右边为行指针所指的右边第一个元素
L[R[H[r]]] = si;//行指针所指的右边第一个元素的左侧为si
L[si] = H[r];//si的左侧为行指针
R[H[r]] = si;//行指针的右侧为si
}
}
void remove(int c) //列表中删掉c列
{
//L[R[c]] = L[c];//表头操作 //c列头指针的右边的元素的左侧指向c列头指针左边的元素
//R[L[c]] = R[c];//c列头指针的左边的元素的右侧指向c列头指针右边的元素
//for (int i = D[c]; i != c; i = D[i])//遍历该列的所有元素
// for (int j = R[i]; j != i; j = R[j])
// {//对于该列的某个元素所在的行进行遍历
// U[D[j]] = U[j];//把该元素从其所在列中除去
// D[U[j]] = D[j];
// --S[Col[j]];//该元素所在的列数目减一
// }
/*重复覆盖*//*c为元素编号,而非列号*//*即删除该元素所在的列,包括它自身和列头指针*/
for (int i = D[c]; i != c; i = D[i])
{
L[R[i]] = L[i], R[L[i]] = R[i];
}
}
void resume(int c)//恢复c列
{
//for (int i = U[c]; i != c; i = U[i])//枚举该列元素
// for (int j = L[i]; j != i; j = L[j])//枚举该列元素所在的行
// ++S[Col[U[D[j]] = D[U[j]] = j]];//D[U[j]]=j;U[D[j]]=j;S[Col[j]]++;
//L[R[c]] = R[L[c]] = c;//c列头指针左右相连
/*重复覆盖*/
for (int i = U[c]; i != c; i = U[i])
{
L[R[i]] = R[L[i]] = i;
}
}
int f_check()//精确覆盖区估算剪枝
{
/*
强剪枝。这个 剪枝利用的思想是A*搜索中的估价函数。即,对于当前的递归深度K下的矩阵,估计其最好情况下(即最少还需要多少步)才能出解。也就是,如果将能够覆盖当 前列的所有行全部选中,去掉这些行能够覆盖到的列,将这个操作作为一层深度。重复此操作直到所有列全部出解的深度是多少。如果当前深度加上这个估价函数返 回值,其和已然不能更优(也就是已经超过当前最优解),则直接返回,不必再搜。
*/
int vis[MNN];
memset(vis, , sizeof(vis));
int ret = ;
for (int c = R[]; c != ; c = R[c]) vis[c] = true;
for (int c = R[]; c != ; c = R[c])
if (vis[c])
{
ret++;
vis[c] = false;
for (int i = D[c]; i != c; i = D[i])
for (int j = R[i]; j != i; j = R[j])
vis[Col[j]] = false;
}
return ret;
}
bool dance(int d,int limit) //选取了d行,limit为限制选取的最大行数
{
/*重复覆盖
1、如果矩阵为空,得到结果,返回
2、从矩阵中选择一列,以选取最少元素的列为优化方式
3、删除该列及其覆盖的行
4、对该列的每一行元素:删除一行及其覆盖的列,
5、进行下一层搜索,如果成功则返回
6、恢复现场,跳至4
7、恢复所选择行
*/
if (d > limit)return false;
if (d + f_check() > limit)return false;
if (R[] == )//全部覆盖了
{
//全覆盖了之后的操作
ansd = d;
return true;
}
int c = R[];//表头结点指向的第一个列
for (int i = R[]; i != ; i = R[i])//枚举列头指针
if (S[i]<S[c])//找到列中元素个数最少的
c = i;
//remove(c);//将该列删去(精确覆盖)
for (int i = D[c]; i != c; i = D[i])
{//枚举该列的元素
ans[d] = Row[i];//记录该列元素的行
remove(i);//新增(重复覆盖)
for (int j = R[i]; j != i; j = R[j])
remove(j);//remove(Col[j])(精确覆盖)
if (dance(d + , limit)) return true;
for (int j = L[i]; j != i; j = L[j])
resume(j);//resume(Col[j])(精确覆盖)
resume(i);//新增(重复覆盖)
}
//resume(c);(精确覆盖)
return false;
}
}dlx;
struct node
{
int x;
int y;
}citys[],radar[];
double dis[][];
int main()
{
int n, m,k;
int t;
scanf("%d", &t); while (t--)
{
double l = , r=,maxx=,maxy=;
scanf("%d%d%d", &n, &m, &k);
for (int i = ; i <= n; i++)
{
scanf("%d%d", &citys[i].x, &citys[i].y);
if (citys[i].x + citys[i].y > maxx + maxy)
{
maxx = citys[i].x, maxy = citys[i].y;
}
}
for (int i = ; i <= m; i++)
{
scanf("%d%d", &radar[i].x, &radar[i].y);
if (radar[i].x + radar[i].y > maxx + maxy)
{
maxx = radar[i].x, maxy = radar[i].y;
}
}
for (int i = ; i <= m; i++)
{
for (int j = ; j <= n; j++)
{
dis[i][j] = sqrt((radar[i].x - citys[j].x)*(radar[i].x - citys[j].x) + (radar[i].y - citys[j].y)*(radar[i].y - citys[j].y));
}
}
r = sqrt(maxx*maxx + maxy*maxy);
while (r - l > 1e-)
{
double mid = (l + r) / ;
dlx.init(m, n);
for (int i = ; i <= m; i++)
{
for (int j = ; j <= n; j++)
{
if (dis[i][j] <= mid) dlx.link(i, j);
}
}
dlx.ansd = -;
if (dlx.dance(,k)) r = mid;
else l = mid;
}
printf("%.6lf\n", l);
}
return ;
}
4、FZU 1686 神龙的难题
题意:有个n*m的矩形,每次可以把n1*m1小矩形范围内的敌人消灭,最少的次数。
思路:DLK重复覆盖模板,枚举所有的小矩形,设为行,把所有敌人编号,记为列。
#include<iostream>
#include<cstdio>
#include<memory.h>
#include<algorithm>
using namespace std;
//重复覆盖:找到一些行,使得这些行的集合中每列至少有一个1
const int MN =;//最大行数
const int MM = ;//最大列数
const int MNN = MM*MN; //最大点数 struct DLX
{
int n, m, si;//n行数m列数si目前有的节点数
//十字链表组成部分
int U[MNN], D[MNN], L[MNN], R[MNN], Row[MNN], Col[MNN];
//第i个结点的U向上指针D下L左R右,所在位置Row行Col列
int H[MN], S[MM]; //记录行的选择情况和列的覆盖情况
int ansd, ans[MN];
void init(int _n, int _m) //初始化空表
{
n = _n;
m = _m;
for (int i = ; i <= m; i++) //初始化第一横行(表头)
{
S[i] = ;
U[i] = D[i] = i; //目前纵向的链是空的
L[i] = i - ;
R[i] = i + ; //横向的连起来
}
R[m] = ; L[] = m;
si = m; //目前用了前0~m个结点
for (int i = ; i <= n; i++)
H[i] = -;
}
void link(int r, int c) //插入点(r,c)
{
++S[Col[++si] = c]; //si++;Col[si]=c;S[c]++;
Row[si] = r;//si该结点的行数为r
D[si] = D[c];//向下指向c的下面的第一个结点
U[D[c]] = si;//c的下面的第一个结点的上面为si
U[si] = c;//si的上面为列指针
D[c] = si;//列指针指向的第一个该列中的元素设为si
if (H[r]<)//如果第r行没有元素
H[r] = L[si] = R[si] = si;
else
{
R[si] = R[H[r]];//si的右边为行指针所指的右边第一个元素
L[R[H[r]]] = si;//行指针所指的右边第一个元素的左侧为si
L[si] = H[r];//si的左侧为行指针
R[H[r]] = si;//行指针的右侧为si
}
}
void remove(int c) //列表中删掉c列
{
//L[R[c]] = L[c];//表头操作 //c列头指针的右边的元素的左侧指向c列头指针左边的元素
//R[L[c]] = R[c];//c列头指针的左边的元素的右侧指向c列头指针右边的元素
//for (int i = D[c]; i != c; i = D[i])//遍历该列的所有元素
// for (int j = R[i]; j != i; j = R[j])
// {//对于该列的某个元素所在的行进行遍历
// U[D[j]] = U[j];//把该元素从其所在列中除去
// D[U[j]] = D[j];
// --S[Col[j]];//该元素所在的列数目减一
// }
/*重复覆盖*//*c为元素编号,而非列号*//*即删除该元素所在的列,包括它自身和列头指针*/
for (int i = D[c]; i != c; i = D[i])
{
L[R[i]] = L[i], R[L[i]] = R[i];
}
} void resume(int c)//恢复c列
{
//for (int i = U[c]; i != c; i = U[i])//枚举该列元素
// for (int j = L[i]; j != i; j = L[j])//枚举该列元素所在的行
// ++S[Col[U[D[j]] = D[U[j]] = j]];//D[U[j]]=j;U[D[j]]=j;S[Col[j]]++;
//L[R[c]] = R[L[c]] = c;//c列头指针左右相连
/*重复覆盖*/
for (int i = U[c]; i != c; i = U[i])
{
L[R[i]] = R[L[i]] = i;
}
} bool vis[MNN];
int f_check()//精确覆盖区估算剪枝
{
/*
强剪枝。这个 剪枝利用的思想是A*搜索中的估价函数。即,对于当前的递归深度K下的矩阵,估计其最好情况下(即最少还需要多少步)才能出解。也就是,如果将能够覆盖当 前列的所有行全部选中,去掉这些行能够覆盖到的列,将这个操作作为一层深度。重复此操作直到所有列全部出解的深度是多少。如果当前深度加上这个估价函数返 回值,其和已然不能更优(也就是已经超过当前最优解),则直接返回,不必再搜。
*/ int ret = ;
for (int c = R[]; c != ; c = R[c]) vis[c] = true;
for (int c = R[]; c != ; c = R[c])
if (vis[c])
{
ret++;
vis[c] = false;
for (int i = D[c]; i != c; i = D[i])
for (int j = R[i]; j != i; j = R[j])
vis[Col[j]] = false;
}
return ret;
}
void dance(int d) //选取了d行
{
/*重复覆盖
1、如果矩阵为空,得到结果,返回
2、从矩阵中选择一列,以选取最少元素的列为优化方式
3、删除该列及其覆盖的行
4、对该列的每一行元素:删除一行及其覆盖的列,
5、进行下一层搜索,如果成功则返回
6、恢复现场,跳至4
7、恢复所选择行
*/
if (d + f_check() >=ansd)return;
if (R[] == )//全部覆盖了
{
//全覆盖了之后的操作
if(ansd > d) ansd=d;
return;
}
int c = R[];//表头结点指向的第一个列
for (int i = R[]; i != ; i = R[i])//枚举列头指针
if (S[i]<S[c])//找到列中元素个数最少的
c = i;
//remove(c);//将该列删去(精确覆盖)
for (int i = D[c]; i != c; i = D[i])
{//枚举该列的元素
ans[d] = Row[i];//记录该列元素的行
remove(i);//新增(重复覆盖)
for (int j = R[i]; j != i; j = R[j])
remove(j);//remove(Col[j])(精确覆盖)
dance(d + );
for (int j = L[i]; j != i; j = L[j])
resume(j);//resume(Col[j])(精确覆盖)
resume(i);//新增(重复覆盖)
}
//resume(c);(精确覆盖)
}
}dlx;
int mp[][];
int main()
{
int n, m,n1,m1;
while (~scanf("%d%d",&n,&m))
{
int cnt = ;
for (int i = ; i <= n; i++)
{
for (int j = ; j <= m; j++)
{
scanf("%d", &mp[i][j]);
if (mp[i][j]) mp[i][j] = ++cnt;
}
}
scanf("%d%d", &n1, &m1);
dlx.init(n*m,cnt);
cnt = ;
for (int i = ; i <=n; i++)
{
for (int j = ; j <=m; j++)
{
for (int x = ; x <n1&&i+x<=n; x++)
{
for (int z = ; z < m1&&z+j<=m; z++)
{
if (mp[i+x][j+z]) dlx.link(cnt, mp[i+x][j+z]);
}
}
cnt++;
}
}
dlx.ansd = 0x3f3f3f3f;
dlx.dance();
printf("%d\n", dlx.ansd);
}
return ;
}
5、poj 1084 Square Destroyer
题意:给出由火柴构成的网格,现在在已经删去若干根火柴后,问最少还需要删去多少火柴,使得网格中的所有正方形被破坏。
思路:DLK重复覆盖,把每根火柴作为行,每个正方形看成列,如果某根火柴可以构成该正方形的一条边,则link.
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 60//55个格子
#define M 70//60个火柴
#define NN 300//每个格子4根火柴,算上辅助是5
#define inf 0x3f3f3f3f
using namespace std;
/*60火柴,25格*/
int ans;
struct DLX
{
int U[NN], D[NN], L[NN], R[NN], C[NN];
int H[M], T[N], cnt;
inline void init()
{
cnt = ;
memset(U, , sizeof(U));
memset(D, , sizeof(D));
memset(L, , sizeof(L));
memset(R, , sizeof(R));
memset(C, , sizeof(C));
memset(H, , sizeof(H));
memset(T, , sizeof(T));
}
inline void newnode(int x, int y)
{
C[++cnt] = y; T[y]++; if (!H[x])H[x] = L[cnt] = R[cnt] = cnt;
else L[cnt] = H[x], R[cnt] = R[H[x]];
R[H[x]] = L[R[H[x]]] = cnt, H[x] = cnt; U[cnt] = U[y], D[cnt] = y;
U[y] = D[U[y]] = cnt;
}
int id[N][N][], eid[][][];
bool destroy[N], map[N][M];
inline void build()
{
init();
int i, j, k, n, m;
int nmrx = , npon = ;
scanf("%d%d", &n, &m);
memset(destroy, , sizeof(destroy));
memset(map, , sizeof(map));
for (k = ; k<n; k++)for (i = ; i + k <= n; i++)for (j = ; j + k <= n; j++)id[k][i][j] = ++nmrx;
for (i = ; i <= n; i++)
{
for (j = ; j <= n; j++)eid[][i][j] = ++npon;
for (j = ; j <= n + ; j++)eid[][i][j] = ++npon;
}
for (i = ; i <= n; i++)eid[][n + ][i] = ++npon;
for (k = ; k<n; k++)
for (i = ; i + k <= n; i++)
for (j = ; j + k <= n; j++)
{
int A = id[k][i][j], temp;
for (temp = j; temp <= j + k; temp++)map[A][eid[][i][temp]] = map[A][eid[][i + k + ][temp]] = ;
for (temp = i; temp <= i + k; temp++)map[A][eid[][temp][j]] = map[A][eid[][temp][j + k + ]] = ;
}
for (i = ; i <= m; i++)
{
scanf("%d", &k);
for (j = ; j <= nmrx; j++)if (map[j][k])destroy[j] = ;
}
for (i = ; i <= nmrx; i++)if (!destroy[i])
{
U[i] = D[i] = i;
L[i] = L[], R[i] = ;
L[] = R[L[]] = i;
}
cnt = nmrx;
for (i = ; i <= nmrx; i++)
if (!destroy[i])
for (j = ; j <= npon; j++)
if (map[i][j])
newnode(j, i);
}
inline void remove(int x)
{
int i = x;
do
{
R[L[i]] = R[i];
L[R[i]] = L[i];
i = D[i];
} while (i != x);
}
inline void resume(int x)
{
int i = x;
do
{
R[L[i]] = i;
L[R[i]] = i;
i = U[i];
} while (i != x);
}
inline void dfs(int x)
{
if (x >= ans)return;
if (!R[])
{
ans = x;
return;
}
int S = R[], W = T[S], i, j;
int del[N], num;
for (i = R[S]; i; i = R[i])if (T[i]<W)
{
W = T[i];
S = i;
}
remove(S);
for (i = D[S]; i != S; i = D[i])
{
del[num = ] = R[i];
for (j = R[R[i]]; j != R[i]; j = R[j])del[++num] = j;
for (j = ; j <= num; j++)remove(del[j]);
dfs(x + );
for (j = num; j; j--)resume(del[j]);
}
resume(S);
return;
}
}dlx;
int main()
{
int g;
scanf("%d", &g);
while (g--)
{
ans = inf;
dlx.build();
dlx.dfs();
printf("%d\n", ans);
}
return ;
}
6、poj 3074 Sudoku
题意:解9*9的数独。
思路:主要建图,每个不确定格子至多可以填9种数字,一共9*9*9行,对于每个格子,其自身、所在行、所在列、所在块均存在约束,共9*9*4列。
#include <iostream>
#include <stdio.h>
#include <string.h>
//精确覆盖问题的定义:给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1
const int MN = **+;//最大行数,共有9*9个格子,每个格子可以放1~9
const int MM = *+*+*+*+;//最大列数
//用第(i - 1) * 9 + j列为1表示i行j列的已经填数。一共占用81列。
//用81 + (i - 1) * 9 + v列表示第i行已经有v这个值。一共占用81列。
//用162 + (j - 1) * 9 + v列表示第j列已经有v这个值。一共占用81列。
//用243 + (3 * ((i - 1) / 3) + (j + 2) / 3 - 1) + v列表示第3*((i - 1) / 3) + (j + 2) / 3宫格已经有v这个值。一共占用81列。
const int MNN = MN*MM; //最大点数 struct DLX
{
int n, m, si;//n行数m列数si目前有的节点数
//十字链表组成部分
int U[MNN], D[MNN], L[MNN], R[MNN], Row[MNN], Col[MNN];
//第i个结点的U向上指针D下L左R右,所在位置Row行Col列
int H[MN], S[MM]; //记录行的选择情况和列的覆盖情况
int ansd, ans[MN];
void init(int _n, int _m) //初始化空表
{
n = _n;
m = _m;
for (int i = ; i <= m; i++) //初始化第一横行(表头)
{
S[i] = ;
U[i] = D[i] = i; //目前纵向的链是空的
L[i] = i - ;
R[i] = i + ; //横向的连起来
}
R[m] = ; L[] = m;
si = m; //目前用了前0~m个结点
for (int i = ; i <= n; i++)
H[i] = -;
}
void link(int r, int c) //插入点(r,c)
{
++S[Col[++si] = c]; //si++;Col[si]=c;S[c]++;
Row[si] = r;//si该结点的行数为r
D[si] = D[c];//向下指向c的下面的第一个结点
U[D[c]] = si;//c的下面的第一个结点的上面为si
U[si] = c;//si的上面为列指针
D[c] = si;//列指针指向的第一个该列中的元素设为si
if (H[r]<)//如果第r行没有元素
H[r] = L[si] = R[si] = si;
else
{
R[si] = R[H[r]];//si的右边为行指针所指的右边第一个元素
L[R[H[r]]] = si;//行指针所指的右边第一个元素的左侧为si
L[si] = H[r];//si的左侧为行指针
R[H[r]] = si;//行指针的右侧为si
}
}
void remove(int c) //列表中删掉c列
{
L[R[c]] = L[c];//表头操作 //c列头指针的右边的元素的左侧指向c列头指针左边的元素
R[L[c]] = R[c];//c列头指针的左边的元素的右侧指向c列头指针右边的元素
for (int i = D[c]; i != c; i = D[i])//遍历该列的所有元素
for (int j = R[i]; j != i; j = R[j])
{//对于该列的某个元素所在的行进行遍历
U[D[j]] = U[j];//把该元素从其所在列中除去
D[U[j]] = D[j];
--S[Col[j]];//该元素所在的列数目减一
}
}
void resume(int c) //恢复c列
{
for (int i = U[c]; i != c; i = U[i])//枚举该列元素
for (int j = L[i]; j != i; j = L[j])//枚举该列元素所在的行
++S[Col[U[D[j]] = D[U[j]] = j]];//D[U[j]]=j;U[D[j]]=j;S[Col[j]]++;
L[R[c]] = R[L[c]] = c;//c列头指针左右相连
}
bool dance(int d) //选取了d行
{
if (R[] == )//全部覆盖了
{
//全覆盖了之后的操作
ansd = d;
return ;
}
int c = R[];//表头结点指向的第一个列
for (int i = R[]; i != ; i = R[i])//枚举列头指针
if (S[i]<S[c])//找到列中元素个数最少的
c = i;
remove(c);//将该列删去
for (int i = D[c]; i != c; i = D[i])
{//枚举该列的元素
ans[d] = Row[i];//记录该列元素的行
for (int j = R[i]; j != i; j = R[j])
remove(Col[j]);//将该列的某个元素的行上的元素所在的列都删去
if (dance(d + ))
return ;
for (int j = L[i]; j != i; j = L[j])
resume(Col[j]);
}
resume(c);
return ;
}
}dlx;
char s[],path[];
struct node
{
int r, c, v;
}nds[MN];
int main()
{
while (~scanf("%s",s))
{
if (s[] == 'e')break;
dlx.init(**,**);
int r=;
for (int i = ; i <= ; i++)
{
for (int j = ; j <= ; j++)
{
if (s[(i - ) * + j - ] == '.')
{
for (int z = ; z <= ; z++)
{
dlx.link(r, (i - ) * + j);
dlx.link(r, + (i - ) * + z);
dlx.link(r, + (j - ) * + z);
dlx.link(r, + (((i - ) / ) * + (j + ) / - ) * + z);
nds[r].r = i, nds[r].c = j, nds[r].v = z;
r++;
}
}
else
{
int z = s[(i - ) * + j - ] - '';
dlx.link(r, (i - ) * + j);
dlx.link(r, + (i - ) * + z);
dlx.link(r, + (j - ) * + z);
dlx.link(r, + (((i - ) / ) * + (j + ) / - ) * + z);
nds[r].r = i, nds[r].c = j, nds[r].v = z;
r++;
}
}
}
dlx.ansd = -;
dlx.dance();
int deep = dlx.ansd;
for (int i = ; i < deep; i++)
{
int posr = dlx.ans[i];
path[(nds[posr].r - ) * + nds[posr].c - ] = '' + nds[posr].v;
}
path[deep] = '\0';
printf("%s\n", path);
}
return ;
}
/*
.2738..1..1...6735.......293.5692.8...........6.1745.364.......9518...7..8..6534.
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
end
*/
7、POJ 3076 Sudoku
题意:解16*16的数独。
思路:和上题近似,不过维数从9变为16.
#include <iostream>
#include <stdio.h>
#include <string.h>
//精确覆盖问题的定义:给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1
const int MN = ** + ;//最大行数,共有16*16个格子,每个格子可以放1~16(A~P)
const int MM = ** + ;//最大列数
const int MNN = MN*MM;//最大点数 struct DLX
{
int n, m, si;//n行数m列数si目前有的节点数
//十字链表组成部分
int U[MNN], D[MNN], L[MNN], R[MNN], Row[MNN], Col[MNN];
//第i个结点的U向上指针D下L左R右,所在位置Row行Col列
int H[MN], S[MM]; //记录行的选择情况和列的覆盖情况
int ansd, ans[MN];
void init(int _n, int _m) //初始化空表
{
n = _n;
m = _m;
for (int i = ; i <= m; i++) //初始化第一横行(表头)
{
S[i] = ;
U[i] = D[i] = i; //目前纵向的链是空的
L[i] = i - ;
R[i] = i + ; //横向的连起来
}
R[m] = ; L[] = m;
si = m; //目前用了前0~m个结点
for (int i = ; i <= n; i++)
H[i] = -;
}
void link(int r, int c) //插入点(r,c)
{
++S[Col[++si] = c]; //si++;Col[si]=c;S[c]++;
Row[si] = r;//si该结点的行数为r
D[si] = D[c];//向下指向c的下面的第一个结点
U[D[c]] = si;//c的下面的第一个结点的上面为si
U[si] = c;//si的上面为列指针
D[c] = si;//列指针指向的第一个该列中的元素设为si
if (H[r]<)//如果第r行没有元素
H[r] = L[si] = R[si] = si;
else
{
R[si] = R[H[r]];//si的右边为行指针所指的右边第一个元素
L[R[H[r]]] = si;//行指针所指的右边第一个元素的左侧为si
L[si] = H[r];//si的左侧为行指针
R[H[r]] = si;//行指针的右侧为si
}
}
void remove(int c) //列表中删掉c列
{
L[R[c]] = L[c];//表头操作 //c列头指针的右边的元素的左侧指向c列头指针左边的元素
R[L[c]] = R[c];//c列头指针的左边的元素的右侧指向c列头指针右边的元素
for (int i = D[c]; i != c; i = D[i])//遍历该列的所有元素
for (int j = R[i]; j != i; j = R[j])
{//对于该列的某个元素所在的行进行遍历
U[D[j]] = U[j];//把该元素从其所在列中除去
D[U[j]] = D[j];
--S[Col[j]];//该元素所在的列数目减一
}
}
void resume(int c) //恢复c列
{
for (int i = U[c]; i != c; i = U[i])//枚举该列元素
for (int j = L[i]; j != i; j = L[j])//枚举该列元素所在的行
++S[Col[U[D[j]] = D[U[j]] = j]];//D[U[j]]=j;U[D[j]]=j;S[Col[j]]++;
L[R[c]] = R[L[c]] = c;//c列头指针左右相连
}
bool dance(int d) //选取了d行
{
if (R[] == )//全部覆盖了
{
//全覆盖了之后的操作
ansd = d;
return ;
}
int c = R[];//表头结点指向的第一个列
for (int i = R[]; i != ; i = R[i])//枚举列头指针
if (S[i]<S[c])//找到列中元素个数最少的
c = i;
remove(c);//将该列删去
for (int i = D[c]; i != c; i = D[i])
{//枚举该列的元素
ans[d] = Row[i];//记录该列元素的行
for (int j = R[i]; j != i; j = R[j])
remove(Col[j]);//将该列的某个元素的行上的元素所在的列都删去
if (dance(d + ))
return ;
for (int j = L[i]; j != i; j = L[j])
resume(Col[j]);
}
resume(c);
return ;
}
}dlx;
char s[];
char path[ * + ];
struct node
{
int r, c, v;
}nds[MN];
void addlink(int r, int c, int v, int& cnt)
{
dlx.link(cnt, (r - ) * + c);
dlx.link(cnt, * + (r - ) * + v);
dlx.link(cnt, ** + (c - ) * + v);
dlx.link(cnt, ** + (((r-)/)*+(c-)/) * + v);
nds[cnt].r = r, nds[cnt].c = c, nds[cnt].v = v;
cnt++;
}
int main()
{
while (~scanf("%s", s))
{ dlx.init(**, * * );
int r = ;
for (int i = ; i < ; i++)
{
if (s[i] == '-')
{
for (int k = ; k <= ; k++)
{
addlink(, i + , k,r);
}
}
else
{
int v = s[i] - 'A' + ;
addlink(, i + , v, r);
}
}
for (int i = ; i <= ; i++)
{
scanf("%s", s);
for (int j = ; j <= ; j++)
{
if (s[j-] == '-')
{
for (int k = ; k <= ; k++)
{
addlink(i, j, k, r);
}
}
else
{
int v = s[j-] - 'A' + ;
addlink(i,j, v, r);
}
}
}
dlx.ansd = -;
dlx.dance();
int deep = dlx.ansd;
for (int i = ; i < deep; i++)
{
int posr = dlx.ans[i];
path[(nds[posr].r - ) * + nds[posr].c - ] = 'A' + nds[posr].v-;
}
path[deep] = '\0';
for (int i = ; i < ; i++)
{
for (int j = ; j < ; j++) printf("%c", path[i * + j]);
printf("\n");
}
printf("\n");
}
return ;
}
8、hdu 4069 Squiggly Sudoku
题意:解9*9的数独,不过每一块的形状不一定是矩形。
思路:先用DFS分块,然后思路和6相同。
#include <iostream>
#include <stdio.h>
#include <string.h>
//精确覆盖问题的定义:给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1
const int MN = * * + ;//最大行数,共有9*9个格子,每个格子可以放1~9
const int MM = * + * + * + * + ;//最大列数
//用第(i - 1) * 9 + j列为1表示i行j列的已经填数。一共占用81列。
//用81 + (i - 1) * 9 + v列表示第i行已经有v这个值。一共占用81列。
//用162 + (j - 1) * 9 + v列表示第j列已经有v这个值。一共占用81列。
//用243 + (3 * ((i - 1) / 3) + (j + 2) / 3 - 1) + v列表示第3*((i - 1) / 3) + (j + 2) / 3宫格已经有v这个值。一共占用81列。
const int MNN = MN*MM; //最大点数
int Count;//几种解决方案
bool iscontinue;//当找到两种及以上解决方案时退出
struct DLX
{
int n, m, si;//n行数m列数si目前有的节点数
//十字链表组成部分
int U[MNN], D[MNN], L[MNN], R[MNN], Row[MNN], Col[MNN];
//第i个结点的U向上指针D下L左R右,所在位置Row行Col列
int H[MN], S[MM]; //记录行的选择情况和列的覆盖情况
int ansd, ans[MN];
int tans[MN];//保存第一个解决方案
void init(int _n, int _m) //初始化空表
{
n = _n;
m = _m;
for (int i = ; i <= m; i++) //初始化第一横行(表头)
{
S[i] = ;
U[i] = D[i] = i; //目前纵向的链是空的
L[i] = i - ;
R[i] = i + ; //横向的连起来
}
R[m] = ; L[] = m;
si = m; //目前用了前0~m个结点
for (int i = ; i <= n; i++)
H[i] = -;
}
void link(int r, int c) //插入点(r,c)
{
++S[Col[++si] = c]; //si++;Col[si]=c;S[c]++;
Row[si] = r;//si该结点的行数为r
D[si] = D[c];//向下指向c的下面的第一个结点
U[D[c]] = si;//c的下面的第一个结点的上面为si
U[si] = c;//si的上面为列指针
D[c] = si;//列指针指向的第一个该列中的元素设为si
if (H[r]<)//如果第r行没有元素
H[r] = L[si] = R[si] = si;
else
{
R[si] = R[H[r]];//si的右边为行指针所指的右边第一个元素
L[R[H[r]]] = si;//行指针所指的右边第一个元素的左侧为si
L[si] = H[r];//si的左侧为行指针
R[H[r]] = si;//行指针的右侧为si
}
}
void remove(int c) //列表中删掉c列
{
L[R[c]] = L[c];//表头操作 //c列头指针的右边的元素的左侧指向c列头指针左边的元素
R[L[c]] = R[c];//c列头指针的左边的元素的右侧指向c列头指针右边的元素
for (int i = D[c]; i != c; i = D[i])//遍历该列的所有元素
for (int j = R[i]; j != i; j = R[j])
{//对于该列的某个元素所在的行进行遍历
U[D[j]] = U[j];//把该元素从其所在列中除去
D[U[j]] = D[j];
--S[Col[j]];//该元素所在的列数目减一
}
}
void resume(int c) //恢复c列
{
for (int i = U[c]; i != c; i = U[i])//枚举该列元素
for (int j = L[i]; j != i; j = L[j])//枚举该列元素所在的行
++S[Col[U[D[j]] = D[U[j]] = j]];//D[U[j]]=j;U[D[j]]=j;S[Col[j]]++;
L[R[c]] = R[L[c]] = c;//c列头指针左右相连
}
void dance(int d) //选取了d行
{
if (!iscontinue) return;
if (R[] == )//全部覆盖了
{
//全覆盖了之后的操作
ansd = d;
if (Count == )
{
memcpy(tans, ans, sizeof(ans));
Count++;
}
else if (Count == ) iscontinue = false;
return;
}
int c = R[];//表头结点指向的第一个列
for (int i = R[]; i != ; i = R[i])//枚举列头指针
if (S[i]<S[c])//找到列中元素个数最少的
c = i;
remove(c);//将该列删去
for (int i = D[c]; i != c; i = D[i])
{//枚举该列的元素
ans[d] = Row[i];//记录该列元素的行
for (int j = R[i]; j != i; j = R[j])
remove(Col[j]);//将该列的某个元素的行上的元素所在的列都删去
dance(d + );
for (int j = L[i]; j != i; j = L[j])
resume(Col[j]);
}
resume(c);
return ;
}
}dlx;
struct node
{
int r, c, v;
}nds[MN];
int dr[] = { ,,,- };
int dc[] = { ,-,, };
int mp[][];//记录值
bool wall[][][][];//记录某个点上下左右是否有墙
//[0][0]左,[0][1]右,[1][0]上,[1][1]下
int block[][];//标记分块
int solution[][];
void DFS(int r, int c, int flag)
{
block[r][c] = flag;
if (!wall[r][c][][]&&!block[r][c-]) DFS(r, c - , flag);
if (!wall[r][c][][]&&!block[r][c+]) DFS(r, c + , flag);
if (!wall[r][c][][]&&!block[r-][c]) DFS(r - , c, flag);
if (!wall[r][c][][]&&!block[r+][c]) DFS(r + , c, flag);
}
int main()
{
int t,Case=;
scanf("%d", &t);
while (t--)
{
int num;
memset(mp, , sizeof(mp));
memset(wall, , sizeof(wall));
memset(block, , sizeof(block));
for (int i = ; i <= ; i++)
{
for (int j = ; j <= ; j++)
{
scanf("%d", &num);
if (num - >= ) num -= , wall[i][j][][] = true;
if (num - >= ) num -= , wall[i][j][][] = true;
if (num - >= ) num -= , wall[i][j][][] = true;
if (num - >= ) num -= , wall[i][j][][] = true;
mp[i][j] = num;
}
}
int id = ;
for (int i = ; i <= ; i++)
{
for (int j = ; j <= ; j++)
{
if (!block[i][j])
{
DFS(i, j, ++id);
}
}
}
dlx.init( * * , * * );
int Cnt = ;
for (int i = ; i <= ; i++)
{
for (int j = ; j <= ; j++)
{
if (mp[i][j]==)
{
for (int z = ; z <= ; z++)
{
dlx.link(Cnt, (i - ) * + j);
dlx.link(Cnt, + (i - ) * + z);
dlx.link(Cnt, + (j - ) * + z);
dlx.link(Cnt, + (block[i][j]-) * + z);
nds[Cnt].r = i, nds[Cnt].c = j, nds[Cnt].v = z;
Cnt++;
}
}
else
{
int z =mp[i][j];
dlx.link(Cnt, (i - ) * + j);
dlx.link(Cnt, + (i - ) * + z);
dlx.link(Cnt, + (j - ) * + z);
dlx.link(Cnt, + (block[i][j]-) * + z);
nds[Cnt].r = i, nds[Cnt].c = j, nds[Cnt].v = z;
Cnt++;
}
}
}
dlx.ansd = -;
iscontinue = true;
Count = ;
dlx.dance();
printf("Case %d:\n", Case++);
if (Count == )
{
printf("No solution\n");
continue;
}
else if (!iscontinue)
{
printf("Multiple Solutions\n");
continue;
}
int deep = dlx.ansd;
for (int i = ; i < deep; i++)
{
int posr = dlx.tans[i];
solution[nds[posr].r][nds[posr].c] = nds[posr].v;
}
for (int i = ; i <= ; i++)
{
for (int j = ; j <= ; j++)
{
printf("%d", solution[i][j]);
}
printf("\n");
}
}
return ;
}
9、hdu 3335 Divisibility
题意:给出n个数,每次从中取出一个数后,不能再取它的倍数或其因子。问最多能取多少个数,
思路:DLK求极大重复覆盖(因为一个数可能是若干个数的倍数,所以用重复覆盖)。当一个数为另一个数的因子或倍数时,建边。
#include <iostream>
#include <stdio.h>
#include <string.h>
//重复覆盖:找到一些行,使得这些行的集合中每列至少有一个1
const int MN = ;//最大行数
const int MM = ;//最大列数
const int MNN = MM*MN + MM + MN + ; //最大点数 struct DLX
{
int n, m, si;//n行数m列数si目前有的节点数
//十字链表组成部分
int U[MNN], D[MNN], L[MNN], R[MNN], Row[MNN], Col[MNN];
//第i个结点的U向上指针D下L左R右,所在位置Row行Col列
int H[MN], S[MM]; //记录行的选择情况和列的覆盖情况
int ansd, ans[MN];
void init(int _n, int _m) //初始化空表
{
n = _n;
m = _m;
for (int i = ; i <= m; i++) //初始化第一横行(表头)
{
S[i] = ;
U[i] = D[i] = i; //目前纵向的链是空的
L[i] = i - ;
R[i] = i + ; //横向的连起来
}
R[m] = ; L[] = m;
si = m; //目前用了前0~m个结点
for (int i = ; i <= n; i++)
H[i] = -;
}
void link(int r, int c) //插入点(r,c)
{
++S[Col[++si] = c]; //si++;Col[si]=c;S[c]++;
Row[si] = r;//si该结点的行数为r
D[si] = D[c];//向下指向c的下面的第一个结点
U[D[c]] = si;//c的下面的第一个结点的上面为si
U[si] = c;//si的上面为列指针
D[c] = si;//列指针指向的第一个该列中的元素设为si
if (H[r]<)//如果第r行没有元素
H[r] = L[si] = R[si] = si;
else
{
R[si] = R[H[r]];//si的右边为行指针所指的右边第一个元素
L[R[H[r]]] = si;//行指针所指的右边第一个元素的左侧为si
L[si] = H[r];//si的左侧为行指针
R[H[r]] = si;//行指针的右侧为si
}
}
void remove(int c) //列表中删掉c列
{
//L[R[c]] = L[c];//表头操作 //c列头指针的右边的元素的左侧指向c列头指针左边的元素
//R[L[c]] = R[c];//c列头指针的左边的元素的右侧指向c列头指针右边的元素
//for (int i = D[c]; i != c; i = D[i])//遍历该列的所有元素
// for (int j = R[i]; j != i; j = R[j])
// {//对于该列的某个元素所在的行进行遍历
// U[D[j]] = U[j];//把该元素从其所在列中除去
// D[U[j]] = D[j];
// --S[Col[j]];//该元素所在的列数目减一
// }
/*重复覆盖*//*c为元素编号,而非列号*//*即删除该元素所在的列,包括它自身和列头指针*/
for (int i = D[c]; i != c; i = D[i])
{
L[R[i]] = L[i], R[L[i]] = R[i];
}
}
void resume(int c)//恢复c列
{
//for (int i = U[c]; i != c; i = U[i])//枚举该列元素
// for (int j = L[i]; j != i; j = L[j])//枚举该列元素所在的行
// ++S[Col[U[D[j]] = D[U[j]] = j]];//D[U[j]]=j;U[D[j]]=j;S[Col[j]]++;
//L[R[c]] = R[L[c]] = c;//c列头指针左右相连
/*重复覆盖*/
for (int i = U[c]; i != c; i = U[i])
{
L[R[i]] = R[L[i]] = i;
}
}
int f_check()//精确覆盖区估算剪枝
{
/*
强剪枝。这个 剪枝利用的思想是A*搜索中的估价函数。即,对于当前的递归深度K下的矩阵,估计其最好情况下(即最少还需要多少步)才能出解。也就是,如果将能够覆盖当 前列的所有行全部选中,去掉这些行能够覆盖到的列,将这个操作作为一层深度。重复此操作直到所有列全部出解的深度是多少。如果当前深度加上这个估价函数返 回值,其和已然不能更优(也就是已经超过当前最优解),则直接返回,不必再搜。
*/
int vis[MNN];
memset(vis, , sizeof(vis));
int ret = ;
for (int c = R[]; c != ; c = R[c]) vis[c] = true;
for (int c = R[]; c != ; c = R[c])
if (vis[c])
{
ret++;
vis[c] = false;
for (int i = D[c]; i != c; i = D[i])
for (int j = R[i]; j != i; j = R[j])
vis[Col[j]] = false;
}
return ret;
}
void dance(int d) //选取了d行,limit为限制选取的最大行数
{
/*重复覆盖
1、如果矩阵为空,得到结果,返回
2、从矩阵中选择一列,以选取最少元素的列为优化方式
3、删除该列及其覆盖的行
4、对该列的每一行元素:删除一行及其覆盖的列,
5、进行下一层搜索,如果成功则返回
6、恢复现场,跳至4
7、恢复所选择行
*/
//if (ansd!=-1&&d > ansd)return;
//if (ansd!=-1&&d + f_check() >ansd)return;
if (R[] == )//全部覆盖了
{
//全覆盖了之后的操作
if(ansd==-)ansd = d;
else if (d > ansd) ansd = d;
}
int c = R[];//表头结点指向的第一个列
for (int i = R[]; i != ; i = R[i])//枚举列头指针
if (S[i]<S[c])//找到列中元素个数最少的
c = i;
//remove(c);//将该列删去(精确覆盖)
for (int i = D[c]; i != c; i = D[i])
{//枚举该列的元素
ans[d] = Row[i];//记录该列元素的行
remove(i);//新增(重复覆盖)
for (int j = R[i]; j != i; j = R[j])
remove(j);//remove(Col[j])(精确覆盖)
dance(d + );
for (int j = L[i]; j != i; j = L[j])
resume(j);//resume(Col[j])(精确覆盖)
resume(i);//新增(重复覆盖)
}
//resume(c);(精确覆盖)
return;
}
}dlx;
long long num[];
int main()
{
int n;
int t;
scanf("%d", &t); while (t--)
{
scanf("%d", &n);
for (int i = ; i <= n; i++)
{
scanf("%lld", &num[i]);
}
dlx.init(n, n);
for (int i = ; i <=n; i++)
{
for (int j = ; j <= n; j++) if (num[i] % num[j] == ||num[j]%num[i]==) dlx.link(i, j);
}
dlx.ansd = -;
dlx.dance();
printf("%d\n",dlx.ansd);
}
return ;
}
拓展:二分图求最大独立集(选最多的数使得两两之间不能整除)。最大独立集 = 点数 - 二分图最大匹配。
#include<iostream>
#include<queue>
#include<memory.h>
#include<algorithm>
#include<cmath>
using namespace std;
int n,dis,Ny,Nx;
const int maxn = ;//最大行数
const int maxm = ;//最大列数
const int maxk = ;
const int INF = 0x7fffffff;
bool mp[maxk][maxk];//1表示该ij可以匹配
int cx[maxk];//记录x集合中匹配的y元素是哪一个
int dx[maxk];
int cy[maxk];//记录y集合中匹配的x元素是哪一个
int dy[maxk];
int vis[maxk];//标记该顶点是否访问过
bool searchP(void) //BFS
{
queue <int> Q;
dis = INF;
memset(dx, -, sizeof(dx));
memset(dy, -, sizeof(dy));
for (int i = ; i <= Nx; i++)
if (cx[i] == -)
{
Q.push(i); dx[i] = ;
}
while (!Q.empty())
{
int u = Q.front(); Q.pop();
if (dx[u] > dis) break; //说明该增广路径长度大于dis还没有结束,等待下一次BFS在扩充
for (int v = ; v <= Ny; v++)
if (mp[u][v] && dy[v] == -)
{ //v是未匹配点
dy[v] = dx[u] + ;
if (cy[v] == -) dis = dy[v]; //得到本次BFS的最大遍历层次
else
{
dx[cy[v]] = dy[v] + ; //v是匹配点,继续延伸
Q.push(cy[v]);
}
}
}
return dis != INF;
} bool DFS(int u)
{
for (int v = ; v <= Ny; v++)
if (!vis[v] && mp[u][v] && dy[v] == dx[u] + )
{
vis[v] = ;
if (cy[v] != - && dy[v] == dis) continue; //层次(也就是增广路径的长度)大于本次查找的dis,是searchP被break的情况,也就是还不确定是否是增广路径,只有等再次调用searchP()在判断。
if (cy[v] == - || DFS(cy[v]))
{ //是增广路径,更新匹配集
cy[v] = u; cx[u] = v;
return ;
}
}
return ;
} int MaxMatch()
{
int res = ;
memset(cx, -, sizeof(cx));
memset(cy, -, sizeof(cy));
while (searchP())
{
memset(vis, , sizeof(vis));
for (int i = ; i <= Nx; i++)
if (cx[i] == - && DFS(i)) res++; //查找到一个增广路径,匹配数res++
}
return res;
}
long long num[];
int main()
{
int C;
scanf("%d", &C);
int Case = ;
while (C--)
{
scanf("%d", &n);
for (int i = ; i <= n; i++) scanf("%lld", &num[i]);
sort(num + , num + + n);
memset(mp, , sizeof(mp));
for (int i = ; i <= n; i++)
{
for (int j = i+; j <=n; j++) if (num[j] % num[i] == ) mp[j][i] = ;
} Nx = n, Ny =n;
int ans = MaxMatch();
printf("%d\n", n - ans);//无向二分图:最小路径覆盖数 = "拆点"前原图的顶点数 - 最大匹配数/2
}
return ;
}
Dancing Link专题的更多相关文章
- 【Dancing Link专题】解题报告
DLX用于优化精确覆盖问题,由于普通的DFS暴力搜索会超时,DLX是一个很强有力的优化手段,其实DLX的原理很简单,就是利用十字链表的快速删除和恢复特点,在DFS时删除一些行和列以减小查找规模,使得搜 ...
- Dancing Link 详解(转载)
Dancing Link详解: http://www.cnblogs.com/grenet/p/3145800.html Dancing Link求解数独: http://www.cnblogs.co ...
- dancing link模板
#include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #i ...
- dancing link 学习资源导航+心得
dancing link简直是求解数独的神器,NOIP2009最后一题靶形数独,DFS 各种改变搜索顺序 都没法过,最后还是用了卡时过得.用dancing link写,秒杀所有数据,总时间才400ms ...
- bzoj 2706: [SDOI2012]棋盘覆盖 Dancing Link
2706: [SDOI2012]棋盘覆盖 Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 255 Solved: 77[Submit][Status] ...
- hustoj 1017 - Exact cover dancing link
1017 - Exact cover Time Limit: 15s Memory Limit: 128MB Special Judge Submissions: 5851 Solved: 3092 ...
- hdu 1426 Sudoku Killer ( Dancing Link 精确覆盖 )
利用 Dancing Link 来解数独 详细的能够看 lrj 的训练指南 和 < Dancing Links 在搜索中的应用 >这篇论文 Dancing Link 来求解数独 , ...
- Dancing Link --- 模板题 HUST 1017 - Exact cover
1017 - Exact cover Problem's Link: http://acm.hust.edu.cn/problem/show/1017 Mean: 给定一个由0-1组成的矩阵,是否 ...
- dancing link
http://www.cnblogs.com/grenet/p/3145800.html 链接给的博客写的很好,比较好懂. 可惜不是c语言... 于是决定自己要建一个模板. 一道裸题:hustoj 1 ...
随机推荐
- crontab执行脚本与手动执行结果不一致
反正网上说是环境变量问题,我就直接在脚本第二行加入以下代码: source /etc/profile source ~/.bashrc 问题是解决了!
- Shallow Heap & Retained Heap
所有包含Heap Profling功能的工具(MAT, Yourkit, JProfiler, TPTP等)都会使用到两个名词,一个是Shallow Size,另一个是 Retained Size. ...
- Ocelot + IdentityServer4 坑自己
现像是 connect/userinfo 可以访问 但是api都提示401 后面发现是在appsettings.json "Options": {"Authority&q ...
- Trees in a Wood. UVA 10214 欧拉函数或者容斥定理 给定a,b求 |x|<=a, |y|<=b这个范围内的所有整点不包括原点都种一棵树。求出你站在原点向四周看到的树的数量/总的树的数量的值。
/** 题目:Trees in a Wood. UVA 10214 链接:https://vjudge.net/problem/UVA-10214 题意:给定a,b求 |x|<=a, |y|&l ...
- java 服务接口API限流 Rate Limit
一.场景描述 很多做服务接口的人或多或少的遇到这样的场景,由于业务应用系统的负载能力有限,为了防止非预期的请求对系统压力过大而拖垮业务应用系统. 也就是面对大流量时,如何进行流量控制? 服务接口的流量 ...
- Servlet 客户端 HTTP 请求
当浏览器请求网页时,它会向 Web 服务器发送特定信息,这些信息不能被直接读取,因为这些信息是作为 HTTP 请求的头的一部分进行传输的.您可以查看 HTTP 协议 了解更多相关信息. 以下是来自于浏 ...
- 如需在 HTML 页面中插入 JavaScript,请使用 <script> 标签。
如需在 HTML 页面中插入 JavaScript,请使用 <script> 标签. <script> 和 </script> 会告诉 JavaScript 在何处 ...
- zmq重点
The zmq_msg_send(3) method does not actually send the message to the socket connection(s). It queues ...
- php 正则匹配省市区
匹配指定前后内容中的值 如匹配/xxx-abc中的abc preg_match('/\/xxx-([^<]*)/i', $route, $matches); echo $matches[1]; ...
- linux终端常用命令
常用的信息显示命令 命令#pwd 用于在屏幕上输出当前的工作目录. 命令#stat 用于显示指定文件的相关信息. 命令#uname -a 用于显示操作系统信息. 命令#hostname 用于显示当前本 ...