CF 277C Game(博弈)

CF 277C  Game

一道恶心的博弈题。。。

博弈部分是正常的NIM游戏规则,但是对于图形的处理好麻烦的说

n*m的矩形网格,已经切了K条线 
再两人轮流操作 合法操作是在网格上画横线或竖线
长度自随意,但是必须产生新的切痕
网格的边界不能切
最后不能操作者败
不难发现行和列是相互独立的 
每一行和每一列单独看成成一堆石子,
完整部分的长度就是石子个数,
每切一刀就相当于取走若干石子

#include
#include
#include
#include
#include
#include
#include
#define mem(a,x) memset(a,x,sizeof(a))
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
/*
	n*m的矩形网格,已经切了K条线 
	再两人轮流操作 合法操作是在网格上画横线或竖线
	长度自随意,但是必须产生新的切痕
	网格的边界不能切
	最后不能操作者败
	不难发现行和列是相互独立的 
	每一行和每一列单独看成成一堆石子,
	完整部分的长度就是石子个数,
	每切一刀就相当于取走若干石子
	
*/ 
struct Line 
{
	int l,r;
	Line(int l = 0,int r = 0):l(l),r(r){}
};
bool cmp(Line a,Line b)
{
	if (a.l == b.l) return a.r < b.r;
	return a.l < b.l;
}
const int N = 100004;
vectorfs[N],sc[N];//fs保存原始输入的线,sc保存处理过没有重叠部分的线
int take[N];//take[i]表示编号为i的行或者列已经被拿走的石头数
int n,m,k;
int id;//划过线的行或列的编号
maprow,cow;//row[i]表示纵坐标为i的行的坐标 
void init()
{
	mem(fs,0);mem(sc,0);mem(take,0);
	row.clear();cow.clear();
	id = 0;
	//输入
	for (int i = 0,x1,x2,y1,y2;i < k;++i)
	{
		scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
		if (x1 == x2)//竖线
		{
			if (y1 > y2) swap(y1,y2);
			if (cow.find(x1) == cow.end())//这一列没有出现过
			{
				cow[x1] = id;//给个编号 
				fs[id].push_back(Line(y1,y2));//给这个编号的列加入一条线段
				++id ;//更新编号 
			} 
			else //这一列已经被划过 也就是已经有编号了
			{
				fs[cow[x1]].push_back(Line(y1,y2));//加入线段 
			} 
		} 
		else //横线 -- 同理
		{
			if (x1 > x2) swap(x1,x2);
			if (row.find(y1) == row.end())
			{
				row[y1] = id;
				fs[id].push_back(Line(x1,x2));
				++id;
			}
			else 
			{
				fs[row[y1]].push_back(Line(x1,x2));
			}
		} 
	} 
	//处理重叠的线 保存于sc中
	for (int i = 0;i < id;++i)
	{
		sort(fs[i].begin(),fs[i].end(),cmp);//这一行或列的所有线排好 
		int l = fs[i][0].l,r = fs[i][0].r;//这一行或列的第一条线 
		for (int j = 1;j < fs[i].size();++j)
		{
			if (fs[i][j].l > r)//这是一条和前面一条没有覆盖的线
			{
				sc[i].push_back(Line(l,r));//前面一条存进去
				take[i] +=  r-l;//已经拿走的石头数 
				l = fs[i][j].l;//更新
				r = fs[i][j].r; 
			} 
			else //覆盖
			{
				r = max(r,fs[i][j].r);//延长前面那条线到后面那条线达到的长度 
			} 
		}
		take[i]+=r-l;
		sc[i].push_back(Line(l,r));//最后一根线不要忘 
	} 
}
void solve()//博弈的过程
{
	int s = 0;
	int r = m-row.size()-1,c = n-cow.size()-1;
	//r表示没有被切过的完整的行
	if (r&1) s^=n;//奇数个异或和为n,偶数个 异或和为0
	if (c&1) s^=m;
	//博弈 -- 当前局面的判断
	for (map::iterator it = row.begin();it!=row.end();++it)//处理行 
	{
		int rest = n-take[it->second];//take[it->second]是该编号行拿走的石头数
		//rest是该行剩下的石头数
		s^=rest; 
	} 
	for (map::iterator it = cow.begin();it!=cow.end();++it)//同理处理列 
	{
		int rest = m - take[it->second];
		s^=rest;
	}
	if (s==0)//败态 
	{
		puts("SECOND");
		return;
	}
	puts("FIRST");
	//胜态需要输出第一步的走法
	//首先考虑完整的行或列
	for (int i = 1;i < n;++i)
	{
		if (cow.find(i) == cow.end())
		{
	 		if (m>=(m^s))
	 		{
	 			int t = m - (m^s);
	 			printf("%d %d %d %d\n",i,0,i,t);
	 			return;
			}
			else break;//完整的都是一样 只需尝试一根即可 
		}
	}
	for (int i = 1;i < m;++i)
	{
	 	if (row.find(i) == row.end())
	 	{
	 		if (n>=(n^s))
	 		{
	 			int t = n - (n^s);
	 			printf("%d %d %d %d\n",0,i,t,i);
	 			return;
			}
			else break;
		}
	} 
	for (map::iterator it = row.begin();it != row.end();++it)
	{
		int id = it->second;
		int rest = n - take[id];
		if (rest >= (rest^s))
		{
			int t = rest - (rest^s);
			int sum = 0,p = 0;
			sc[id].push_back(Line(n,n));
			for (int i = 0;i < sc[id].size();++i)
			{
				if (sum + sc[id][i].l- p >= t)
				{
					printf("%d %d %d %d\n",0,it->first,p+t-sum,it->first);
					return;
				}
				sum += sc[id][i].l- p;
				p = sc[id][i].r;
			}
		}
	}
	for (map::iterator it = cow.begin();it != cow.end();++it)
	{
		int id = it->second,rest = m - take[id];
		if (rest >= (rest^s))
		{
			int t = rest - (rest^s);
			int sum = 0,p = 0;
			sc[id].push_back(Line(m,m));
			for (int i = 0;i < sc[id].size();++i)
			{
				if (sum+sc[id][i].l - p >= t)
				{
					printf("%d %d %d %d\n",it->first,0,it->first,p+t-sum);
					return;
				}
				sum += sc[id][i].l-p;
				p = sc[id][i].r;
			}
		}
	}
} 
int main()
{
	while (cin>>n>>m>>k)
	{
		init();//建图 
		solve();//博弈 
	}
	return 0;
}

参考题解: http://blog.csdn.net/acm_cxlove/article/details/8627484

你可能感兴趣的:(ACM题解与算法,ACM(算法))