• hdu3811 Permutation


    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3811

    题意:给定1~N个数,求出至少满足一个条件的排列总数

    m个条件如下:Ai 个数为Bi (0<i <m)

    分析:“本意是一个复杂的容斥原理,但是开场不久被秒杀,才发现又一个比较简单的状态DP方法” 这是武大预赛解题报告上的原句。

    确实,用容斥原理来解决确实十分蛋疼,最直接就是满足各个条件之后去重,好SB的样子==!。可是用状态DP却很好解决了。

    我们通过求出一个条件都不满足的排列总数,从而间接的求出满足至少一个条件的排列总数。

    对于当前的位置,利用前面已经求得的种数求得。

    结合代码比较好解释,看代码吧 。

    #include<iostream>
    #include<algorithm>
    using namespace std;
    __int64 fact[18],dp[1<<18];
    int g[18][18];
    void init()
    {
    	fact[0]=1;
    	for(int i=1;i<=17;i++)
    		fact[i]=i*fact[i-1];
    }
    int main()
    {
    	int T,cas=0,n,m,a,b;
    	init();
    	scanf("%d",&T);
    	while(T--)
    	{
    		scanf("%d %d",&n,&m);
    		memset(g,0,sizeof(g));
    		for(int i=0;i<m;i++)
    		{
    			scanf("%d %d",&a,&b);
    			a--;b--;
    			g[a][b]=1;//标记一下,a位置不能选择b
    		}
    		memset(dp,0,sizeof(dp));
    		dp[0]=1;//初始化为1,
    		for(int i=0;i<n;i++)//枚举每一个长度
    			for(int j=(1<<n)-1;j>=0;j--)//枚举每一个状态,利用位运算,j的每一个位表示该数字是否已被用
    			{//这里注意j的枚举顺序,不能颠倒,想想为什么?
    				//其实从小到大枚举的话,会改掉之前所保存的值
    				if(dp[j]==0) continue;
    				for(int k=0;k<n;k++)//枚举当前位置的所有可选择的数字
    				{
    					if((j&(1<<k))!=0)//j 状态中已有了k这个数字
    						continue;
    					if(g[i][k]==1)//i位置不能选择k
    						continue;
    					dp[j|(1<<k)]+=dp[j];//j状态变成j|(1<<k)]状态产生的排列数,j|(1<<k)]状态是j状态第i+1个位置选择了k之后的状态
                                                                      //状态转移,扩展序列的长度
    				}
    			}
    		//fact[n]表示n!,即n个数的全排列,减掉不满足条件的排列数,即为所求。
    		//dp[(1<<n)-1]表示不满足条件的排列总数,其中(1<<n)-1  对应的二进制每一个的前N个位均为,即该状态下n个数字都已经选择
    		printf("Case %d: %I64d\n",++cas,fact[n]-dp[(1<<n)-1]);
    	}
    	return 0;
    }
    
  • 相关阅读:
    VUE动画Javascript钩子不生效问题记录
    vue-resource
    shell脚本 回顾 小练习
    mysql 回顾小练习
    jvm调优(二)
    jvm调优(一)
    性能调优笔记(二)
    性能调优笔记(一)
    mac 下 配置appium +ios真机环境
    pycharm中 unittests in xxxx 运行模式
  • 原文地址:https://www.cnblogs.com/nanke/p/2225912.html
Copyright © 2020-2023  润新知