CF599E Sandy and Nuts

3 篇文章 0 订阅

这题挺难的,因为 n ≤ 13 n\le 13 n13,很自然地想到状压(雾)。

题目

在这里插入图片描述

dp 式子

d p [ i ] [ j ] dp[i][j] dp[i][j] 表示在状态 i i i 下,当前根为 j j j 的方案数,显然,加第二维方便转移。

可以写出一下转移方程: d p [ i ] [ j ] = ∑ d p [ u ] [ t ] × d p [ u   x o r   i ] [ j ] dp[i][j]=\sum dp[u][t]\times dp[u\ xor\ i][j] dp[i][j]=dp[u][t]×dp[u xor i][j]

最后的答案就是 ∑ d p [ 2 n − 1 ] [ k ] \sum{dp[2^n-1][k]} dp[2n1][k]

这显然对吧,意为将 t t t 为根的子树加入 j j j 的子树中。(即将 j j j t t t 连边)

但是在这里会重复计算(显然),所以我们可以选一个 i i i 中的特殊点,强制设定 u u u 中必须有这个元素,这样就可以避免重复(不妨想想为什么),我选的是不等于 j j j 的第 1 1 1 个点。

p.s.在这里要满足以下条件 t ∈ u t\in u tu(此为dp1), j ∈ u   x o r   i j\in u\ xor\ i ju xor i(此为dp2), j ∈ i j\in i ji(此为dp3)。

LCA

现在我们考虑 L C A \mathcal{LCA} LCA因为其本质为限制,不妨考虑它限制了什么。(废话)

考虑若 j = = c k j==c_k j==ck,则 a k a_k ak b k b_k bk 不能同时出现在 u u u。(这很显然,因为这样 L C A ( a k , b k ) \mathcal {LCA(a_k,b_k)} LCA(ak,bk) 就不等于 c k c_k ck 了)(此为LCA1)

另外,若 c k = = t c_k == t ck==t a k   o r   b k a_k\ or\ b_k ak or bk 有一个不在 u u u 中,舍。(此为LCA2)

原有边

若存在一边使一个端点在 i i i 里(不等于 j j j),另一个端点不在 i i i 里,舍。(原因显然,要保证它是棵树)(此为e1)

若存在多个 t t t j j j 的连边大于等于 2 2 2,舍。(此为e2)(p.s.若连边数为 0 0 0,则随便选 t t t,若连边数为 1 1 1 t t t 被固定)

到了这里,所有的元素考虑完了,时间复杂度 O ( 3 n × n ( n + m + q ) ) \mathcal{O}(3^n\times n(n+m+q)) O(3n×n(n+m+q))(枚举子集是 3 n 3^n 3n)。由于这个时间复杂度跑不满,所以能 A。

感悟:剪枝能在外层循环剪就在外层循环剪。

另外,安利一个枚举子集的方法 f o r ( i n t   k   =   i ;   k ;   k   =   ( ( k   −   1 )   &   i ) ) for(int\ k\ =\ i;\ k;\ k\ =\ ((k\ -\ 1)\ \&\ i)) for(int k = i; k; k = ((k  1) & i))

Code
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <climits>
#include <vector>
#define LL long long
using namespace std;
struct Node {
	int X, Y;
	Node() {}
	Node(int x, int y) { X = x; Y = y; }
};
const int MAXN = 15, MAXM = 105;
int n, m, q, X_[MAXN], Y_[MAXN], num, tmp;
Node u;
LL dp[1 << MAXN][MAXN], res;
bool vis[1 << MAXN][MAXN];
vector <Node> v[MAXN]; 
bool Check_In(int x, int y) { return ((x >> (y - 1)) & 1); }
int main() {
	int x, y, z, f;
	scanf("%d%d%d", &n, &m, &q);
	for(int i = 1; i <= m; i ++) scanf("%d%d", &X_[i], &Y_[i]);
	for(int i = 1; i <= q; i ++) scanf("%d%d%d", &x, &y, &z), v[z].push_back(Node(x, y));
	for(int i = 1; i <= n; i ++) dp[1 << (i - 1)][i] = 1;
	for(int i = 1; i < (1 << n); i ++) { // LCA2
		for(int j = 1; j <= n; j ++) {
			f = 0;
			for(unsigned int k = 0; k < v[j].size(); k ++) {
				if(!Check_In(i, v[j][k].X) || !Check_In(i, v[j][k].Y)) { f = 1; break; }
			}
			vis[i][j] = f;
		}
	}
	for(int i = 1; i < (1 << n); i ++) {
		for(int j = 1; j <= n; j ++) {
			if(!Check_In(i, j)) continue; // dp3
			f = 0;
			for(int t = 1; t <= m; t ++) if(j != X_[t] && Check_In(i, X_[t]) && !Check_In(i, Y_[t])) f = 1; // e1
			for(int t = 1; t <= m; t ++) if(j != Y_[t] && Check_In(i, Y_[t]) && !Check_In(i, X_[t])) f = 1; // e1
			int qwq = (i ^ (1 << (j - 1))), mst = 1;
			if(!qwq || f) continue;
			while(!(qwq & 1)) qwq >>= 1, mst ++;
			for(int k = i; k; k = ((k - 1) & i)) {
				if(!Check_In(k, mst)) continue; // 防止重复 
				f = 0;
				for(unsigned t = 0; t < v[j].size(); t ++) { // LCA1
					u = v[j][t];
					if(Check_In(k, u.X) && Check_In(k, u.Y)) { f = 1; break; }
				}
				if(f) continue;
				num = 0; tmp = 0;
				for(int t = 1; t <= m; t ++) { // e2
					if(Check_In(k, X_[t]) && Y_[t] == j) tmp ++, num = X_[t];
					else if(Check_In(k, Y_[t]) && X_[t] == j) tmp ++, num = Y_[t];
				}
				if(!tmp) {
					for(int t = 1; t <= n; t ++) {
						if(!Check_In(k, t) || vis[k][t] || !Check_In(i ^ k, j)) continue; // dp1/2 & LCA2
						dp[i][j] += dp[k][t] * dp[i ^ k][j];
					}
				}
				else if(tmp == 1) {
					if(!Check_In(k, num) || vis[k][num] || !Check_In(i ^ k, j)) continue; // dp1/2 & LCA2
					dp[i][j] += dp[k][num] * dp[i ^ k][j];
				}
			}
		}
	}
	printf("%lld", dp[(1 << n) - 1][1]); // 注意 1号点为根	 
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值