2025-12-10 hetao1733837的刷题笔记

2025-12-10 hetao1733837的刷题笔记

LG2664 树上游戏

原题链接:树上游戏

分析

这咋点分治?

显然,这个需要一定的转化,因为普通点分治是不好合并两个子树的 c n t cnt cnt 的。

那么,换一个角度思考,既然直接求不好求,那我对于每个颜色 j j j,记 c n t j cnt_j cntj 表示以 i i i 为端点,包含颜色 j j j 的路径数量,那么 s u m i = ∑ c n t j sum_i=\sum cnt_j sumi=∑cntj。 c n t j cnt_j cntj 的更新也很容易, c n t c o l u + = s z u cnt_{col_u}+=sz_u cntcolu+=szu, u u u 是子树。

行,那我们维护子树中以当前根节点为端点的路径对根的贡献lca 为当前根节点的路径对子树内每个点的贡献,第一部分好做,第二部分,分讨出现的颜色即可。

正解

cpp 复制代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 200005;
int n, tot, sz[N], mx, rt;
int c[N];
bool vis[N];
vector<int> e[N];
void getrt(int u, int fa){
    sz[u] = 1;
    int maxn = 0;
    for (auto v : e[u]){
        if (v == fa || vis[v])
            continue;
        getrt(v, u);
        sz[u] += sz[v];
        maxn = max(maxn, sz[v]);
    }
    maxn = max(maxn, tot - sz[u]);
    if (maxn < mx){
        mx = maxn;
        rt = u;
    }
}
int ans[N], sum;
int cnt[N], val[N];
int root;
void getdis(int u, int fa, int col){
    sz[u] = 1;
    if (!val[c[u]]){
        sum -= cnt[c[u]];
        col++;
    }
    val[c[u]]++;
    ans[u] += sum + col * sz[root];
    for (auto v : e[u]){
        if (v == fa || vis[v]){
            continue;
        }
        getdis(v, u, col);
        sz[u] += sz[v];
    }
    val[c[u]]--;
    if (!val[c[u]]){
        sum += cnt[c[u]];
    }
}
void calc(int u, int fa){
    if (!val[c[u]]){
        cnt[c[u]] += sz[u];
        sum += sz[u];
    }
    val[c[u]]++;
    for (auto v : e[u]){
        if (v == fa || vis[v])
            continue;
        calc(v, u);
    }
    val[c[u]]--;
}
void clear1(int u, int fa, int col){
    if (!val[c[u]])
        col++;
    val[c[u]]++;
    ans[u] -= col;
    ans[root] += col;
    for (auto v : e[u]){
        if (v == fa || vis[v])
            continue;
        clear1(v, u, col);
    }
    val[c[u]]--;
}
void clear2(int u, int fa){
    cnt[c[u]] = 0;
    for (auto v : e[u]){
        if (v == fa || vis[v])
            continue;
        clear2(v, u);
    }
}
int son[N];
void solve(int u){
    vis[u] = 1;
    int total = 0;
    root = u;
    ans[u]++;
    for (auto v : e[u]){
        if (vis[v])
            continue;
        son[++total] = v;
    }
    sz[u] = 1;
    sum = 1;
    cnt[c[u]] = 1;
    val[c[u]] = 1;
    for (int i = 1; i <= total; i++){
        int v = son[i];
        getdis(v, u, 0);
        calc(v, u);
        sz[u] += sz[v];
        cnt[c[u]] += sz[v];
        sum += sz[v];
    }
    clear2(u, 0);
    sz[u] = 1;
    sum = 1;
    cnt[c[u]] = 1;
    val[c[u]] = 1;
    for (int i = total; i >= 1; i--){
        int v = son[i];
        getdis(v, u, 0);
        calc(v, u);
        sz[u] += sz[v];
        cnt[c[u]] += sz[v];
        sum += sz[v];
    }
    val[c[u]] = 0;
    clear1(u, 0, 0);
    clear2(u, 0);
    for (auto v : e[u]){
        if (vis[v])
            continue;
        tot = sz[v];
        mx = n + 1;
        rt = 0;
        getrt(v, u);
        solve(rt);
    }
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i++){
        cin >> c[i];
    }
    for (int i = 1; i < n; i++){
        int x, y;
        cin >> x >> y;
        e[x].push_back(y);
        e[y].push_back(x);
    }
    rt = 0;
    tot = n;
    mx = n + 1;
    getrt(1, 0);
    solve(rt);
    for (int i = 1; i <= n; i++)
        cout << ans[i] << "\n";
}

LG6626 [省选联考 2020 B 卷] 消息传递

原题链接:[省选联考 2020 B 卷] 消息传递

分析

我的思路并未将我引向点分治,而是引向了猜结论......好像这种类似传染的维护是最多不超过某个瓶颈的。

神秘转化,那不就是与给定点距离等于 k k k 的点的个数?

行,那就可以了,开始he!

问题来了,如何控制某一个点?难道把该点强制作为整棵树的根?那不就是深度问题了?并非可以,因为这样是 O ( m n ) O(mn) O(mn) 的。

行吧,我们看点分治怎么操作。

由上面暴力的思想,我们不妨将其离线。然后直接做就行。

正解

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int T, n, m, tot, rt, sz[N], mx, ans[N], de[N], buc[N], d;
vector<int> e[N];
bool vis[N];
vector<pair<int, int>> ask[N], tmp;
void getrt(int u, int fa){
    sz[u] = 1;
    int maxn = 0;
    for (auto v : e[u]){
        if (v == fa || vis[v])
            continue;
        getrt(v, u);
        sz[u] += sz[v];
        maxn = max(maxn, sz[v]);
    }
    maxn = max(maxn, tot - sz[u]);
    if (maxn < mx){
        mx = maxn;
        rt = u;
    }
}
void getdis(int u, int fa){
    d = max(d, de[u]);
    buc[de[u]]++;
    for (auto o : ask[u]){
        if (o.first >= de[u])
            tmp.push_back({o.first - de[u], o.second});
    }
    for (auto v : e[u]){
        if (v == fa || vis[v])
            continue;
        de[v] = de[u] + 1;
        getdis(v, u);
    }
}
void calc(int u){
    tmp.clear();
    d = 0;
    de[u] = 0;
    getdis(u, 0);
    for (auto o : tmp){
        ans[o.second] += buc[o.first];
    }
    for (int i = 0; i <= d; i++){
        buc[i] = 0;
    }
    for (auto v : e[u]){
        if (vis[v])
            continue;
        de[v] = 1;
        tmp.clear();
        d = 0;
        getdis(v, u);
        for (auto o : tmp)
            ans[o.second] -= buc[o.first];
        for (int i = 0; i <= d; i++)
            buc[i] = 0;
    }
}
void solve(int u){
    calc(u);
    vis[u] = 1;
    for (auto v : e[u]){
        if (vis[v])
            continue;
        tot = sz[v];  
        rt = 0;
        mx = 0x3f3f3f3f; 
        getrt(v, u);
        solve(rt);
    }
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> T;
    while (T--){
        cin >> n >> m;
        for (int i = 1; i <= n; i++){
            vis[i] = 0;
            ask[i].clear();
            e[i].clear();
        }
        for (int i = 1; i <= m; i++){
            ans[i] = 0;
        }
        for (int i = 1; i < n; i++){
            int u, v;
            cin >> u >> v;
            e[u].push_back(v);
            e[v].push_back(u);
        }
        for (int i = 1; i <= m; i++){
            int x, k;
            cin >> x >> k;
            ask[x].push_back({k, i});
        }
        tot = n;
        rt = 0;
        mx = 0x3f3f3f3f;
        getrt(1, 0);
        solve(rt);
        for (int i = 1; i <= m; i++)
            cout << ans[i] << '\n';
    }
}

LG2056 [ZJOI2007] 捉迷藏

原题链接:[ZJOI2007] 捉迷藏

分析

很明显,是一个动态问题。

点分树这个思想🐂🍺啊,他相当于把分治的过程建了树,然后修改变成了树上的 l o g log log,🐂🍺!

那么,对于本题,我们发现,在点分树上,最长距离一定不在同一棵子树,所以,记录每棵子树中黑点到子树根的最长距离即可。那么,对于改成黑点,相当于点分树删除;改成白点,相当于点分树加点。

为了快速得出最值,我们开 p r i o r i t y _ q u e u e priority\_queue priority_queue 即可!

正解

没一个能过 hack 的!****************************************************************************************************************

不写了!

LG6329 【模板】点分树 / 震波

原题链接:【模板】点分树 / 震波

分析

放弃了!

正解

放弃了!

我要清一清洛谷首页!

暴力判断把 P1101 单词方阵 给过了,我以前这么菜吗?

LG5838 [USACO19DEC] Milk Visits G

原题链接:[USACO19DEC] Milk Visits G

分析

我必须给这个树剖鼓掌,他把每种 c c c 的 d f n dfn dfn 扔进一个 b u c buc buc,然后在路径查找时二分,虽然是双 l o g log log,但是我"会"树剖这个 t r i c k trick trick 是天才的。甚至省略了线段树!!!

正解

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int n, m, t[N];
vector<int> e[N], buc[N];
int fa[N], son[N], top[N], sz[N], de[N];
void dfs1(int u, int pa){
	sz[u] = 1;
	fa[u] = pa;
	de[u] = de[pa] + 1;
	for (auto v : e[u]){
		if (v == pa)
			continue;
		dfs1(v, u);
		sz[u] += sz[v];
		if (sz[v] > sz[son[u]])
			son[u] = v;
	}
}
int dfn[N], rk[N], ncnt;
void dfs2(int u, int tp){
	top[u] = tp;
	dfn[u] = ++ncnt;
	rk[ncnt] = u;
	if (son[u])
		dfs2(son[u], tp);
	for (auto v : e[u]){
		if (v == fa[u] || v == son[u])
			continue;
		dfs2(v, v);
	}
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= n; i++){
		cin >> t[i];
	}
	for (int i = 1; i < n; i++){
		int x, y;
		cin >> x >> y;
		e[x].push_back(y);
		e[y].push_back(x);
	}
	dfs1(1, 0);
	dfs2(1, 1);
	for (int i = 1; i <= ncnt; i++){
		buc[t[rk[i]]].push_back(i);
	}
	while (m--){
		int a, b, c;
		cin >> a >> b >> c;
		bool flag = 0;
		while (top[a] != top[b]){
            if (de[top[a]] < de[top[b]])
                swap(a, b);
            auto it = lower_bound(buc[c].begin(), buc[c].end(), dfn[top[a]]);
            if (it != buc[c].end() && *it <= dfn[a]){
                flag = 1;
                break;
            }
            a = fa[top[a]];  
        }
        if (!flag){
            if (dfn[a] > dfn[b])
                swap(a, b);
            auto it = lower_bound(buc[c].begin(), buc[c].end(), dfn[a]);
            if (it != buc[c].end() && *it <= dfn[b])
                flag = 1;
        }
		cout << flag;
	}
}

LG1637 三元上升子序列

原题链接:三元上升子序列

分析

树状数组+动态规划!国庆的时候 Taoran 就讲过,行,作为第一道来写!

正解

cpp 复制代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 30005, M = 5;
int n, a[N], b[N], m, dp[N][M], c[N];
void add(int x, int v){
	for (int i = x; i <= n; i += i & (-i))
		c[i] += v;
}
int query(int x){
	int res = 0;
	for (int i = x; i; i -= i & (-i))
		res += c[i];
	return res;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n;
	for (int i = 1; i <= n; i++){
		cin >> a[i];
		b[i] = a[i];
	}
	sort(b + 1, b + n + 1);
	m = unique(b + 1, b + n + 1) - b - 1;
	for (int i = 1; i <= n; i++){
		dp[i][1] = 1;
		a[i] = lower_bound(b + 1, b + m + 1, a[i]) - b;
	}
	for (int i = 2; i <= 3; i++){
		memset(c, 0, sizeof(c));
		for (int j = 1; j <= n; j++){
			dp[j][i] = query(a[j] - 1);
			add(a[j], dp[j][i - 1]);
		}
	}
	int ans = 0;
	for (int i = 1; i <= n; i++)
		ans += dp[i][3];
	cout << ans;
}

感觉本质还是树状数组对于逆序对求解中的一些思想。

相关推荐
天上飞的粉红小猪2 小时前
线程概念&&控制
linux·开发语言·c++
中屹指纹浏览器2 小时前
基于机器学习的代理 IP 风险动态评估与指纹协同技术
服务器·网络·经验分享·笔记·媒体
饼里个饼2 小时前
AD 8层板笔记——RK3588持续更新中
笔记
ZouZou老师2 小时前
C++设计模式之命令模式:以家具生产为例
c++·设计模式·命令模式
步达硬件2 小时前
【matlab】代码库-一维线性插值
数据结构·算法·matlab
myw0712052 小时前
湘大oj-数码积性练习笔记
c语言·数据结构·笔记·算法
思成不止于此2 小时前
【MySQL 零基础入门】DQL 核心语法(一):学生表基础查询与聚合函数篇
数据库·笔记·学习·mysql
普贤莲花2 小时前
得物面试总结20251210
程序人生·算法·leetcode
hz_zhangrl2 小时前
CCF-GESP 等级考试 2025年9月认证C++五级真题解析
开发语言·数据结构·c++·算法·青少年编程·gesp·2025年9月gesp