2012国家集训队Round 1 day1 攻占黄金乡

问题描述
  《海猫鸣泣之时》EP8终于在今年暑假的末尾出了汉化,作为整个作品中高人气人物古户绘梨花,自然也是在剧中大活跃。在攻占黄金乡的战役中担任了指挥官一职,而整个战役过程也都被记录在了图书之都中,方便后来人的复盘。
  当时的战况如下,黄金乡可以看做是一个长方体空间,我们用(0,0,0)~(n-1,m-1,k-1)表示里面的每一个单位区域,绘梨花指挥了t艘不同等级的战舰依靠魔法突然出现在了黄金乡的t个不同的区域,之后从战舰上便源源不断的涌出山羊们。每一个单位时刻,山羊们会从自己所在的区域向四周6个方向扩展一个区域(如果那个相邻的区域已经被占领了,就不扩展),如果两队山羊在同一时刻想占领同一区域,那么等级高的山羊优先占领。
  没过多久,黄金乡就变成了一片山羊海,但是作为指挥官的绘梨花却因茫茫多的山羊而找不到战舰所在的位置了,于是她将问题交给了身边的你——山羊君,作为一个急于立下战功然后回故乡找山羊子的青年将领,你自然不会放弃这个机会,于是很快就找到了战舰所在的位置,你的这份功绩自然也会被记录到图书之都的文书之中。
输入格式
  第一行一个数test,表示数据组数,以下test部分。
  每部分第一行3个数n,m,k,以下n部分,每部分为m行k列的字符矩阵。
  第i部分表示区域(i,0,0)~(i,m-1,k-1)中的战况。
  不同等级的山羊我们用不同的小写英文字母表示,字典序越小的字母表示山羊等级越高。
  相邻部分之间用空行隔开。
  注意下面的样例输入输出中的<空行>表示该行没有任何内容。
输出格式
  输出test部分,每部分用空行隔开。
  每部分有t行,t为该组数据中战舰的数目,每行格式为
  ch x y z
  表示编号为ch的战舰的位置为(x,y,z)。
  战舰输出顺序无关,如果有多解,输出任意解即可。
样例输入
2
1 2 2
dd
gg
<空行>
3 3 3
aaa
aaa
baa
<空行>
aaa
aaa
baa
<空行>
aaa
aaa
bcc
样例输出
d 0 0 0
g 0 1 0
<空行>
a 1 1 1
b 1 2 0
c 2 2 1
数据规模和约定
  测试点1~3: n=1,n*m*k<=10
  测试点4~6: n=1,n*m*k<=100
  测试点7~10: n=1,n*m*k<=500
  测试点11~20: n*m*k<=1500
  所有所有数据 test<=10,t<=26

  时间限制:5秒


啊,,六个方向的话,实质就是每次只能选择对x,y,z三个坐标中的一个进行+1或-1操作= =

显然这是不存在什么正经的算法能求解的。。在省冬听了yzc学长的讲解,码了一发

如果是搜索的话,搜什么?怎么剪枝?

不妨考虑枚举空间中任意两个点,这个点对能否同时放置飞船?

考虑在空间中的某个点上放置飞船,随着时间的流逝,不断涌出山羊,在不考虑别的飞船的限制的情况下,这样最终会形成一个在空间中斜着摆放的正方体,如果有限制,就剩下一个连通块

枚举一个点对,同时放上飞船,考虑它们各自所在的连通块,如果边界没有相接,说明它们的摆放互不影响

如果边界有某几段是连着的,那么一定满足,边界上相邻的两个点中(分别属于两艘飞船),从飞船A到自己那块边界的点a的距离dis(A,a)和dis(B,b)相比,要么相等,要么相差1(考虑优先级之类的)

只要不满足上面的条件,说明这两个点不能同时摆放飞船!!!

对于原图涌出山羊的方式,显然,从点A出发到a的山羊,要的时间是两点的曼哈顿距离


这样就得到了一个强力剪枝,对于每个点,预处理如果这个点放飞船,空间中哪些点是一定不能放飞船的,如果在某个方案里选择了这个点,就把其它点都从点集删除,持续搜索直至找到可行解

对于搜索树,一个显然的优化是优先搜索包含限制条件较多的点

在搜索过程中,某一状态下选择了某个点,使得某个还未选择飞船着陆点的颜色已经无点可选,那再接下去也是没办法找到可行解的,这是另一条强力剪枝

以上三条剪枝全部添加,注意常数,代码实测最慢的点仅仅接近700ms(原题时限5s~)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
#include<bitset>
using namespace std;

const int maxn = 1505;
const int maxm = maxn*maxn;
const int dx[6] = {-1,1,0,0,0,0};
const int dy[6] = {0,0,-1,1,0,0};
const int dz[6] = {0,0,0,0,-1,1};

struct Point{
	int x,y,z; Point(){}
	Point(int x,int y,int z): x(x),y(y),z(z){}
};

int n,m,k,T,Need,tot,tp,Cnt,stk[26],cnt[maxn],p[maxn]
	,pos[maxn],last[maxm],from[maxm],Num[maxm],siz[26],res[26];
char c[maxn]; bool bo[26];

vector <int> v[maxn];
vector <Point> G[26][26];

int Get_Num(const Point &p) {return p.z * n * m + p.y * n + p.x;}
bool cmp(const int &x,const int &y) {return v[x].size() > v[y].size();}
Point Get_Point(const int &t) {return Point(t % n,t / n % m,t / n / m);}
void Add(int x,int y) {Num[++Cnt] = y; from[Cnt] = last[x]; last[x] = Cnt;}
int Dis(const Point &A,const Point &B) {return abs(A.x - B.x) + abs(A.y - B.y) + abs(A.z - B.z);}

int getint()
{
	char ch = getchar(); int ret = 0;
	while (ch < '0' || '9' < ch) ch = getchar();
	while ('0' <= ch && ch <= '9')
		ret = ret*10 + ch - '0',ch = getchar();
	return ret;
}

char Getc()
{
	char ch = getchar();
	while (ch < 'a' || 'z' < ch) ch = getchar();
	return ch - 'a';
}

void Build_Graph()
{
	for (int x = 0; x < n; x++)
		for (int y = 0; y < m; y++)
			for (int z = 0; z < k; z++)
			{
				char now = Getc(); ++siz[now];
				c[Get_Num(Point(x,y,z))] = now;
				if (!bo[now]) ++Need,bo[now] = 1;
			}
	for (int x = 0; x < n; x++)
		for (int y = 0; y < m; y++)
			for (int z = 0; z < k; z++)
				for (int t = 0; t < 6; t++)
				{
					int xx = x + dx[t],yy = y + dy[t],zz = z + dz[t];
					if (xx < 0 || xx >= n || yy < 0 || yy >= m || zz < 0 || zz >= k) continue;
					Point p1 = Point(x,y,z),p2 = Point(xx,yy,zz);
					if (c[Get_Num(p1)] == c[Get_Num(p2)]) continue;
					G[c[Get_Num(p1)]][c[Get_Num(p2)]].push_back(p1);
				}
}

void Dfs_Pre()
{
	for (int i = 0; i < tot; i++)
		for (int j = i + 1; j < tot; j++)
		{
			if (c[i] == c[j]) {v[i].push_back(j); v[j].push_back(i); continue;}
			if (!G[c[i]][c[j]].size()) continue;
			Point p1 = Get_Point(i),p2 = Get_Point(j);
			vector <Point> &g = G[c[i]][c[j]];
			for (int t = 0; t < g.size(); t++)
			{
				bool flag = 0; Point pa = g[t];
				for (int o = 0; o < 6; o++)
				{
					int xx = pa.x + dx[o],yy = pa.y + dy[o],zz = pa.z + dz[o];
					if (xx < 0 || xx >= n || yy < 0 || yy >= m || zz < 0 || zz >= k) continue;
					Point pb = Point(xx,yy,zz);
					if (c[Get_Num(pb)] != c[j]) continue;
					int dis1 = Dis(p1,pa),dis2 = Dis(p2,pb);
					if (dis1 == dis2) continue;
					if (abs(dis1 - dis2) > 1) {flag = 1; break;}
					if (c[i] < c[j] && dis1 < dis2) {flag = 1; break;}
					if (c[i] > c[j] && dis1 > dis2) {flag = 1; break;}
				}
				if (flag) {v[i].push_back(j); v[j].push_back(i); break;}
			}
		}
}

bool Dfs(int x,int sum)
{
	if (sum == Need) return 1;
	if (cnt[p[x]]) return Dfs(x + 1,sum);
	bo[c[p[x]]] = 1;
	stk[tp++] = p[x]; --res[c[p[x]]];
	
	bool pass = 1;
	for (int i = last[p[x]]; i; i = from[i]) 
	{
		if (!cnt[Num[i]]) 
		{
			--res[c[Num[i]]];
			if (!bo[c[Num[i]]] && !res[c[Num[i]]]) pass = 0;
		}
		++cnt[Num[i]];
	}
	if (pass && Dfs(x + 1,sum + 1)) return 1;
	for (int i = last[p[x]]; i; i = from[i]) 
	{
		--cnt[Num[i]];
		if (!cnt[Num[i]]) ++res[c[Num[i]]];
	}
	bool flag = 0; --tp; bo[c[p[x]]] = 0;
	if (res[c[p[x]]]) flag = Dfs(x + 1,sum);
	++res[c[p[x]]]; return flag;
}

void Clear()
{
	for (int i = 0; i < tp; i++)
	{
		Point pn = Get_Point(stk[i]);
		printf("%c %d %d %d\n",c[stk[i]] + 'a',pn.x,pn.y,pn.z);
	}
	for (int i = 0; i < tot; i++) cnt[i] = 0,v[i].clear(),last[i] = 0;
	for (int i = 0; i < 26; i++)
		for (int j = 0; j < 26; j++) G[i][j].clear();
	for (int i = 0; i < 26; i++) bo[i] = siz[i] = 0; 
	Need = tp = Cnt = 0; puts("");
}

int main()
{
	#ifdef DMC
		freopen("DMC.txt","r",stdin);
	#endif
	
	T = getint();
	while (T--)
	{
		n = getint(); m = getint(); k = getint();
		tot = n * m * k; Build_Graph(); Dfs_Pre();
		for (int i = 0; i < tot; i++) p[i] = i;
		sort(p,p + tot,cmp);
		for (int i = 0; i < tot; i++) pos[p[i]] = i;
		for (int i = 0; i < tot; i++)
			for (int j = 0; j < v[i].size(); j++)
				if (pos[v[i][j]] > pos[i]) Add(i,v[i][j]);
		for (int i = 0; i < 26; i++) res[i] = siz[i],bo[i] = 0;
		Dfs(0,0); Clear();
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值