精确/重复覆盖问题区分

用的全都是 DLX (好像只能用 DLX?我也不知道)

没想到一个小小的精确覆盖问题和重复覆盖问题,需要注意的不同点这么重要!

精确覆盖问题

怎么实现?

1 找到当前没有被删掉的列中 1 的个数最小的一列

2 把这一列删掉,这里删掉是指先把当前的点的左右链表删掉,然后再把这个点所在的列的点的上下链表删掉

3 枚举这一列的每个 \(1\),计入答案,并把这一列中有 \(1\) 的行中有 \(1\) 的列全部删掉

删除的方法就是把每个 1 的点的上下链表给删掉,这样下次遍历到当前列,从上往下遍历的时候就不会把这个点遍历到

4 继续递归

5 恢复 \(3\) 操作

6 恢复 \(2\) 操作

7 删除答案

8 如果还是找不到答案,说明无解

代码实现(注意注释的地方)

#include <map>
#include <set>
#include <queue>
#include <cmath>
#include <ctime>
#include <stack>
#include <random>
#include <vector>
#include <cstdio>
#include <sstream>
#include <cstdlib>
#include <iomanip>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <unordered_map>

#define fi first
#define se second
#define LL long long
#define m_p make_pair
#define p_b emplace_back
#define lowbit(x) x&(-x)
#define PII pair<int,int>
#define all(x) x.begin(),x.end()
using namespace std;

inline int read(){
	int x=0,w=1;
	char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void write(int x){
	if(x<0) putchar('-'),x=-x;
	if(x>9) write(x/10);
	putchar(x%10+'0');
}

const int N=5510+10;
const int INF=1e9+7;

int n,m;
int l[N],r[N],u[N],d[N],s[N],row[N],col[N],cnt;
int ans[N],top;

void init()
{
	for (int i=0;i<=m;i++)
	{
		l[i]=i-1,r[i]=i+1;
		u[i]=d[i]=i;
	}
	l[0]=m,r[m]=0;
	cnt=m+1;
}

void add(int& hh,int& tt,int x,int y)
{
	row[cnt]=x,col[cnt]=y,s[y]++;
	u[cnt]=y,d[cnt]=d[y],u[d[y]]=cnt,d[y]=cnt;
	r[hh]=l[tt]=cnt,r[cnt]=tt,l[cnt]=hh;
	tt=cnt++;
}

void remove(int p)
{
	r[l[p]]=r[p],l[r[p]]=l[p];
	for (int i=d[p];i!=p;i=d[i])
		for (int j=r[i];j!=i;j=r[j])
		{
			s[col[j]]--;
			d[u[j]]=d[j],u[d[j]]=u[j]; // 这里的删除是把上下链表删除
		}
}

void resume(int p)
{
	for (int i=u[p];i!=p;i=u[i])
		for (int j=l[i];j!=i;j=l[j])
		{
			d[u[j]]=j,u[d[j]]=j;
			s[col[j]]++;
		}
	r[l[p]]=p,l[r[p]]=p;
}

bool dfs()
{
	if (!r[0]) return 1;
	int p=r[0];
	for (int i=r[0];i;i=r[i])
		if (s[i]<s[p])
			p=i;
	remove(p);
	for (int i=d[p];i!=p;i=d[i])
	{
		ans[++top]=row[i];
		for (int j=r[i];j!=i;j=r[j]) remove(col[j]);
		if (dfs()) return 1;
		for (int j=l[i];j!=i;j=l[j]) resume(col[j]);
		top--;
	}
	resume(p);
	return 0;
}

int main(){
	cin>>n>>m;
	init();
	for (int i=1;i<=n;i++)
	{
		int hh=cnt,tt=cnt;
		for (int j=1;j<=m;j++)
		{
			int x;
			scanf("%d",&x);
			if (x) add(hh,tt,i,j);
		}
	}
	if (dfs())
		for (int i=1;i<=top;i++) cout<<ans[i]<<" ";
	else cout<<"No Solution!";
	return 0;
}
/*
--to taozhiming--
0.不要犯愚蠢的错误!
1.数组大小是否足够?数组大小是否足够?数组大小是否足够?整数溢出?
2.你的算法有反例吗?
3.时间复杂性?内存使用?精度误差?
4.是否删除调试?
5.考试的时候你是不是要摆烂?
6.题读对了吗
*/

重复覆盖问题

怎么实现?

1 用个 IDA* 来剪枝(具体的我就不说了)

2 找到最少 \(1\) 的列

3 枚举当前列的每一个 \(1\)

4 删除掉当前列 注意:这里没有删掉枚举到的点

为什么不删掉枚举到的点?如果删掉了枚举到的点,也就是说 \(i\) 这个点的左右都没了,那下面的枚举这么办?所以说不能删掉当前枚举到的点

5 计入答案

6 把当前行的 \(1\) 所在的列的每个点的左右链表删掉

为什么删左右链表?因为当前列已经有一个 \(1\) 了,所以说这一列的点不需要选了

为什么不删上下链表?也是因为删掉了上下链表就没法恢复了

7 继续递归

8 恢复 \(6\) 操作

9 恢复 \(4\) 操作

代码实现(同样注意注释的地方)

#include <map>
#include <set>
#include <queue>
#include <cmath>
#include <ctime>
#include <stack>
#include <random>
#include <vector>
#include <cstdio>
#include <sstream>
#include <cstdlib>
#include <iomanip>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <unordered_map>

#define fi first
#define se second
#define LL long long
#define m_p make_pair
#define p_b push_back
#define lowbit(x) x&(-x)
#define PII pair<int,int>
#define all(x) x.begin(),x.end()
using namespace std;

inline int read(){
	int x=0,w=1;
	char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void write(int x){
	if(x<0) putchar('-'),x=-x;
	if(x>9) write(x/10);
	putchar(x%10+'0');
}

const int N=10000+10;
const int INF=1e9+7;

int n,m;
int l[N],r[N],u[N],d[N],s[N],row[N],col[N],cnt;
int ans[N],top;
bool vis[N];

void init()
{
	for (int i=0;i<=m;i++)
	{
		l[i]=i-1,r[i]=i+1;
		col[i]=u[i]=d[i]=i;
		s[i]=0;
	}
	l[0]=m,r[m]=0;
	cnt=m+1;
}

void add(int &hh,int &tt,int x,int y)
{
	row[cnt]=x,col[cnt]=y,s[y]++;
	u[cnt]=y,d[cnt]=d[y],u[d[y]]=cnt,d[y]=cnt;
	r[hh]=l[tt]=cnt,l[cnt]=hh,r[cnt]=tt;
	tt=cnt++;
}

int h()
{
	int tot=0;
	memset(vis,0,sizeof vis);
	for (int i=r[0];i;i=r[i])
	{
		if (vis[i]) continue;
		vis[i]=1;
		tot++;
		for (int j=d[i];j!=i;j=d[j])
			for (int k=r[j];k!=j;k=r[k])
				vis[col[k]]=1;
	}
	return tot;
}

void remove(int p)
{
	for (int i=d[p];i!=p;i=d[i])
		l[r[i]]=l[i],r[l[i]]=r[i]; //注意这里是删的是左右链表
}

void resume(int p)
{
	for (int i=u[p];i!=p;i=u[i])
		l[r[i]]=i,r[l[i]]=i;
}

bool dfs(int k,int depth)
{
	if (k+h()>depth) return 0;
	if (!r[0]) return 1;
	int p=r[0];
	for (int i=r[0];i;i=r[i])
		if (s[i]<s[p])
			p=i;
	for (int i=d[p];i!=p;i=d[i])
	{
		remove(i);
		ans[k]=row[i];
		for (int j=r[i];j!=i;j=r[j]) remove(j);
		if (dfs(k+1,depth)) return 1;
		for (int j=l[i];j!=i;j=l[j]) resume(j);
		resume(i);
	}
	return 0;
}

int main(){
	cin>>n>>m;
	init();
	for (int i=1;i<=n;i++)
	{
		int hh=cnt,tt=cnt;
		for (int j=1;j<=m;j++)
		{
			int x;
			scanf("%d",&x);
			if (x) add(hh,tt,i,j);
		}
	}
	int depth=0;
	while(!dfs(0,depth)) depth++;
	printf("%d\n",depth);
	for (int i=0;i<depth;i++) cout<<ans[i]<<" ";
	return 0;
}
/*
--to taozhiming--
0.不要犯愚蠢的错误!
1.数组大小是否足够?数组大小是否足够?数组大小是否足够?整数溢出?
2.你的算法有反例吗?
3.时间复杂性?内存使用?精度误差?
4.是否删除调试?
5.考试的时候你是不是要摆烂?
6.题读对了吗
*/

现在我们回过头来,想想变化最大的是什么?

就是我们删的是上下链表还是左右链表对吧

删除方式不一样的原因是目的不同:

精确覆盖问题删除时,是为了删掉某一行,而删掉某一行的方法就是让上面的点直接遍历到下面的点,所以精确覆盖问题的删除是删上下链别

重复覆盖问题删掉时,是因为这一列已经有一个 \(1\) 了,所以这一列别的有没有 \(1\) 已经无所谓了,所以直接把这一列删掉,删除一列的方法同上可得删除左右链就OK

posted @ 2023-06-24 16:54  taozhiming  阅读(50)  评论(0编辑  收藏  举报