[CSP-S模拟测试]:硬币(博弈论+DP+拓展域并查集)

题目传送门(内部题135)


输入格式

  第一行包含一个整数$T$,表示数据组数。
  对于每组数据,第一行两个整数$h,w$,表示棋盘大小。
  接下来$h$行,每行一个长度为$w$的字符串,每个位置由为$o,x,e$中一个。如果这个位置为$e$表示没有硬币,如果是$o$表示正面朝上,否则表示反面朝上。


输出格式

  共$T$行,每行一个整数表示小$M$的分数。


样例

样例输入:

1
2 5
exexe
xeoex

样例输出:

3


数据范围与提示

  $10\%$的数据,保证答案都为$0$或$1$。
  $30\%$的数据,保证答案不为$3$。
  另外$30\%$的数据,保证$h,w\leqslant 15$。
  $100\%$的数据,保证$1\leqslant T,h,w\leqslant 100$。


题解

又没有打正解……

首先,答案一定是$0,1,2,3$中的一个。

如果有一种方案能使所有面都朝上,那么答案一定不小于$2$;因为小$M$哪怕要丢掉最后的$1$分也要拿到这$2$分。

如果不能的话答案就只与$h+w$的奇偶性有关了。

那么不妨先来考虑是否可以让正面都朝上。

分为两种情况:

  $\alpha.$正面朝上,那么如果翻转行就要翻转列;如果不翻转行就一定不能翻转列。

  $\beta.$反面朝上,那么如果翻转行就不能翻转列;如果不翻转行就一定要翻转列。

用拓展域并查集维护,看最终态有没有矛盾即可。

再来考虑是否先手必胜。

分为三种情况:

  $\alpha.$如果有一个集合对应偶数次翻转,它的对立集合也对应偶数次翻转,那么不用管它。

  $\beta.$如果有一个集合对应奇数次翻转,它的对立集合也对应奇数次翻转。

  $\gamma.$如果有一个集合对应奇数次翻转,他的对立集合对应偶数次翻转。

所以我们可以只考虑后两种状态。

考虑$DP$,设$dp[i][j]$表示$\beta$状态有$i$个,$\gamma$状态有$j$个是否可行。

初始化$dp[0][0]=0$。

状态转移方程有$3$个:

  $\alpha.dp[i][j]|=!dp[i][j-1]$:选择两个奇数集合,将其变成偶数集合。

  $\beta.dp[i][j]|=!dp[i-1][j]$:选择一个奇偶面集合的奇面,将其变成偶数集合。

  $\gamma.dp[i][j]|=!dp[i-1][j+1]$:选择一个奇偶面集合的偶面,将其变成奇数集合。

这样我们就解决了这道题。

时间复杂度:$\Theta(T\times h\times w)$。

期望得分:$100$分。

实际得分:$100$分。


代码时刻

#include<bits/stdc++.h>
using namespace std;
int h,w;
char ch[101];
int f[401],cnt[401];
bool vis[401],dp[201][401];
void pre_work()
{
	memset(vis,0,sizeof(vis));
	memset(cnt,0,sizeof(cnt));
	memset(dp,0,sizeof(dp));
}
int find(int x){return x==f[x]?x:f[x]=find(f[x]);}
void merge(int x,int y){f[find(x)]=find(y);}
bool check(){for(int i=1;i<=h+w;i++)if(find(i)==find(i+h+w))return 0;return 1;}
int judge()
{
	int res1=0,res2=0;
	for(int i=1;i<=h+w;i++)cnt[find(i)]++;
	for(int i=1;i<=h+w;i++)
	{
		if(vis[find(i)])continue;
		vis[find(i)]=vis[find(i+h+w)]=1;
		int flag=0;
		if(cnt[find(i)]&1)flag++;
		if(cnt[find(i+h+w)]&1)flag++;
		if(flag==1)res1++;if(flag==2)res2++;
	}
	for(int i=1;i<=res1+res2;i++)dp[0][i]=i&1;
	for(int i=1;i<=res1;i++)
	{
		dp[i][0]|=(!dp[i-1][1])|(!dp[i-1][0]);
		for(int j=1;j<=res1+res2;j++)
			dp[i][j]|=(!dp[i-1][j+1])|(!dp[i-1][j])|(!dp[i][j-1]);
	}
	return dp[res1][res2];
}
int main()
{
	int T;scanf("%d",&T);
	while(T--)
	{
		pre_work();
		scanf("%d%d",&h,&w);
		for(int i=1;i<=((h+w)<<1);i++)f[i]=i;
		for(int i=1;i<=h;i++)
		{
			scanf("%s",ch+1);
			for(int j=1;j<=w;j++)
				switch(ch[j])
				{
					case 'o':
						merge(i,j+h);
						merge(i+h+w,j+2*h+w);
					break;
					case 'x':
						merge(i,j+2*h+w);
						merge(i+h+w,j+h);
					break;
				}
		}
		if(check())printf("%d\n",judge()+2);
		else printf("%d\n",(h+w)&1);
	}
	return 0;
}

rp++

猜你喜欢

转载自www.cnblogs.com/wzc521/p/11824558.html
今日推荐