【bzoj4455】[Zjoi2016]小星星
2016-04-07 23:21
645 查看
Problem
Description
小Y是一个心灵手巧的女孩子,她喜欢手工制作一些小饰品。她有n颗小星星,用m条彩色的细线串了起来,每条细线连着两颗小星星。有一天她发现,她的饰品被破坏了,很多细线都被拆掉了。这个饰品只剩下了n-1条细线,但通过这些细线,这颗小星星还是被串在一起,也就是这些小星星通过这些细线形成了树。小Y找到了这个饰品的设计图纸,她想知道现在饰品中的小星星对应着原来图纸上的哪些小星星。如果现在饰品中两颗小星星有细线相连,那么要求对应的小星星原来的图纸上也有细线相连。
小Y想知道有多少种可能的对应方式。只有你告诉了她正确的答案,她才会把小饰品做为礼物送给你呢。
Input
第一行包含个2正整数n,m,表示原来的饰品中小星星的个数和细线的条数。接下来m行,每行包含2个正整数u,v,表示原来的饰品中小星星u和v通过细线连了起来。
这里的小星星从1开始标号。保证u≠v,且每对小星星之间最多只有一条细线相连。
接下来n-1行,每行包含个2正整数u,v,表示现在的饰品中小星星u和v通过细线连了起来。
保证这些小星星通过细线可以串在一起。
Output
输出共1行,包含一个整数表示可能的对应方式的数量。如果不存在可行的对应方式则输出0。Sample Input
4 31 2
1 3
1 4
4 1
4 2
4 3
Sample Output
6数据范围
对于100%的数据 n<=1000Solution
首先,这道题有很多的部分分。20%可以暴力枚举。
40%可以当做一条链的情况做状压DP。
总共60%~70%的数据不用卡常。
当然剩下的30%我就呵呵哒。
咳咳,进入正题。
首先我们考虑一种比较直观的状压方法,用f[i][state]表示i号点所在的子树,使用了state所表示的点进行映射。这样每一次转移都需要枚举子集,复杂度O(3n∗n2),需要非常高(jiao)深(shi)的常数优化技巧。
这种做法的复杂度瓶颈明显出现在枚举子集上,那怎么优化这个枚举子集的过程呢?我们考虑使用容斥原理。假设每个点映射的点构成的集合是可重集(用state表示),也就是说树上两个不同点的映射可以相同。那么首先枚举state,复杂度O(2n),接下来只需要记f[i][j]表示第树上i号点映射图中j号点的方案数,枚举所有孩子的f[k][l],就可以做到O(n3)计算。注意k的枚举一共只有n次。
接下来使用容斥原理,对于State=Si,我们记reti=Σf[root][j],j∈Si,若|Si|≡n(mod2),则ans+=reti,否则ans−=reti。只是使用了同加异减的想法,非常简单。
至此,整个问题在O(2n∗n3)的时间内得到完成,只需要少量的常数优化即可通过本题。
Code
#include <bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define red(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long inline int read() { char c = getchar(); int x = 0, f = 1; while(!isdigit(c)) { if (c == '-') f = -1; c = getchar(); } while(isdigit(c)) { x = x * 10 + c - '0'; c = getchar(); } return x * f; } const int N = 20; int n, m, tail = 0; ll f ; int hash , g , gt , fa , p , q , head , e [2]; void addedge(int s, int t) { e[++tail][0] = t; e[tail][1] = head[s]; head[s] = tail; } void bfs(int s) { int l = 1, r = 1; q[1] = s; while(l <= r) { int x = q[l], ch = 0; l++; rep(i, 1, n) if (gt[x][i] && i != fa[x]) q[++r] = i, fa[i] = x, ++ch, addedge(x, i); if (ch == 0) hash[x] = 1; } } void dp(int s, int state, int cnt) { red(i, n, 1) { int x = q[i]; if (hash[x]) continue; rep(j, 1, cnt) { for(int i = head[x]; i != -1; i = e[i][1]) { int ch = e[i][0]; ll num = 0; if (fa[ch] != x) continue; rep(k, 1, cnt) if (g[p[j]][p[k]]) num += f[ch][k]; f[x][j] *= num; } } } } int get_cnt(int x) { int cnt = 0, j = 0; while(x) { j++; if (x & 1) p[++cnt] = j; x >>= 1; } return cnt; } int main() { n = read(); m = read(); rep(i, 1, n) g[i][i] = 1; int x, y; rep(i, 1, m) x = read(), y = read(), g[x][y] = g[y][x] = 1; rep(i, 1, n - 1) x = read(), y = read(), gt[x][y] = gt[y][x] = 1; memset(hash, 0, sizeof(hash)); fa[1] = 0; rep(i, 1, n) head[i] = -1; bfs(1); ll ans = 0, tag = n % 2; rep(state, 1, ((1 << n) - 1)) { int cnt = get_cnt(state); ll flag = (cnt % 2 == tag) ? 1 : -1, _ans = 0; rep(i, 1, n) rep(j, 1, cnt) f[i][j] = 1; dp(1, state, cnt); rep(i, 1, cnt) _ans += f[1][i]; ans += flag * _ans; } cout << ans << endl; return 0; }
尾声
听说写ZJOI的题解会有很多人看诶然而我马上也要省选啦TAT
咕咕
加油啦
End.
相关文章推荐
- zcmu1138
- Java中的Runnable、Callable、Future、FutureTask的区别与示例
- 153 mysql索引优化入门
- django 1.8 日志配置
- C# 使用正则表达式去掉字符串中的数字,或者去掉字符串中的非数字
- Spring Aop
- 将电脑变成外网可访问的服务器
- 黎活明给程序员的忠告
- Spring Boot Web应用的异常处理
- Mycat 数据库分库分表中间件
- 对WinForm的App.config文件进行加密
- 【BZOJ3590】[Snoi2013]Quare【双连通分量】【状压DP】【神题】
- 问卷调查
- LeetCode *** 86. Partition List
- 一个排好序的数组,找出两数之和为m的所有组合
- 在NodeJS中使用流程控制工具Async
- HDU 5273 Dylans loves sequence 暴力递推
- FZU 2032 高精度小数加法
- 在CentOS上安装Java环境:[1]使用yum安装java
- zookeeper、zkui、kafka部署技术