• [NOI2020] 命运


    题面

    题解

    CF1327F AND Segments + 整体 dp。

    首先预处理 (mathrm{pre}_i) 表示向上最深的 (f(e) = 1) 的边的深度最小值。

    (f_{i, j}) 表示当前在点 (i),最深的 (f(e) = 1) 的深度为 (j) 的方案数。

    枚举点 (i) 和儿子之间的边是否设成 (1),有:

    [f_{i, j} = [mathrm{pre}_i < j leq mathrm{dep}_i]prod_{s in mathrm{son}(i)} (f_{s,j} + f_{s, mathrm {dep}_s}) ]

    其中 (f_{s, mathrm{dep}_s}) 表示将 (s) 和它父亲的边设成 (1) 的方案数。

    考虑用线段树合并维护,那么只需要维护区间加,区间乘和区间赋值,维护一个标记 ((a, b)) 使得 (f stackrel{(a, b)}{longrightarrow} af + b)

    手推一下标记如何合并即可。

    代码

    #include <cstdio>
    #include <algorithm>
    #include <vector>
    
    inline int read()
    {
    	int data = 0, w = 1; char ch = getchar();
    	while (ch != '-' && (ch < '0' || ch > '9')) ch = getchar();
    	if (ch == '-') w = -1, ch = getchar();
    	while (ch >= '0' && ch <= '9') data = data * 10 + (ch ^ 48), ch = getchar();
    	return data * w;
    }
    
    const int N(5e5 + 10), Mod(998244353);
    struct edge { int next, to; } e[N << 1];
    int n, m, head[N], e_num, dep[N], pre[N], rt[N];
    inline void add_edge(int from, int to)
    	{ e[++e_num] = (edge) {head[from], to}, head[from] = e_num; }
    void dfs(int x, int fa)
    {
    	dep[x] = dep[fa] + 1;
    	for (int i = head[x]; i; i = e[i].next)
    		if (e[i].to != fa) dfs(e[i].to, x);
    }
    
    int ls[N << 6], rs[N << 6], cur, pool[N << 6], top;
    struct node { int a, b; } t[N << 6], I = (node) {1, 0};
    inline node operator * (const node &x, const node &y)
    	{ return (node) {1ll * x.a * y.a % Mod, (y.b + 1ll * x.b * y.a) % Mod}; }
    inline int operator == (const node &x, const node &y) { return x.a == y.a && x.b == y.b; }
    int newNode(const node &v)
    {
    	int x = top ? pool[top--] : ++cur;
    	return ls[x] = rs[x] = 0, t[x] = v, x;
    }
    
    void pushdown(int x)
    {
    	if (t[x] == I) return;
    	if (!ls[x]) ls[x] = newNode(t[x]); else t[ls[x]] = t[ls[x]] * t[x];
    	if (!rs[x]) rs[x] = newNode(t[x]); else t[rs[x]] = t[rs[x]] * t[x];
    	t[x] = I;
    }
    
    void Modify(int x, int ql, int qr, const node &v, int l = 1, int r = n)
    {
    	if (ql > qr) return;
    	if (ql <= l && r <= qr) return (void) (t[x] = t[x] * v);
    	int mid = (l + r) >> 1; pushdown(x);
    	if (ql <= mid) Modify(ls[x], ql, qr, v, l, mid);
    	if (mid < qr) Modify(rs[x], ql, qr, v, mid + 1, r);
    }
    
    int Query(int x, int p, int l = 1, int r = n)
    {
    	if (l == r) return t[x].b;
    	int mid = (l + r) >> 1; pushdown(x);
    	if (p <= mid) return Query(ls[x], p, l, mid);
    	else return Query(rs[x], p, mid + 1, r);
    }
    
    int merge(int &x, int &y)
    {
    	if (!ls[x] && !rs[x]) std::swap(x, y);
    	if (!ls[y] && !rs[y])
    		return t[x] = t[x] * (node) {t[y].b, 0}, x;
    	pushdown(x), pushdown(y);
    	ls[x] = merge(ls[x], ls[y]);
    	rs[x] = merge(rs[x], rs[y]);
    	return x;
    }
    
    void clear(int &x) { if (!x) return; clear(ls[x]), clear(rs[x]), pool[++top] = x, x = 0; }
    void dp(int x, int fa)
    {
    	pre[x] = std::max(pre[x], pre[fa]);
    	rt[x] = ++cur, t[cur] = (node) {0, 0}, Modify(cur, pre[x] + 1, dep[x], (node) {0, 1});
    	for (int i = head[x]; i; i = e[i].next) if (e[i].to != fa)
    		dp(e[i].to, x), rt[x] = merge(rt[x], rt[e[i].to]), clear(rt[e[i].to]);
    	if (x != 1) Modify(rt[x], 1, dep[x] - 1, (node) {1, Query(rt[x], dep[x])});
    }
    
    int main()
    {
    	n = read();
    	for (int i = 1, a, b; i < n; i++)
    		a = read(), b = read(), add_edge(a, b), add_edge(b, a);
    	m = read(), dfs(1, 0);
    	for (int i = 1, x, y; i <= m; i++)
    		x = read(), y = read(), pre[y] = std::max(pre[y], dep[x]);
    	dp(1, 0), printf("%d
    ", Query(rt[1], 1));
    	return 0;
    }
    
  • 相关阅读:
    JavaScript技巧
    函数
    windows实现应用双开
    vue组件中name属性有啥作用
    文本超出长度后多余部分显示省略号
    el-tree控件动态获取数据赋值给treeData渲染问题:render-after-expand属性
    elementUI弹框dialog的打开和关闭
    自然语言处理工具之gensim / 预训练模型 word2vec doc2vec
    Linux 根目录空间不足解决方法
    文本挖掘预处理之分词 / 向量化 / TF-IDF / Hash trick 附代码 Demo
  • 原文地址:https://www.cnblogs.com/cj-xxz/p/13534898.html
Copyright © 2020-2023  润新知