• [CSP-S模拟测试]:连连看(图论+容斥)


    题目传送门(内部题74)


    输入格式

      输入文件$link.in$
      第一行三个整数$n,m,k$,之间用空格隔开,$n,m$表示地图行数和列数,$k$表示每个方块周围相邻的位置(至多有$4$个,至少有$2$个,在地图的角上就是$2$个,地图的边上就是$3$个,地图内部就是$4$个)中,最多有$k$个位置是空地。
      接下来$n$行,每行$m$个自然数,之间用空格隔开,描述地图。


    输出格式

      输出文件$link.out$
      一行一个整数表示这一步有多少种选法。


    样例

    样例输入1:

    1 3 1
    1 1 1

    样例输出1:

    2

    样例输入2:

    2 3 1
    0 0 0
    1 1 1

    样例输出2:

    3


    数据范围与提示

    样例$1$解释:

      两种选法,$(x,y)$表示第$x$行第$y$列的方块
      第一种:$(1,1)$和$(1,2)$,第二种:$(1,2)$和$(1,3)$
      注意第$1$行第$1$列的方块和第$3$列的方块并不能连通。

    样例$2$解释:

      三种选法,$(x,y)$表示第$x$行第$y$列的方块
      第一种:$(1,1)$和$(1,2)$,第二种:$(1,2)$和$(1,3)$,第三种:$(1,1)$和$(1,3)$

    数据范围:

      对所有测试点,$nleqslant 10^3,mleqslant 10^3,kin{0,1,2,3,4}$,地图中出现的数字都是$[0,10^6]$的整数。
      第$1$个测试点:$k=0$
      第$2,3,4$测试点:$n>1,m>1$,且只有满足横纵坐标均为奇数的位置才会有方块。
      也就是说,如果某个位置的横坐标或纵坐标是偶数,这个位置就是空地(数字$0$)。$k=4$。
      第$5,6,7$个测试点:$n imes mleqslant 1,000,k=4$。
      第$8,9,10$个测试点:$k=1$
      第$11,12,13,14$个测试点:$k=2$
      第$15,16$个测试点:$k=3$
      第$17,18,19,20$个测试点:$k=4$
      第$8,11,12,15,17$个测试点还满足:$n=500,m=500$
      第$18$个测试点还满足:$n=700,m=700$
      如果担心输入时间过长,可以使用第二题给出的读入函数。


    题解

    相当有意思的一道题呢~

    不妨设每一块全白为一个“空地极大联通块”。

    再不妨设每两个不是空地的块之间也有一个“空地极大联通块”。

    那么,挨着同一个“空地极大联通块”的相同颜色的就可以相互连线了。

    但是显然不能直接统计每一个“空地极大联通块”,因为有可能出现如下图中的情况:

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    1

    1

    1

    0

    1

    1

    1

    0

    0

    1

    0

    0

    1

    0

    0

    1

    0

    0

    1

    0

    1

    0

    1

    0

    1

    0

    0

    1

    0

    0

    1

    0

    0

    1

    0

    0

    1

    1

    1

    0

    1

    1

    1

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    会发现,可么可能有一对点同时属于多个“空地极大联通块”,但是不用担心,容斥一下就好了。

    因为一个点最多属于$4$个“空地极大联通块”,所以状态数也不多。

    于是可以设$f1[],f2[][],f3[][][],f4[][][][]$分别表示仅属于集合……的,属于集合……和……的,……

    但是这样时间可空间都不允许。

    考虑其实非$0$项并不多,于是可以仅存储这些非$0$项,然后排个序将一样的颜色排到一起就可以容斥啦。

    时间复杂度:$Theta(4 imes 15 imes n imes m)$。

    期望得分:$100$分。

    实际得分:$100$分。


    代码时刻

    #include<bits/stdc++.h>
    using namespace std;
    struct rec{int col,a[5];}e[15000001];
    int n,m,k;
    int Map[1001][1001],vis[1001][1001];
    int gs[1001][1001][5],sum[1001][1001],gss,top;
    long long ans,now=1;
    void dfs(int x,int y)
    {
    	if(x>1&&!vis[x-1][y])
    		if(!Map[x-1][y]){vis[x-1][y]=gss;dfs(x-1,y);}
    		else if(gs[x-1][y][sum[x-1][y]]!=gss)gs[x-1][y][++sum[x-1][y]]=gss;
    	if(x<n&&!vis[x+1][y])
    		if(!Map[x+1][y]){vis[x+1][y]=gss;dfs(x+1,y);}
    		else if(gs[x+1][y][sum[x+1][y]]!=gss)gs[x+1][y][++sum[x+1][y]]=gss;
    	if(y>1&&!vis[x][y-1])
    		if(!Map[x][y-1]){vis[x][y-1]=gss;dfs(x,y-1);}
    		else if(gs[x][y-1][sum[x][y-1]]!=gss)gs[x][y-1][++sum[x][y-1]]=gss;
    	if(y<m&&!vis[x][y+1])
    		if(!Map[x][y+1]){vis[x][y+1]=gss;dfs(x,y+1);}
    		else if(gs[x][y+1][sum[x][y+1]]!=gss)gs[x][y+1][++sum[x][y+1]]=gss;
    }
    bool cmp(rec a,rec b)
    {
    	if(a.col!=b.col)return a.col<b.col;
    	for(int i=1;i<5;i++)
    		if(a.a[i]!=b.a[i])return a.a[i]<b.a[i];
    	return 0;
    }
    bool diff(rec a,rec b)
    {
    	if(a.col!=b.col)return 1;
    	for(int i=1;i<5;i++)
    		if(a.a[i]!=b.a[i])return 1;
    	return 0;
    }
    int coun(rec a)
    {
    	for(int i=1;i<5;i++)
    		if(a.a[i]==0x3f3f3f3f)return i-1;
    	return 4;
    }
    int main()
    {
    	scanf("%d%d%d",&n,&m,&k);
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++)
    			scanf("%d",&Map[i][j]);
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++)
    		{
    			if(!Map[i][j])continue;
    			if(Map[i][j]==Map[i-1][j])
    			{
    				gs[i][j][++sum[i][j]]=++gss;
    				gs[i-1][j][++sum[i-1][j]]=gss;
    			}
    			if(Map[i][j]==Map[i][j-1])
    			{
    				gs[i][j][++sum[i][j]]=++gss;
    				gs[i][j-1][++sum[i][j-1]]=gss;
    			}
    		}
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++)
    			if(!Map[i][j]&&!vis[i][j])
    			{gss++;dfs(i,j);}
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++)
    			if(Map[i][j])
    			{
    				if(sum[i][j]>=1)
    					for(int k=1;k<=sum[i][j];k++)
    					{
    						e[++top].col=Map[i][j];
    						e[top].a[1]=gs[i][j][k];
    						e[top].a[2]=0x3f3f3f3f;
    						e[top].a[3]=0x3f3f3f3f;
    						e[top].a[4]=0x3f3f3f3f;
    					}
    				if(sum[i][j]>=2)
    					for(int k=1;k<=sum[i][j];k++)
    						for(int l=k+1;l<=sum[i][j];l++)
    						{
    							e[++top].col=Map[i][j];
    							e[top].a[1]=gs[i][j][k];
    							e[top].a[2]=gs[i][j][l];
    							e[top].a[3]=0x3f3f3f3f;
    							e[top].a[4]=0x3f3f3f3f;
    						}
    				if(sum[i][j]>=3)
    					for(int k=1;k<=sum[i][j];k++)
    						for(int l=k+1;l<=sum[i][j];l++)
    							for(int o=l+1;o<=sum[i][j];o++)
    							{
    								e[++top].col=Map[i][j];
    								e[top].a[1]=gs[i][j][k];
    								e[top].a[2]=gs[i][j][l];
    								e[top].a[3]=gs[i][j][o];
    								e[top].a[4]=0x3f3f3f3f;
    							}
    				if(sum[i][j]>=4)
    				{
    					e[++top].col=Map[i][j];
    					e[top].a[1]=gs[i][j][1];
    					e[top].a[2]=gs[i][j][2];
    					e[top].a[3]=gs[i][j][3];
    					e[top].a[4]=gs[i][j][4];
    				}
    			}
    	sort(e+1,e+top+1,cmp);
    	for(int i=1;i<=top;i++)
    		if(diff(e[i+1],e[i]))
    		{
    			if(coun(e[i])&1)ans+=now*(now-1)/2;
    			else ans-=now*(now-1)/2;
    			now=1;
    		}
    		else now++;
    	printf("%lld
    ",ans);
    	return 0;
    }
    

    rp++

  • 相关阅读:
    sql 主键 标识 默认值
    SQL Server跨服务器查询
    C# 取整数
    RegisterClientScriptBlock、RegisterStartupScript
    UpdatePanel
    C#创建(从数据库中导出)Excel文件(含Interop.Excel.Dll)
    基类、接口的应用——表单控件:一次添加、修改一条记录,一次修改多条记录。(上)
    利用JS获取IE客户端IP及MAC的实现
    Net用DataTable导出Excel通用函数(修正了Excel进程删除不掉问题)
    感人至深的文章
  • 原文地址:https://www.cnblogs.com/wzc521/p/11716008.html
Copyright © 2020-2023  润新知