zoukankan      html  css  js  c++  java
  • 逃亡「GDOI2017」

    【题目描述】
    兔兔和蛋蛋是一对cp,她们的国家目前正发生战乱,所以她们正在决定是否一起逃离这个国家。

    该国家有(n)个城市,城市之间的道路形成一棵有向树。只能从父节点城市走到子节点城市。(1)号城市是首都,从城市(1)可以到达所有城市。每个城市都有一支军队,编号为(i)的城市其军队的攻击力为(b_i),如果城市(i)能到达城市(j),且(b_i > b_j)(城市 i 军队的攻击力大于城市 j 军队的攻击力),则城市(i)会向城市(j)发动(a_i)次战争。

    兔兔和蛋蛋这对cp决定当战争发生次数之和多于(k)的时候一起逃离这个国家,但是他们现在不知道各个城市的攻击力(b_i),只知道(0 leq b_i leq m)。她们想知道该国家发生战争次数恰好为(0, 1,dots, k) 的方案数(两个方案不同当且仅当存在一个城市(i)(b_i)的值不同),你能帮助她们吗?

    【输入格式】
    第一行三个整数(n,m,k),意义如上所述。

    第二行(n)个数(a_i),意义如上所述。

    第三行(n-1)个数,第(i)个数表示第(i + 1)个城市的父节点城市编号。

    【输出格式】
    (k + 1)行,第(i)行表示战争发生次数为(i-1)的方案数,答案可能很大,你只需要输出答案模(10^9 + 7)之后的值就可以了。

    (0 < n leq 14, k leq 20, 0 leq m leq 100000000, 0 leq a_i, b_i leq m)

    题解

    看到(nle 14)就八成是状压DP了

    容易发现我们只关心(n)个点之间的大小关系,至于(m)的限制最后乘上组合数即可

    所以我们可以考虑这样一种DP策略

    开始时所有点都没有被赋值(指(b_i)) 然后我们按照值从小到大的顺序一个一个地给它们赋值 这样每次赋值的点都比之前赋了值的所有点要大(或等于) 这样的话就方便进行转移

    我们设(g[i][S])表示 (S)为已赋值的点的集合 此时将(i)点赋值 会新产生多少战争

    由于是按照从小到大的顺序赋值的 所以(b_i)肯定比(S)中所有点都大 所以(g[i][S])就等于 (S)中在(i)子树里的点的数目 乘 (a_i)

    但是还存在一个问题 万一两个点的值一样怎么办?这个其实也不难解决

    我们设(f[i][j][S])表示已赋值的点有(i)个不同的值 有(j)场战争 已赋值的点集是(S)的方案数

    最外层循环是(i) 然后按照dfs序来枚举一个要赋值的点(x) 我们打算把(x)赋值为(i)

    然后枚举加入(x)后有多少场战争以及一个包含(x)的集合(S) 那么去掉(x)后就是(S_0=S-2^x)

    首先 (b_x)可以比之前的所有点都大 即(f[i][j][S] += f[i-1][j-g[x][S_0]][S_0])

    然后 也可能前面已经加入了值也是(i)的点 所以(f[i][j][S] += f[i][j-g[x][S_0]][S_0])

    由于我们是按照dfs序枚举的 所以第二种情况中一定没有(x)子树中的点先被赋值成(i) 所以这个转移是没有问题的

    最后统计答案 发动(k)次战争的方案数就是(sumlimits_{i=1}^{n} f[i][k][2^n-1] * C(m,i))
    (C(m,i))就是我们要从(m)个可选的值里取(i)个来用

    时间复杂度(O(n^22^nk))

    更新:忘记放代码了QAQ

    代码

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	for (; ch > '9' || ch < '0'; ch = getchar()) if (ch == '-') f = -1; 
    	for (; ch <= '9' && ch >= '0'; ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ '0');
    	return x * f; 
    }
    
    const ll mod = 1000000007;
    int n, m, mx;
    int head[20], pre[40], to[40], sz;
    int dfn[20], rnk[20], out[20], a[20], tme;
    int f[15][21][(1<<15)], g[20][(1<<15)]; 
    ll fac[20], inv[20];
    //"点x"表示dfs序第x位的点 
    //f[i][j][k] 共有i个不同的b[x] 会发动j次战争 k为已经确定b[x]的点的状压 
    //g[i][k] k为已经确定b[x]的点的状压 确定b[i]会新增加多少战争
    
    void addedge(int u, int v) {
    	pre[++sz] = head[u]; head[u] = sz; to[sz] = v;
    	pre[++sz] = head[v]; head[v] = sz; to[sz] = u;
    }
    
    ll calc(int l, int r) {
    	ll ret = 1;
    	for (int i = l; i <= r; i++) ret = ret * i % mod;
    	return ret;
    }
    
    inline ll C(int _n, int _m) {
    	return calc(_n - _m + 1, _n) * inv[_m] % mod;
    }
    
    inline ll fpow(ll x, ll t) {
    	ll ret = 1;
    	for (; t; t >>= 1, x = x * x % mod) if (t & 1) ret = ret * x % mod;
    	return ret;
    }
    
    void dfs(int x, int fa) { //确定dfs序 
    	dfn[x] = ++tme; rnk[tme] = x;
    	for (int i = head[x]; i; i = pre[i]) {
    		if (to[i] != fa) dfs(to[i], x);
    	}
    	out[x] = tme;
    }
    
    int main() {
    	fac[0] = inv[0] = 1;
    	for (int i = 1; i <= 14; i++) fac[i] = fac[i-1] * i % mod;
    	for (int i = 1; i <= 14; i++) inv[i] = fpow(fac[i], mod - 2);
    	n = read(); m = read()+1; mx = read();
    	for (int i = 1; i <= n; i++) a[i] = read();
    	for (int i = 2; i <= n; i++) {
    		addedge(read(), i);
    	}
    	dfs(1, 0); 
    	for (int i = 0; i <= (1 << n) - 1; i++) {
    		for (int j = 1; j <= n; j++) {
    			int x = rnk[j];
    			if (!(i & (1 << (j-1)))) {
    				for (int k = dfn[x] + 1; k <= out[x]; k++) {
    					if (i & (1 << (k-1))) g[j][i] += a[x];
    				}
    			}
    		}
    	}
    	f[0][0][0] = 1;
    	for (int i = 1; i <= min(n, m); i++) { //枚举有i个不同的b[x]的取值 
    		for (int x = 1; x <= n; x++) { //枚举要确定b[x]的值
    			for (int j = 0; j <= mx; j++) { //将会有j场战争 
    				for (int k = (1 << (x-1)); k <= (1 << n) - 1; k = ((k + 1) | (1 << (x-1)))) { //枚举一个包含j的状态压缩 
    					int now = j - g[x][k-(1<<(x-1))]; //加入x前有now场战争
    					if (now < 0) continue;
    					f[i][j][k] = (f[i][j][k] + f[i-1][now][k-(1<<(x-1))]) % mod;
    					f[i][j][k] = (f[i][j][k] + f[i][now][k-(1<<(x-1))]) % mod; 
    				} 
    			}
    		} 
    	} 
    	for (int i = 0; i <= mx; i++) {
    		ll ans = 0;
    		for (int j = 1; j <= min(n, m); j++) {
    			ans = (ans + f[j][i][(1<<n)-1] * C(m, j) % mod) % mod;
    		}	
    		printf("%lld
    ", ans);
    	}
    	return 0;
    }
    
  • 相关阅读:
    表单标签
    无序列表有序列表
    跳转锚点
    HTML标签01
    HTML基本结构和属性
    python爬虫学习笔记(二十三)-Scrapy框架 CrawlSpider
    python爬虫学习笔记(二十二)-Scrapy框架 案例实现
    python爬虫学习笔记(二十一)-Scrapy框架 setting
    python爬虫学习笔记(二十)-Scrapy框架 Pipeline
    python爬虫学习笔记(十九)-Scrapy 数据的保存
  • 原文地址:https://www.cnblogs.com/ak-dream/p/AK_DREAM66.html
Copyright © 2011-2022 走看看