zoukankan      html  css  js  c++  java
  • JZOJ 6756. 2020.07.21【NOI2020】模拟T2 (搜索有用状态+背包dp)

    题目大意:

    (n)种花色,每个花色(m)种数字,每个花色数字牌不超过(4)张的雀魂。

    随机选(3k+2)个牌,问能拆成(k)个面子和(1)个对子的概率。

    (n,m le 10^9, k le 30)

    题解:

    先考虑一种花色的牌选出(0..3k+2)的方案数,后面做一个背包就可以组合。

    再考虑一种花色拆成若干段(>0)的连续段会比较好做,求出每个连续段(每个数字牌数都(>0))的方案数,再用一个(O(k^5))的dp也能组合。

    问题是求(f[i][j])表示长度(=i)的连续段,用了(j)张牌且合法的方案数。

    考虑怎么判断一个段是否合法,可以dp,([i=0 sim 4][j = 0 sim 4][k = 0 sim 1])表示最后两个的剩余大小和有没有选对子。

    计数不难想到dp套dp,但是(2^{5*5*2})有点大,可是经过搜索发现有用状态只有(248)个。

    于是就做完了,挺好写的。

    比赛时忘记搜索缩状态了,GG

    Code:

    #include<bits/stdc++.h>
    #define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
    #define ff(i, x, y) for(int i = x, _b = y; i <  _b; i ++)
    #define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
    #define ll long long
    #define pp printf
    #define hh pp("
    ")
    using namespace std;
    
    ll m, n, k;
    
    const int N = 100;
    
    const int mo = 1e9 + 7;
    
    ll ksm(ll x, ll y) {
    	ll s = 1;
    	for(; y; y /= 2, x = x * x % mo)
    		if(y & 1) s = s * x % mo;
    	return s;
    }
    
    ll cnt[N][N];
    
    ll c[5][5];
    
    void add(ll &x, ll y) {
    	(x += y) %= mo;
    }
    
    namespace sub1 {
    	ll a2[51];
    	int id[5][5][2], id0;
    	map<ll, int> bz; int bz0;
    	
    	int bx[51];
    	
    	const int M = 300;
    	
    	int to[M][5];
    	
    	int ok1[M], ok2[M];
    	
    	int dg(ll s) {
    		if(bz[s]) return bz[s];
    		bz[s] = ++ bz0;
    		ok1[bz0] = s >> id[0][0][0] & 1;
    		ok2[bz0] = s >> id[0][0][1] & 1;
    		fo(x, 1, 4) {
    			fo(i, 0, 49) bx[i] = 0;
    			fo(i, 0, 4) fo(j, 0, 4) fo(k, 0, 1) if(s >> id[i][j][k] & 1) {
    				 fo(nk, 0, !k) fo(c, 0, 1) {
    					int a[4];
    					a[1] = i, a[2] = j, a[3] = x;
    					while(a[1] && a[2] && a[3]) a[1] --, a[2] --, a[3] --;
    					if(a[1]) continue;
    					a[3] -= nk * 2 + c * 3;
    					if(a[3] < 0) continue;
    					bx[id[a[2]][a[3]][k | nk]] = 1;
    				}
    			}
    			ll ns = 0;
    			fo(i, 0, 49) if(bx[i]) ns |= a2[i];
    			to[bz[s]][x] = dg(ns);
    		}
    		return bz[s];
    	}
    	
    	ll f[N][N][M];
    	
    	
    	void work() {
    		fo(i, 0, 4) {
    			c[i][0] = 1;
    			fo(j, 1, i) c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % mo;
    		}
    		a2[0] = 1; fo(i, 1, 50) a2[i] = a2[i - 1] * 2;
    		fo(i, 0, 4) fo(j, 0, 4) fo(k, 0, 1)
    			id[i][j][k] = ++ id0;
    		dg(a2[id[0][0][0]]);
    		f[0][0][1] = 1;
    		fo(i, 0, 3 * k + 2) {
    			fo(j, 0, 3 * k + 2) {
    				fo(u, 1, bz0) if(f[i][j][u]) {
    					if(ok1[u]) add(cnt[i][j], f[i][j][u]);
    					if(ok2[u]) add(cnt[i][j], f[i][j][u]);
    					fo(x, 1, 4) if(j + x <= 3 * k + 2) {
    						add(f[i + 1][j + x][to[u][x]], f[i][j][u] * c[4][x]);
    					}
    				}
    			}
    		}
    	}
    }
    
    ll g[N];
    
    ll inv[N];
    
    ll C(int n, int m) {
    	if(n < m) return 0;
    	ll s = 1;
    	fo(i, n - m + 1, n)	s = s * i % mo;
    	fo(i, 1, m) s = s * inv[i] % mo;
    	return s;
    }
    
    namespace sub2 {
    	ll f[N][N][N];
    	void work() {
    		fo(i, 1, 3 * k + 2) inv[i] = ksm(i, mo - 2);
    		f[0][0][0] = 1;
    		fo(i, 0, 3 * k + 2) {
    			fo(j, 0, 3 * k + 2) {
    				fo(u, 0, 3 * k + 2) if(f[i][j][u]) {
    					add(g[u], f[i][j][u] * C(m - j + 1, i));
    					fo(p, 1, 3 * k + 2) fo(q, 1, 3 * k + 2) if(cnt[p][q] && j + p <= 3 * k + 2 && u + q <= 3 * k + 2) {
    						if(u % 3 == 2 && q % 3 == 2) continue;
    						add(f[i + 1][j + p][u + q], f[i][j][u] * cnt[p][q]);
    					}
    				}
    			}
    		}
    	}
    }
    
    namespace sub3 {
    	ll f[N][N];
    	void work() {
    		f[0][0] = 1;
    		ll ans = 0;
    		fo(i, 0, 3 * k + 2) {
    			fo(j, 0, 3 * k + 2) if(f[i][j]) {
    				fo(u, 1, 3 * k + 2) if(j + u <= 3 * k + 2 && g[u]) {
    					if(j % 3 == 2 && u % 3 == 2) continue;
    					add(f[i + 1][j + u], f[i][j] * g[u]);
    				}
    			}
    			ans = (ans + C(n, i) * f[i][3 * k + 2]) % mo;
    		}
    		pp("%lld
    ", ans);
    	}
    }
    
    int main() {
    	freopen("majsoul.in", "r", stdin);
    	freopen("majsoul.out", "w", stdout);
    	scanf("%lld %lld %lld", &m, &n, &k);
    	sub1 :: work();
    	sub2 :: work();
    	sub3 :: work();
    }
    
  • 相关阅读:
    mysql那些事(1)手机号与座机号码如何存储
    分享一个PHP调用RestFul接口的函数
    php sprintf用法
    HTTP状态码详解
    PHP随机生成中国人姓名的类
    PHP计算两组经纬度坐标之间的距离
    PHP根据经纬度获取在范围坐标的数据
    PHP 利用QQ邮箱发送邮件「PHPMailer」
    PHP中利用PHPMailer配合QQ邮箱实现发邮件
    修改PHP上传文件大小限制
  • 原文地址:https://www.cnblogs.com/coldchair/p/13357519.html
Copyright © 2011-2022 走看看