• [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,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++

  • 相关阅读:
    CSharpThinkingC# 要点(附加三)
    CSharpThinkingC#3 革新(附加二)
    CSharpThinking委托相关(二)
    C++之this指针与另一种“多态”
    《C++应用程序性能优化::第二章C++语言特性的性能分析》学习和理解
    《C++应用程序性能优化::第一章C++对象模型》学习和理解
    回答总结:C实现“动态绑定”
    编译器对临时变量的优化简单理解
    虚函数表里边保存的不一定是虚函数的地址
    C++对象内存布局测试总结
  • 原文地址:https://www.cnblogs.com/wzc521/p/11824558.html
Copyright © 2020-2023  润新知