• ●BZOJ 2669 [cqoi2012]局部极小值


    题链:

    http://www.lydsy.com/JudgeOnline/problem.php?id=2669

    题解:

    容斥,DP,DFS


    先看看 dp 部分:
    首先呢,X的个数不会超过 8个
    个数很少,所以考虑状压,把需要填 X的那几个位置状压为二进制10表示对应的那个X位置是否已经填数。
    同时填的数互不重复,考虑从小填到大。
    cnt[S] 表示除了不在集合 S 里的 X 位置及其周围的位置,剩下的位置个数
    定义 dp[i][S]表示从小到大填数填完了i这个数,且已经填了的 S 这个集合里的 X 位置的方案数
    转移:依次去填数 1~N*M,每次有两种选择:
    1).把这个数填在 某个 X 位置(枚举一个 k表示第 k个 X 位置填当前数)
    dp[i][S]+=dp[i-1][S^(1<<(k-1))]

    2).把这个数填在非 X 位置,那么填的位置有 cnt[S]-(i-1) 种。
    dp[i][s]+=dp[i-1][s]*(cnt[s]-(i-1)) (好好理解一下这个转移)

    这样 dp 可以保证那些给出的 X 位置一定是局部最小值,
    因为第二种转移的填数位置都不能填在还没有填数的 X 位置的周围。
    所以就完了么?
    当然还没有,尽管我们保证了给出的 X 位置一定是局部最小值,
    但是没有保证非 X位置一定不是非局部最小值。即,求出来的 dp[N*M][all_S(全集)]的意思是至少all_S集合里的 X位置为局部最小值的方案数
    所以容斥如下:
    ANS = 至少多填了0个局部最小值的方案数(dp[N*M][all_S])
              -至少多填了1个局部最小值的方案数
             +至少多填了2个局部最小值的方案数
              -....+ ....
    这些用于容斥的方案数的求法:
    DFS 搜索出哪些非 X 位置还可以改为 X ,
    然后对于每一种新的填法,去跑一遍上述的dp即可求得对应的方案数。

    代码:

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #define _ % mod
    #define filein(x) freopen(#x".in","r",stdin);
    #define fileout(x) freopen(#x".out","w",stdout);
    using namespace std;
    const int mv[9][2]={{0,0},{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1}};
    const int mod=12345678;
    char mp[10][10];
    int N,M,ANS;
    int solve(){
    	static bool vis[10][10];
    	static int dp[30][1<<8],cnt[1<<8],x[10],y[10],tot,tmp;
    	tot=0; memset(dp,0,sizeof(dp));
    	for(int i=1;i<=N;i++)
    		for(int j=1;j<=M;j++) if(mp[i][j]=='X') 
    			tot++,x[tot]=i,y[tot]=j;
    	for(int s=0;s<1<<tot;s++){
    		tmp=0; memset(vis,0,sizeof(vis));
    		for(int i=1;i<=tot;i++) if(!(s&(1<<(i-1))))
    			for(int k=0;k<9;k++)
    				vis[x[i]+mv[k][0]][y[i]+mv[k][1]]=1;
    		for(int i=1;i<=N;i++)
    			for(int j=1;j<=M;j++) 
    				if(!vis[i][j]) tmp++;
    		cnt[s]=tmp;
    	}
    	dp[0][0]=1;
    	for(int i=1;i<=N*M;i++)
    		for(int s=0;s<1<<tot;s++){
    			dp[i][s]=(1ll*dp[i][s]+1ll*dp[i-1][s]*max(cnt[s]-(i-1),0)_)_;
    			for(int k=1;k<=tot;k++) if(s&(1<<(k-1)))
    				dp[i][s]=(1ll*dp[i][s]+dp[i-1][s^(1<<(k-1))])_;
    		}
    	return dp[N*M][(1<<tot)-1];
    }
    void dfs(int x,int y,int t){
    	if(y==M+1){dfs(x+1,1,t);return;}
    	if(x==N+1){
    		int tmp=solve();
    		if(t&1) tmp=(-1ll*tmp+mod)_;
    		ANS=((1ll*ANS+tmp)_+mod)_;
    		return;
    	}
    	dfs(x,y+1,t);
    	bool fg=1;
    	for(int k=0;k<9;k++) 
    		if(mp[x+mv[k][0]][y+mv[k][1]]=='X') fg=0;
    	if(fg){
    		mp[x][y]='X';
    		dfs(x,y+1,t+1);
    		mp[x][y]='.';
    	}
    }
    int main()
    {
    	scanf("%d%d",&N,&M);
    	for(int i=1;i<=N;i++) 
    		scanf("%s",mp[i]+1);
    	dfs(1,1,0);
    	printf("%d",ANS);
    	return 0;
    }

  • 相关阅读:
    SQLSERVER调用DLL程序
    RAISERROR语句
    SQLSERVER表联结(INNER JOIN,LEFT JOIN,RIGHT JOIN,FULL JOIN,CROSS JOIN,CROSS APPLY,OUTER APPLY)
    SQL Server在存储过程中编写事务处理代码的三种方法
    找出表中缺失的连续数据(如:2,4,7,9;需要找出:1,3,5,6,8的数据)
    sqlserver用于统计表索引情况
    你需要了解的HTTP协议
    自己实现一个类似 jQuery 的函数库
    JS 函数 学习笔记
    JS 数组 学习笔记
  • 原文地址:https://www.cnblogs.com/zj75211/p/8035053.html
Copyright © 2020-2023  润新知