HDU 5031:看脸的DFS

我仍然觉得这道题十分的不科学……


题目是说你有一个坐标系中的点阵,其中点的两个坐标范围分别为0~n和0~m,而且都是整数点。你的好基友在这坐标系上画了几条直线,每条直线保证只穿过整数点,而且至少穿过3个整数点。现在你得到了一个矩阵,矩阵描述了每个点都被穿过了多少次,你需要确定你的基友最少画了多少条直线。答案保证不超过14.


这真没什么好办法,只能搜索,但是如果真老老实实地搜,肯定会T到死……


既然要搜索,我们肯定要找到这里所有的直线,不妨先看看怎样不重不漏地找出这里所有的直线吧,我的算法是这样的:先预处理出所有不是0的点,从左向右,从上向下扫描每个点,以它为起点,然后从它开始继续从左向右,从上到下枚举每个点作为终点,如果这两个点确定的向量的两个分量不互质(例如(1,1)和(3,3)向量是(2,2)),那么肯定没戏,他们中间一定隔过了一个点,或者中间的点是0;设这个向量为vec;

然后从起点开始每次减去vec枚举,看到0就说明没戏,看到不为0的点也说明没戏,因为我是从左向右,从上到下扫描的,假如我看到一个在我上面的(或左边的)点,那么应该是这个点来找我才对,这说明这条直线已经被找过了,所以不要;

接下来从终点每次加上vec枚举,看到0说明没戏,而且要注意记录一下是否看到了至少一个1,如果是的话就ok,这就是一条满足要求的直线了。


然后说搜索,我最后AC时采用的搜索姿势是:

(1)找线的工作放在每层搜索之前做,这很重要,因为你会发现找到一条直线并划去这条直线之后,下一层的合法直线数会急剧下降,这对于这道题来说非常重要,如果你一开始就找出了所有直线,维护他们是否合法的代价就太大了,还不如每次都现找;

(2)找出所有直线后按照他们覆盖点的个数排序,当前层选取直线的时候从大到小选取,并且只取最大的5条(如果合法直线比5条多的话)。然后,第一次找到一个解,就认为这个解是最优解并退出。

这个真的是看脸,我至今都觉得这个算法不科学,只能说他对的概率不小就是了,但是拿这个算法AC简直糊我一脸……

不评论了,这个看脸的社会……

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
using namespace std;
 
const int INF=1<<30;
int n,m,t;

struct Point
{
	int x,y;
	Point(){}
	Point(int _x, int _y)
	{
		x=_x;
		y=_y;
	}
	void set(int _x, int _y)
	{
		x=_x;
		y=_y;
	}
	Point operator + (const Point &b) const
	{
		return Point(x+b.x, y+b.y);
	}
	Point operator - (const Point &b) const
	{
		return Point(x-b.x, y-b.y);
	}
	int operator ^(const Point &b) const
	{
		return x*b.y - y*b.x;
	}
};

struct Line
{
	Point s,e;
	int cover;
	Line(){}
	void set(Point _s, Point _e, int _c)
	{
		s=_s;
		e=_e;
		cover=_c;
	}
	
	bool operator < (const Line &b)
	{
		return cover < b.cover;
	}
};


Point point[5000];
int p_point = 0;

int map[70][70];
int min_ans = 14;
bool found = false;
 
void init()
{
	p_point=0;
	memset(map, 0, sizeof(map));
	min_ans = 14;
	found = false;
	return;
}

int gcd(int x, int y)
{
	if(x<y)
		swap(x,y);
	if(y==0)
		return x;
	if(x%y==0)
		return y;
	return gcd(y, x%y);
}

bool valid(Point p)
{
	return p.x >=0 && p.y >= 0 && p.x <=n && p.y <=m;
}

bool CheckLine(int p1, int p2, int &cover)//判断p1 p2组成的直线是否合法,顺便算出他们的覆盖点数
{
	Point now_vec = point[p2] - point[p1];
	if(gcd( abs(now_vec.x), abs(now_vec.y)) != 1)
		return false;
	
	int now_cover = 0;
	Point tmp = point[p1];
	tmp = tmp - now_vec;
	while(valid(tmp))
	{
		if(map[tmp.x][tmp.y] == 0)
			return false;
		else if(tmp.x <= point[p1].x)
			return false;
		
		now_cover ++;
		tmp = tmp - now_vec;
	}
	
	tmp = point[p2];
	tmp = tmp + now_vec;
	bool found_one = false;
	while(valid(tmp))
	{
		found_one = true;
		if(map[tmp.x][tmp.y] == 0)
			return false;
		now_cover++;
		tmp = tmp + now_vec;
	}
	if(!found_one)
		return false;
	
	cover = now_cover;
	return true;
}

void DetermineLines(Line line[], int &p_line)
{
	int i,j;
	for(i=1; i<=p_point -1;i++)
	{
		if(map[point[i].x][point[i].y] ==0)
			continue;
		for(j=i+1; j<=p_point; j++)
		{
			if(map[point[j].x][point[j].y]==0)
				continue;
			int cover;
			if( CheckLine(i,j, cover) )
			{
				line[++p_line].set(point[i], point[j], cover);
			}
		}
	}
	return;
}

void drop(Line l)
{
	Point p1 = l.s, p2 = l.e;
	
	Point now_vec = p2 - p1;
	Point tmp = p1;
	
	while(valid(tmp))
	{
		map[tmp.x][tmp.y] --;
		tmp = tmp - now_vec;
	}
	
	tmp = p2;
	while(valid(tmp))
	{
		map[tmp.x][tmp.y] --;
		tmp = tmp + now_vec;
	}
	return ;
}

void resume(Line l)
{
	Point p1 = l.s, p2 = l.e;
	
	Point now_vec = p2 - p1;
	Point tmp = p1;
	while(valid(tmp))
	{
		map[tmp.x][tmp.y]++;
		tmp = tmp - now_vec;
	}
	
	tmp = p2;
	while(valid(tmp))
	{
		map[tmp.x][tmp.y]++;
		tmp = tmp + now_vec;
	}
	return ;
}

bool cmp(const Line &a, const Line &b)
{
	return a.cover > b.cover;
}

bool check()
{
	int i;
	for(i=1; i<=p_point; i++)
	{
		if(map[point[i].x][point[i].y] != 0)
			return false;
	}
	return true;
}

void DFS(int depth)
{
	Line line[3000];
	int p_line = 0;
	
	DetermineLines(line, p_line);
	sort(line+1, line + p_line +1, cmp);
	
	if(check())
	{
		min_ans = depth-1;
		found = true;
		return ;
	}
	
	if(depth > min_ans)
		return;
	
	int i;
	
	for(i=1; i<=min(p_line,4);i++)
	{
		drop(line[i]);
		
		DFS(depth+1);
		if(found)
			break;
			
		resume(line[i]);
	}
	return ;
}

int main()
{
	scanf("%d", &t);
	int files;
	for(files=1; files<=t; files++)
	{
		init();
		scanf("%d %d", &n, &m);
		int i,j;
		for(i=0; i<=n; i++)
		{
			for(j=0; j<=m; j++)
			{
				scanf("%d", &map[i][j]);
				if(map[i][j]!=0)
				{
					point[++p_point].set(i,j);
				}
			}
		}
		
		DFS(1);
		printf("%d\n", min_ans);
		
	}
	
    //system("pause");
    return 0;
}















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值