mahjong
期望和牌巡数等于对于所有 i ≥ 0 i\ge 0 i≥0 ,摸了 i i i 张牌仍然没和的概率之和。考虑如何DP一堆牌是否能和,七对子可以直接把对子记下来,而普通的集合需要跑一个DP,记 f ( i , a , b , c ) f(i, a,b,c) f(i,a,b,c) 表示考虑前 i i i 张, a a a 表示是否有雀头, b b b 表示 i − 2 , i − 1 , i i-2,i-1,i i−2,i−1,i 的顺子个数, c c c 表示 i − 1 , i , i + 1 i-1,i,i+1 i−1,i,i+1 的顺子个数,能得到的最大面子数,这里 0 ≤ b , c < 3 , a ∈ { 0 , 1 } 0\le b,c<3, a\in \{0, 1\} 0≤b,c<3,a∈{0,1} 。然后就可以跑个DP套DP了。注意到有效状态不多,直接搜出所有有效状态就能过了。
#include <bits/stdc++.h>
using namespace std;
const int md = 998244353;
inline void add(int &x, int y) {
x += y;
if (x >= md) {
x -= md;
}
}
inline int mul(int x, int y) {
return (int) ((long long) x * y % md);
}
long long encode(vector<int> dp) {
long long code = 0;
for (int i = 0; i < 19; ++i) {
code = code * 8 + dp[i] + 1;
}
return code;
}
int main() {
#ifdef wxh010910
freopen("input.txt", "r", stdin);
#endif
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
vector<vector<int>> goes;
vector<vector<int>> all;
map<long long, int> id;
auto extend = [&](vector<int> dp) {
long long code = encode(dp);
if (!id.count(code)) {
id[code] = all.size();
all.push_back(dp);
goes.push_back(vector<int>(5, -1));
}
};
{
vector<int> initial(19, -1);
initial[0] = initial[18] = 0;
extend(initial);
}
for (int i = 0; i < (int) all.size(); ++i) {
vector<int> dp = all[i];
for (int num = 0; num <= 4; ++num) {
vector<int> new_dp(19, -1);
new_dp[18] = dp[18] + (num >= 2);
for (int state = 0; state < 18; ++state) {
if (dp[state] != -1) {
int eyes = state / 9, foo = state / 3 % 3, bar = state % 3;
if (num >= foo + bar) {
int new_num = num - (foo + bar);
for (int baz = 0; baz < 3 && baz <= new_num; ++baz) {
int to_triplet = new_num - baz;
new_dp[eyes * 9 + bar * 3 + baz] = max(new_dp[eyes * 9 + bar * 3 + baz], dp[state] + foo + (to_triplet >= 3));
if (to_triplet >= 2) {
new_dp[(eyes | 1) * 9 + bar * 3 + baz] = max(new_dp[(eyes | 1) * 9 + bar * 3 + baz], dp[state] + foo);
}
}
}
}
}
bool win = new_dp[18] >= 7;
for (int state = 9; state < 18; ++state) {
if (new_dp[state] >= 4) {
win = true;
}
}
if (!win) {
for (int state = 0; state < 9; ++state) {
new_dp[state] = min(new_dp[state], 4);
}
extend(new_dp);
goes[i][num] = id[encode(new_dp)];
}
}
}
int n;
cin >> n;
vector<int> cnt(n);
for (int i = 0; i < 13; ++i) {
int foo, bar;
cin >> foo >> bar;
--foo;
++cnt[foo];
}
vector<int> fact(n * 4 + 1), inv_fact(n * 4 + 1);
fact[0] = fact[1] = inv_fact[0] = inv_fact[1] = 1;
for (int i = 2; i <= n * 4; ++i) {
fact[i] = mul(fact[i - 1], i);
inv_fact[i] = mul(md - md / i, inv_fact[md % i]);
}
for (int i = 2; i <= n * 4; ++i) {
inv_fact[i] = mul(inv_fact[i - 1], inv_fact[i]);
}
auto binom = [&](int x, int y) {
return mul(fact[x], mul(inv_fact[y], inv_fact[x - y]));
};
auto inv_binom = [&](int x, int y) {
return mul(inv_fact[x], mul(fact[y], fact[x - y]));
};
int m = all.size();
vector<vector<int>> dp(1, vector<int>(m));
dp[0][0] = 1;
for (int i = 0; i < n; ++i) {
vector<vector<int>> new_dp((i + 1) * 4 + 1, vector<int>(m));
for (int j = 0; j <= i * 4; ++j) {
for (int k = 0; k < m; ++k) {
if (dp[j][k]) {
for (int num = cnt[i]; num <= 4; ++num) {
if (goes[k][num] != -1) {
add(new_dp[j + num][goes[k][num]], mul(dp[j][k], binom(4 - cnt[i], 4 - num)));
}
}
}
}
}
swap(dp, new_dp);
}
int ans = 0;
for (int i = 13; i <= n * 4; ++i) {
for (int j = 0; j < m; ++j) {
if (dp[i][j]) {
add(ans, mul(dp[i][j], inv_binom(4 * n - 13, i - 13)));
}
}
}
cout << ans << "\n";
return 0;
}
segment
记 f ( x ) f(x) f(x) 表示 x x x 有标记的概率, g ( x ) g(x) g(x) 表示 x x x 到根的链有标记的概率,那么:
- 从根到目标区间的链: f ′ ( x ) = 1 2 f ( x ) , g ′ ( x ) = 1 2 g ( x ) f'(x) = \frac{1}{2} f(x), g'(x) = \frac{1}{2} g(x) f′(x)=21f(x),g′(x)=21g(x) 。
- 目标区间: f ′ ( x ) = 1 2 ( f ( x ) + 1 ) , g ′ ( x ) = 1 2 ( g ( x ) + 1 ) f'(x)=\frac{1}{2}(f(x)+1), g'(x) = \frac{1}{2}(g(x)+1) f′(x)=21(f(x)+1),g′(x)=21(g(x)+1) 。
- 目标区间的子树: f ′ ( x ) = f ( x ) , g ′ ( x ) = 1 2 ( g ( x ) + 1 ) f'(x)=f(x), g'(x)=\frac{1}{2}(g(x)+1) f′(x)=f(x),g′(x)=21(g(x)+1) 。
- 被下放标记的点: f ′ ( x ) = 1 2 ( f ( x ) + g ( x ) ) , g ′ ( x ) = g ( x ) f'(x) = \frac{1}{2}(f(x)+g(x)), g'(x)=g(x) f′(x)=21(f(x)+g(x)),g′(x)=g(x) 。
- 其他点: f ′ ( x ) = f ( x ) , g ′ ( x ) = g ( x ) f'(x)=f(x), g'(x)=g(x) f′(x)=f(x),g′(x)=g(x) 。
第一种、第二种、第四种只有 O ( n log n ) O(n\log n) O(nlogn) 个,暴力维护;第三种可以认为是子树打标记,懒标记维护即可。
#include <bits/stdc++.h>
using namespace std;
const int md = 998244353;
const int p = (md + 1) / 2;
inline void add(int &x, int y) {
x += y;
if (x >= md) {
x -= md;
}
}
inline void sub(int &x, int y) {
x -= y;
if (x < 0) {
x += md;
}
}
inline int mul(int x, int y) {
return (int) ((long long) x * y % md);
}
int main() {
#ifdef wxh010910
freopen("input.txt", "r", stdin);
#endif
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, m;
cin >> n >> m;
vector<int> f(n * 2 - 1, 0);
vector<int> g(n * 2 - 1, 0);
vector<int> a(n * 2 - 1, 1);
vector<int> b(n * 2 - 1, 0);
int ans = 0;
int cnt = 1;
auto apply = [&](int x, int aa, int bb) {
g[x] = mul(g[x], aa);
add(g[x], bb);
a[x] = mul(a[x], aa);
b[x] = mul(b[x], aa);
add(b[x], bb);
};
function<void(int, int, int, int, int)> modify = [&](int x, int l, int r, int ll, int rr) {
if (ll <= l && r <= rr) {
sub(ans, f[x]);
add(f[x], 1);
f[x] = mul(f[x], p);
add(ans, f[x]);
apply(x, p, p);
} else {
int y = (l + r) >> 1, z = x + ((y - l + 1) << 1);
if (a[x] != 1 && b[x] != 0) {
apply(x + 1, a[x], b[x]);
apply(z, a[x], b[x]);
a[x] = 1;
b[x] = 0;
}
if (ll <= y) {
modify(x + 1, l, y, ll, rr);
} else {
sub(ans, f[x + 1]);
add(f[x + 1], g[x + 1]);
f[x + 1] = mul(f[x + 1], p);
add(ans, f[x + 1]);
}
if (rr > y) {
modify(z, y + 1, r, ll, rr);
} else {
sub(ans, f[z]);
add(f[z], g[z]);
f[z] = mul(f[z], p);
add(ans, f[z]);
}
sub(ans, f[x]);
f[x] = mul(f[x], p);
add(ans, f[x]);
g[x] = mul(g[x], p);
}
};
while (m--) {
int type;
cin >> type;
if (type == 2) {
cout << mul(ans, cnt) << "\n";
} else {
int l, r;
cin >> l >> r;
--l;
--r;
modify(0, 0, n - 1, l, r);
cnt = mul(cnt, 2);
}
}
return 0;
}
minimax
考虑暴力怎么做,枚举一个 k k k ,算修改不超过 k k k 时能改变根权值的方案数。
考虑根权值的叶子对应的链,如果这条链上有值改变了,则根的权值就会改变。补集转化后,求它们都不会变的方案数,这可以用一个简单的树DP实现。注意到 k k k 从 1 1 1 到 n n n ,每个叶子的DP值至多改变一次,直接上个动态DP就行了。
#include <bits/stdc++.h>
using namespace std;
const int md = 998244353;
inline void add(int &x, int y) {
x += y;
if (x >= md) {
x -= md;
}
}
inline void sub(int &x, int y) {
x -= y;
if (x < 0) {
x += md;
}
}
inline int mul(int x, int y) {
return (int) ((long long) x * y % md);
}
inline int inv(int a) {
int b = md, u = 0, v = 1;
while (a) {
int t = b / a;
b -= t * a;
swap(a, b);
u -= t * v;
swap(u, v);
}
if (u < 0) {
u += md;
}
return u;
}
struct node {
int foo, bar;
node(int foo = 1, int bar = 0): foo(foo), bar(bar) {
}
};
node unite(const node &l, const node &r) {
node res;
res.foo = mul(l.foo, r.foo);
res.bar = mul(r.bar, l.foo);
add(res.bar, l.bar);
return res;
}
class segtree {
public:
vector<node> tree;
int n;
void init(int sz) {
n = sz;
tree.resize(n * 2 - 1);
}
void modify(int x, int l, int r, int p, node v) {
if (l == r) {
tree[x] = v;
} else {
int y = (l + r) >> 1, z = x + ((y - l + 1) << 1);
if (p <= y) {
modify(x + 1, l, y, p, v);
} else {
modify(z, y + 1, r, p, v);
}
tree[x] = unite(tree[x + 1], tree[z]);
}
}
void modify(int p, node v) {
modify(0, 0, n - 1, p, v);
}
int query() {
return tree[0].bar;
}
};
struct num {
int foo, bar;
num(int foo = 1, int bar = 0): foo(foo), bar(bar) {
}
void multiply(int x) {
if (!x) {
++bar;
} else {
foo = mul(foo, x);
}
}
void divide(int x) {
if (!x) {
--bar;
} else {
foo = mul(foo, inv(x));
}
}
int get() {
return bar ? 0 : foo;
}
};
int main() {
#ifdef wxh010910
freopen("input.txt", "r", stdin);
#endif
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, low, high;
cin >> n >> low >> high;
vector<vector<int>> adj(n);
for (int i = 0; i < n - 1; ++i) {
int from, to;
cin >> from >> to;
--from;
--to;
adj[from].push_back(to);
adj[to].push_back(from);
}
vector<int> depth(n);
vector<int> value(n);
vector<int> pr(n, -1);
vector<int> sz(n, 1);
function<void(int)> init_value = [&](int x) {
value[x] = depth[x] & 1 ? n : -1;
for (auto y : adj[x]) {
if (y != pr[x]) {
depth[y] = depth[x] + 1;
pr[y] = x;
init_value(y);
if (depth[x] & 1) {
value[x] = min(value[x], value[y]);
} else {
value[x] = max(value[x], value[y]);
}
sz[x] += sz[y];
}
}
if (value[x] >= n || value[x] < 0) {
value[x] = x;
}
};
init_value(0);
vector<int> chain;
for (int i = value[0]; ~i; i = pr[i]) {
chain.push_back(i);
}
for (int i = (int) chain.size() - 1; i; --i) {
int from = chain[i], to = chain[i - 1];
adj[from].erase(find(adj[from].begin(), adj[from].end(), to));
adj[to].erase(find(adj[to].begin(), adj[to].end(), from));
sz[from] -= sz[to];
pr[to] = -1;
}
vector<vector<int>> modifies(n + 1);
vector<int> son(n, -1);
vector<int> top(n, -1);
vector<int> ways(n);
vector<bool> type(n);
vector<segtree> seg(n);
vector<num> light(n);
vector<int> dp(n);
function<void(int, int, bool, bool)> hld = [&](int x, int c, bool t, bool parity) {
type[x] = t;
top[x] = c;
for (auto y : adj[x]) {
if (y != pr[x] && (son[x] == -1 || sz[y] > sz[son[x]])) {
son[x] = y;
}
}
if (son[x] == -1) {
ways[x] = 2;
seg[c].init(depth[x] - depth[c] + 1);
if (!parity) {
if (value[x] < value[0]) {
dp[x] = 2;
modifies[value[0] - value[x] + 1].push_back(x);
}
} else {
if (value[x] > value[0]) {
dp[x] = 2;
modifies[value[x] - value[0] + 1].push_back(x);
}
}
seg[c].modify(depth[x] - depth[c], node(0, dp[x]));
} else {
hld(son[x], c, !t, parity);
ways[x] = ways[son[x]];
for (auto y : adj[x]) {
if (y != pr[x] && y != son[x]) {
hld(y, y, !t, parity);
ways[x] = mul(ways[x], ways[y]);
if (t) {
light[x].multiply(dp[y]);
} else {
light[x].multiply((ways[y] + md - dp[y]) % md);
}
}
}
if (t) {
dp[x] = mul(dp[son[x]], light[x].get());
seg[c].modify(depth[x] - depth[c], node(light[x].get(), 0));
} else {
dp[x] = mul(ways[son[x]] + md - dp[son[x]], light[x].get());
dp[x] = (ways[x] + md - dp[x]) % md;
seg[c].modify(depth[x] - depth[c], node(light[x].get(), (ways[x] - mul(light[x].get(), ways[son[x]]) + md) % md));
}
}
};
vector<int> ans(n + 1);
ans[n] = 2;
num cur;
for (auto x : chain) {
for (auto y : adj[x]) {
hld(y, y, false, depth[x] & 1);
ans[n] = mul(ans[n], ways[y]);
cur.multiply(dp[y]);
}
}
for (int i = 1; i < n; ++i) {
for (auto x : modifies[i]) {
seg[top[x]].modify(depth[x] - depth[top[x]], node(0, 1));
x = top[x];
int new_dp = seg[x].query();
while (value[pr[x]] != value[0]) {
if (type[pr[x]]) {
light[pr[x]].divide(dp[x]);
light[pr[x]].multiply(new_dp);
} else {
light[pr[x]].divide((ways[x] + md - dp[x]) % md);
light[pr[x]].multiply((ways[x] + md - new_dp) % md);
}
dp[x] = new_dp;
x = pr[x];
if (type[x]) {
seg[top[x]].modify(depth[x] - depth[top[x]], node(light[x].get(), 0));
} else {
seg[top[x]].modify(depth[x] - depth[top[x]], node(light[x].get(), (ways[x] - mul(light[x].get(), ways[son[x]]) + md) % md));
}
x = top[x];
new_dp = seg[x].query();
}
cur.divide(dp[x]);
cur.multiply(new_dp);
dp[x] = new_dp;
}
ans[i] = ans[n];
sub(ans[i] = ans[n], cur.get());
}
sub(ans[n], 1);
for (int i = n; i; --i) {
sub(ans[i], ans[i - 1]);
}
for (int i = low; i <= high; ++i) {
if (i > low) {
cout << " ";
}
cout << ans[i];
}
cout << "\n";
return 0;
}