题目传送门(内部题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,wleqslant 15$。
$100\%$的数据,保证$1leqslant T,h,wleqslant 100$。
题解
又没有打正解……
首先,答案一定是$0,1,2,3$中的一个。
如果有一种方案能使所有面都朝上,那么答案一定不小于$2$;因为小$M$哪怕要丢掉最后的$1$分也要拿到这$2$分。
如果不能的话答案就只与$h+w$的奇偶性有关了。
那么不妨先来考虑是否可以让正面都朝上。
分为两种情况:
$alpha.$正面朝上,那么如果翻转行就要翻转列;如果不翻转行就一定不能翻转列。
$eta.$反面朝上,那么如果翻转行就不能翻转列;如果不翻转行就一定要翻转列。
用拓展域并查集维护,看最终态有没有矛盾即可。
再来考虑是否先手必胜。
分为三种情况:
$alpha.$如果有一个集合对应偶数次翻转,它的对立集合也对应偶数次翻转,那么不用管它。
$eta.$如果有一个集合对应奇数次翻转,它的对立集合也对应奇数次翻转。
$gamma.$如果有一个集合对应奇数次翻转,他的对立集合对应偶数次翻转。
所以我们可以只考虑后两种状态。
考虑$DP$,设$dp[i][j]$表示$eta$状态有$i$个,$gamma$状态有$j$个是否可行。
初始化$dp[0][0]=0$。
状态转移方程有$3$个:
$alpha.dp[i][j]|=!dp[i][j-1]$:选择两个奇数集合,将其变成偶数集合。
$eta.dp[i][j]|=!dp[i-1][j]$:选择一个奇偶面集合的奇面,将其变成偶数集合。
$gamma.dp[i][j]|=!dp[i-1][j+1]$:选择一个奇偶面集合的偶面,将其变成奇数集合。
这样我们就解决了这道题。
时间复杂度:$Theta(T imes h imes 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
",judge()+2);
else printf("%d
",(h+w)&1);
}
return 0;
}
rp++