5.5 最大前缀和 & 时间流逝 & 树上异或

注意:暴力是 \(n!\) 的,不是指数。

马上就想到了假设当前选数状态是一个前缀的方案数,应该是 \(f_i \times g_{S-i}\)

考虑计算 \(f\)\(g\)。利用一些性质,简单计算即可……

需要注意的是 \(f\) 的转移,如果是【将 \(a_i\) 放在最后】不好转移,于是我们考虑把他放到最前面。


形式化题意

给定一长为 \(N\) 的有序序列 \(a\),序列内元素两两不同。你现在有一个栈,初始为空,你会不断执行以下操作直到栈内元素之和 \(>T\)。一次「操作」过程如下:

  • 如果栈不为空,则有 \(P\) 的概率弹出一个元素。

  • 如果没弹,在所有 \(\le\) 栈顶的 \(a_i\) 里随机地取一个,将其入栈(不把 \(a_i\)​ 删掉,下一次也可能继续入栈)。

问操作终止时进行操作次数的期望。

Sol

首先考虑状态树。是一个单调递减的状态,并且和很小。这个状态数考试的时候要去搜一下,如果可以接受就直接搞。结论:在 \(10^6\) 左右。

假设根节点为 \(\varnothing\)。则 \(f_i=P\times (f_{fa}+1)+\dfrac{1-P}{in_i} \times \sum (f_{to}+1)\)

注意这里的概率都是 \(i\) 这个点的,不能弄成其它。比如 \(f_{fa}+1\) 表示 \(i\) 走一步可以到 \(fa\)

【树上高斯消元】:其实就是将这个式子后效性去除。

\(f_x=k_x\times f_{fa}+b_x\),带进去每次转移的时候一下算下 \(k, b\) 即可。

注意到这是一个线性关系,我们根本就不需要知道 \(dp\) 的值,只需每次回溯 \(k,b\) 最后线性推出。

此题更特殊,因为根节点 \(P=0\),所以 \(dp_1=b_1\)


P9745 一道非常好的树上 dp

树上问题可以先考虑链的情况。

链的话就是一个 \(n^2\) dp。钦定当前第一次断。

这个题的状态定义非常的妙,记住!!!

首先注意到:\(x=\sum \prod \bigoplus\) 在加入一个新的 \(\bigoplus'\) 后,答案为 \(x\times \bigoplus'\)

注意题目是【联通块删边】。因此普通的状态不行。\(f(i,S)\) 表示当前和 \(i\) 相连的块 xor 和为 \(S\),其余的块的异或乘积的和。注意还有个 其余的块

这个状态的优势在于:你每次算一条边,如果不删,那么可以直接和 \(S\) 进行背包转移;如果删,那么跟 \(i\) 的块没有任何影响,只是更新即可。

因此还需要记录一个真正的 \(dp(i)\) 表示 \(i\) 子树的答案,处理【删边】的情况。

并且我们当前已经知道 \(i\) 所在块的状态,所以只记录其他的会更方便。

\(f(i,S)=f(i,S)\times dp(to) + \sum f(i,S\bigoplus x) \times f(to,x)\)

后面的转移是不删边,由于计算的不包含 \(i\),所以把另外两块独立的乘起来即可。

\(g\) 转移显然。

然后考虑优化,应该可以看出来要拆位。\(f(i,j,0/1)\)

然后你发现后面的转移就是把 \(S\) 给二进制分解,做一做即可。

但是为什么拆位算出来是对的。考虑 \(dp\) 的转移,这显然是没有问题的。

反正记住异或可以拆位就行了,合并应该都适用。

#include <bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;
const int N = 5e5 + 5, M = 63, Mod = 998244353; ll read();
int n, m; vector <int> G[N];
ll a[N];
int dp[N], f[N][M][2];

void dfs(int x, int fa) {
	// init x only 具体来说,是和 x 结合乘了多少 
	for(int i = 0;i <= 61; ++i)
		if(a[x] & (1ll << i)) f[x][i][1] = 1;
		else f[x][i][0] = 1;
	for(int to : G[x]) {
		if(to == fa) continue ;
		dfs(to, x);
		for(int j = 0;j <= 61; ++j) {
			int xx = f[x][j][1], y = f[x][j][0];
f[x][j][1] = (1ll * xx * dp[to] % Mod + 1ll * y * f[to][j][1] % Mod + 1ll * xx * f[to][j][0] % Mod) % Mod;
f[x][j][0] = (1ll * y * dp[to] % Mod + 1ll * xx * f[to][j][1] % Mod + 1ll * y * f[to][j][0] % Mod) % Mod;
		}
	}
	for(int j = 0;j <= 61; ++j) dp[x] = (dp[x] + (1ll << j) % Mod * 1ll * f[x][j][1] % Mod) % Mod;
}
// dp(x) = \sum f(x, y) * y 其它子树内连通块异或和的乘积。
// 具体地,你的拆位只是当一个状态变化 
signed main() {
	cin >> n;
	for(int i = 1;i <= n; ++i) a[i] = read();
	for(int i = 2, x;i <= n; ++i) {
		x = read(); 
		G[x].pb(i), G[i].pb(x); 
	} dfs(1, 0);
	cout << dp[1];
    return 0;
}
ll read() {
	char c; ll f = 1, sum = 0;
	while(c < '0' or c > '9') {if(c == '-') f = -1;c = getchar();}
	while(c >= '0' and c <= '9') {sum = (sum << 3) + (sum << 1) + (c ^ 48);c = getchar();} 
	return sum * f;
}
// f(i, j, 0) = f(i,j,0)*g(to) + f(i,j,0)*f()
// 每一位的单独情况 
posted @ 2024-05-05 09:06  LCat90  阅读(28)  评论(0编辑  收藏  举报