• 【洛谷P3160】[CQOI2012]局部极小值


    前言

    【题目传送门】
    好难的题......

    心路历程:

    • 干想没思路。
    • 疯狂翻题解。
    • 看代码太长,想放弃。
    • 又整理了下思路,发现核心其实没有那么复杂。
    • 依据一片题解的代码开始写。
    • 写完之后调到 (90pts)
    • 终于调过。

    实现细节巨多,根本注意不到。
    用时:(3h)

    题解

    DP 的基础思路

    说实话丢人的我连朴素 DP 都没设计出来,或者说不知道怎么转移
    看到数据范围,大概能想到状压 DP。
    问题来了,压什么?
    仔细想想,和题目有关而且能压的,也就是最小点了(最多只有 (8) 个)。
    所以一维表示最小点的选择状态。
    那么为了转移,自然地再加一维表示选了几个数字,正好符合空间,不错。
    接下来确定转移顺序
    假设从小往大填数字,那么填某一个最小点之前,它相邻的点都一定不能被填。
    据此,可以知道每种状态下有哪些点能选(其实需要的是知道有多少个点能选)。
    转移就可以分成两种情况:放在最小点/不放在最小点。

    进一步的思考+容斥

    但是这求出的答案是什么?
    实际上,我们保证了每一个X的位置一定是局部最小值,但是没有保证每一个.的位置一定不是局部最小值。
    所以要减去放错一个高点的情况。
    然而这样又会减多,例如同时放错两个高点的情况又被多减了一次。
    这就是一个容斥问题了。

    关于容斥,其实我也没有系统学过,我的理解大概是发现减去一部分,会多减去重叠的部分,根据数学归纳每一层都是这样“加一层,减一层”的形式,这样推导到最底层即可。(其实有个公式的)
    (PS:简单的容斥还可以用位运算实现,不过和本题无关啦)

    可以用 DFS 枚举每一个能够更改成X.,重新跑 DP 计算答案,根据容斥进行增减。(这个 DFS 也挺恶心人的)

    一点细节

    如果一开始就不符合条件,可以直接特判掉。

    代码(保姆级注释)

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    const int INF = 0x3f3f3f3f,N = 1<<8,mod = 12345678;
    char s[10][10];
    int n,m,px[31],py[31];
    ll dp[N][31],ans;
    bool vis[10][10];
    int dx[9] = {0,1,0,-1,0,1,1,-1,-1};
    int dy[9] = {0,0,1,0,-1,1,-1,1,-1};
    inline ll read()
    {
    	ll ret=0;char ch=' ',c=getchar();
    	while(!(c>='0'&&c<='9')) ch=c,c=getchar();
    	while(c>='0'&&c<='9') ret=(ret<<1)+(ret<<3)+c-'0',c=getchar();
    	return ch=='-'?-ret:ret;
    }
    void print_map()
    {
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=1;j<=m;j++) printf("%c",s[i][j]);
    		printf("
    ");
    	}
    }
    int calc()//按照当前状态算出方案数 
    {
    	int cnt=0; 
    	for(int i=1;i<=n;i++)	
    		for(int j=1;j<=m;j++)	
    			if(s[i][j]=='X') px[++cnt]=i,py[cnt]=j;
    	//print_map();
    	memset(dp,0,sizeof(dp));
    	dp[0][0]=1;
    	for(int mask=0;mask<(1<<cnt);mask++)//枚举状态 
    	{
    		memset(vis,0,sizeof(vis));
    		for(int i=1;i<=cnt;i++)//标记当前状态下哪些点不能选
    			if(!(mask&(1<<(i-1))))//每个最小点周围的点都不能选 
    				for(int j=0;j<=8;j++)
    					vis[px[i]+dx[j]][py[i]+dy[j]]=1;
    		int tot=0;
    		for(int i=1;i<=n;i++)	 
    			for(int j=1;j<=m;j++) tot+=!vis[i][j];//计算有多少个能选的点 
    		//printf("tot=%d
    ",tot);
    		for(int i=0;i<=tot;i++)	//刷表法,i从0开始 
    			if(dp[mask][i])
    			{
    				(dp[mask][i+1]+=dp[mask][i]*(tot-i))%=mod;//当前数字不放在最小点 
    				for(int j=0;j<cnt;j++)	
    					if(!(mask&(1<<j))) 
    						(dp[mask|(1<<j)][i+1]+=dp[mask][i])%=mod;//当前数字放在最小点 
    			}
    	}
    	//printf("dp:%d
    ",dp[(1<<cnt)-1][n*m]); 
    	return dp[(1<<cnt)-1][n*m];
    }
    void dfs(int x,int y,int op)//op是容斥的正负号 
    {//dfs从左上向右下一个个填新的状态 
    	if(x==n+1) {ans=(ans+op*calc()%mod+mod)%mod;return;}
    	if(y==m+1) {dfs(x+1,1,op);return;}
    	dfs(x,y+1,op);//不改当前这个点的类型 
    	bool flag=1;
    	for(int i=0;i<=8;i++)
    	{//判断是否能改当前这个点的类型 
    		int tx=x+dx[i],ty=y+dy[i];
    		if(tx>=1&&tx<=n&&ty>=1&&ty<=m&&s[tx][ty]=='X') flag=0;
    	}	
    	if(flag) 
    	{
    		s[x][y]='X';
    		dfs(x,y+1,-op);//改当前这个点的类型 
    		s[x][y]='.';
    	}
    		
    }
    int main()
    {
    	n=read(),m=read();
    	for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
    	for(int i=1;i<=n;i++)	
    		for(int j=1;j<=m;j++)	
    			if(s[i][j]=='X')
    				for(int k=1;k<=8;k++)
    				{
    					int x=i+dx[k],y=j+dy[k];
    					if(x>=1&&x<=n&&y>=1&&y<=m&&s[x][y]=='X')
    						return puts("0"),0;//特判初始状态不合法 
    				}
    	dfs(1,1,1);
    	printf("%lld
    ",(ans+mod)%mod);
    	return 0;
    }
    
    
  • 相关阅读:
    setState 是异步吗?
    React优化点滴
    JS原型,作用域,this,闭包
    Webpack 模块化打包优化
    JS异步编程
    Web网络安全
    Http2.0和Http3.0
    Http协议基础
    Harris算子以及未来的规划...
    剑指offer 二维数组查找
  • 原文地址:https://www.cnblogs.com/conprour/p/15501592.html
Copyright © 2020-2023  润新知