• 【bzoj4197】[Noi2015]寿司晚宴 分解质因数+状态压缩dp


    题目描述

    为了庆祝 NOI 的成功开幕,主办方为大家准备了一场寿司晚宴。小 G 和小 W 作为参加 NOI 的选手,也被邀请参加了寿司晚宴。

    在晚宴上,主办方为大家提供了 n−1 种不同的寿司,编号 1,2,3,…,n−1,其中第 i 种寿司的美味度为 i+1 (即寿司的美味度为从 2 到 n)。
    现在小 G 和小 W 希望每人选一些寿司种类来品尝,他们规定一种品尝方案为不和谐的当且仅当:小 G 品尝的寿司种类中存在一种美味度为 x 的寿司,小 W 品尝的寿司中存在一种美味度为 y 的寿司,而 x 与 y 不互质。
    现在小 G 和小 W 希望统计一共有多少种和谐的品尝寿司的方案(对给定的正整数 p 取模)。注意一个人可以不吃任何寿司。

    输入

    输入文件的第 1 行包含 2 个正整数 n,p,中间用单个空格隔开,表示共有 n 种寿司,最终和谐的方案数要对 p 取模。

    输出

    输出一行包含 1 个整数,表示所求的方案模 p 的结果。

    样例输入

    3 10000

    样例输出

    9


    题解

    分解质因数+状态压缩dp

    对于这种题肯定想到使用状压dp来解决,即设f[v1][v2]表示甲选质数的状态为v1,乙选质数的状态为v2的方案数。

    但是这样状态数会爆炸。

    考虑:大于$sqrt{500}$的质数在某数中最多只会出现一次,而小于等于$sqrt{500}$的质数只有8个。

    所以我们只需要记录8个小质数的状态,对于大质数单独处理。

    对于每个2~n的数,把它们分解质因数,并记录它们包含小质数的状态和包含的大质数(没有则为0)。

    然后把每个数按照大质数的大小排序,这样每个大质数出现的位置是一段连续的区间。

    对于每个大质数对应的区间,设$g[i][j]$表示乙不选这个大质数,甲、乙状态分别为i、j的方案数;$h[i][j]$表示甲不选这个大质数,甲、乙状态分别为i、j的方案数。

    那么初始$g[i][j]=h[i][j]=f[i][j]$,转移时$g[i|v][j]+=g[i][j] (v&j=0)$,h同理。

    更新完g和h后更新f为$f[i][j]=g[i][j]+h[i][j]-f[i][j]$,因为两者都不选的情况算了2次。

    对于不包含大质数的数,拿出来单独处理即可。

    注意一下循环顺序,不要重复更新,必要时可以记录原状态(不包含大质数的数的处理方法中,f数组可能在使用前已经更新,所以这里再使用g数组记录原来的f)

    时间复杂度$O(500·2^{16})$

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define N 520
    using namespace std;
    const int pos[] = {0 , 0 , 0 , 1 , 0 , 2 , 0 , 3 , 0 , 0 , 0 , 4 , 0 , 5 , 0 , 0 , 0 , 6 , 0 , 7};
    struct data
    {
    	int val , mp;
    	bool operator<(const data a)const {return mp > a.mp;}
    }a[N];
    int f[N][N] , g[N][N] , h[N][N];
    int main()
    {
    	int n , p , i , j , k , l , last , t , ans = 0;
    	scanf("%d%d" , &n , &p);
    	for(i = 2 ; i <= n ; i ++ )
    	{
    		for(j = 2 , t = i ; j * j <= t ; j ++ )
    		{
    			if(t % j == 0)
    			{
    				a[i - 1].val |= (1 << pos[j]);
    				while(t % j == 0) t /= j;
    			}
    		}
    		if(t > 1)
    		{
    			if(t > 19) a[i - 1].mp = t;
    			else a[i - 1].val |= (1 << pos[t]);
    		}
    	}
    	sort(a + 1 , a + n);
    	f[0][0] = 1;
    	for(i = last = 1 ; i < n ; i = last + 1)
    	{
    		if(a[i].mp)
    		{
    			while(last < n - 1 && a[last + 1].mp == a[i].mp) last ++ ;
    			for(j = 0 ; j < 256 ; j ++ )
    				for(k = 0 ; k < 256 ; k ++ )
    					g[j][k] = h[j][k] = f[j][k];
    			for(j = i ; j <= last ; j ++ )
    				for(k = 255 ; ~k ; k -- )
    					for(l = 255 ; ~l ; l -- )
    						if(!(k & l) && !(a[j].val & l))
    							g[k | a[j].val][l] = (g[k | a[j].val][l] + g[k][l]) % p , h[l][k | a[j].val] = (h[l][k | a[j].val] + h[l][k]) % p;
    			for(j = 0 ; j < 256 ; j ++ )
    				for(k = 0 ; k < 256 ; k ++ )
    					f[j][k] = ((g[j][k] + h[j][k] - f[j][k]) % p + p) % p;
    		}
    		else
    		{
    			last = i;
    			for(j = 0 ; j < 256 ; j ++ )
    				for(k = 0 ; k < 256 ; k ++ )
    					g[j][k] = f[j][k];
    			for(j = 255 ; ~j ; j -- )
    				for(k = 255 ; ~k ; k -- )
    					if(!(j & k) && !(a[i].val & k))
    						f[j | a[i].val][k] = (f[j | a[i].val][k] + g[j][k]) % p , f[k][j | a[i].val] = (f[k][j | a[i].val] + g[j][k]) % p;
    		}
    	}
    	for(i = 0 ; i < 256 ; i ++ )
    		for(j = 0 ; j < 256 ; j ++ )
    			if(!(i & j) && f[i][j])
    				ans = (ans + f[i][j]) % p;
    	printf("%d
    " , ans);
    	return 0;
    }
    
  • 相关阅读:
    深入理解JavaScript中的this关键字
    使用yii的layout,加入<?php echo $content; ?>这句话时,它会自动在子页面上面添加一个div包裹
    使用 BEGINCONTENT() 和 ENDCONTENT() 设定 YII 的 LAYOUTS
    孙悟空的师傅是谁
    PHP MemCached高级缓存配置图文教程
    C++中的基类与派生类
    矩阵快速幂
    华为上机测试题
    2016网易研发题目
    冒泡排序
  • 原文地址:https://www.cnblogs.com/GXZlegend/p/7128250.html
Copyright © 2020-2023  润新知