2026-10-07 hetao1733837 的刷题记录

CF1187E Tree Painting

原题链接:E. Tree Painting

题意

给你一棵树,初始所有节点为白色。每次操作选择一个与已经染成黑色的点相邻的点(第一次任选一个)染黑,获得的权值是选择点未所在连通块未被染黑的连通块的大小。求最大的权值。

分析

这个真的换根了吗?

哦,那么,理论上就是我们需要知道最开始的点是哪个。

那么,我们设 d p u dp_{u} dpu 表示我们第一次染 u u u 的最大值。

我们发现,每次染色,都会使拿个连通块大小 − 1 -1 −1。

我为什么感觉这个题可以直接贪心?

静静地思考一下。

不是,我真觉得这个是个贪......转移是贪的。

我们发现,由于这是一个树,所以,我们每次的选择都是相当于这个连通块的大小 − 1 -1 −1,所以,构成了一个等差数列(这里是错误的)。

我们需要明确的就是每次的每个节点的子树大小......我在说什么......所以,换根的意义就是这个,我们需要更新大小。然后你做完了。

天啊,这个题解交上去能过审握持......

怎么写代码呢?

我们明确一下流程
#mermaid-svg-kHGyBQL2120KCid1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kHGyBQL2120KCid1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kHGyBQL2120KCid1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kHGyBQL2120KCid1 .error-icon{fill:#552222;}#mermaid-svg-kHGyBQL2120KCid1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kHGyBQL2120KCid1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kHGyBQL2120KCid1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kHGyBQL2120KCid1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kHGyBQL2120KCid1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kHGyBQL2120KCid1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kHGyBQL2120KCid1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kHGyBQL2120KCid1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kHGyBQL2120KCid1 .marker.cross{stroke:#333333;}#mermaid-svg-kHGyBQL2120KCid1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kHGyBQL2120KCid1 p{margin:0;}#mermaid-svg-kHGyBQL2120KCid1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kHGyBQL2120KCid1 .cluster-label text{fill:#333;}#mermaid-svg-kHGyBQL2120KCid1 .cluster-label span{color:#333;}#mermaid-svg-kHGyBQL2120KCid1 .cluster-label span p{background-color:transparent;}#mermaid-svg-kHGyBQL2120KCid1 .label text,#mermaid-svg-kHGyBQL2120KCid1 span{fill:#333;color:#333;}#mermaid-svg-kHGyBQL2120KCid1 .node rect,#mermaid-svg-kHGyBQL2120KCid1 .node circle,#mermaid-svg-kHGyBQL2120KCid1 .node ellipse,#mermaid-svg-kHGyBQL2120KCid1 .node polygon,#mermaid-svg-kHGyBQL2120KCid1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kHGyBQL2120KCid1 .rough-node .label text,#mermaid-svg-kHGyBQL2120KCid1 .node .label text,#mermaid-svg-kHGyBQL2120KCid1 .image-shape .label,#mermaid-svg-kHGyBQL2120KCid1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-kHGyBQL2120KCid1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kHGyBQL2120KCid1 .rough-node .label,#mermaid-svg-kHGyBQL2120KCid1 .node .label,#mermaid-svg-kHGyBQL2120KCid1 .image-shape .label,#mermaid-svg-kHGyBQL2120KCid1 .icon-shape .label{text-align:center;}#mermaid-svg-kHGyBQL2120KCid1 .node.clickable{cursor:pointer;}#mermaid-svg-kHGyBQL2120KCid1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kHGyBQL2120KCid1 .arrowheadPath{fill:#333333;}#mermaid-svg-kHGyBQL2120KCid1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kHGyBQL2120KCid1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kHGyBQL2120KCid1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kHGyBQL2120KCid1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kHGyBQL2120KCid1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kHGyBQL2120KCid1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kHGyBQL2120KCid1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kHGyBQL2120KCid1 .cluster text{fill:#333;}#mermaid-svg-kHGyBQL2120KCid1 .cluster span{color:#333;}#mermaid-svg-kHGyBQL2120KCid1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-kHGyBQL2120KCid1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kHGyBQL2120KCid1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-kHGyBQL2120KCid1 .icon-shape,#mermaid-svg-kHGyBQL2120KCid1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kHGyBQL2120KCid1 .icon-shape p,#mermaid-svg-kHGyBQL2120KCid1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kHGyBQL2120KCid1 .icon-shape .label rect,#mermaid-svg-kHGyBQL2120KCid1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kHGyBQL2120KCid1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kHGyBQL2120KCid1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kHGyBQL2120KCid1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} build graph
dfs
change root
get max
print

题解

我们设 d p u dp_{u} dpu 表示最开始选 u u u 的收益,如果说每次都单独求,那么,复杂度是 O ( n 2 ) O(n^2) O(n2) 的,显然不能过。

那么,由于每个点都会作为"根节点",不妨考虑换根 DP。

我们发现每个点的权值就是:

∑ v ∈ s o n u s z v \sum\limits_{v\in son_u}^{}{sz_{v}} v∈sonu∑szv

那么,我们可以进行非常轻易的转移。

我们发现,在换根的时候,对于 v v v 的子树,他的所有点的贡献都会 − 1 -1 −1,因为我们选择了 v v v;而对于 v v v 子树之外的点,他们的连通块都更大了,因为之前的 u = f a v u=fa_v u=fav 不选了,所以,剩下的的都 + 1 +1 +1,所以,我们有了转移方程:

d p v = d p u + ( n − s z v ) − s z v dp_{v} = dp_{u}+(n-sz_v)-sz_v dpv=dpu+(n−szv)−szv

那么,你做完了!

正解

cpp 复制代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 200005;
int n;
vector<int> e[N];
int sz[N], dp[N];
int ans;
void dfs1(int u, int fa){
    sz[u] = 1;
    dp[u] = 0;
    for (int v : e[u]){
        if (v == fa) 
            continue;
        dfs1(v, u);
        sz[u] += sz[v];
        dp[u] += dp[v];
    }
    dp[u] += sz[u];
}
void dfs2(int u, int fa){
    ans = max(ans, dp[u]);
    for (int v : e[u]){
        if (v == fa) 
            continue;
        dp[v] = dp[u] + n - 2 * sz[v];
        dfs2(v, u);
    }
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> n;
    for (int i = 1, u, v; i < n; i++){
        cin >> u >> v;
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs1(1, 0);
    ans = dp[1];
    dfs2(1, 0);
    cout << ans;
    return 0;
}

但是,问题来了,为什么 l z lz lz 用我的代码造的数据我第一发 RE 了?

LGP10962 Computer

原题链接:Computer

分析

这个板完了!

别急,似乎并非啊......

难道说我要记录一个极长链和一个次长链然后 DP 吗?好像还真是......哦不不不,这无疑是令人难绷得......

难道说我在直径上吗?喔......居然是真的!

那这个不止绿啊......


这个题竟然有两个方法!

方法一

我们知道,在一个点到树上的最远距离一定是直径的端点,那么,这个就可以直接做了!但是呢,这里有一点比较复杂就是说,因为这个树是带权的,所以说,我们还需要树上差分 + LCA 之类的东西来求距离,不过复杂度倒是可以接受......其实也不用,直接 dfs 两边预处理就做完了!

方法二

我们跑二遍 dfs,记录 up 和 down 表示向上和向下的最长长度,向下还要再记录一个次长。怎么感觉本质上和直径的那么像呢?这个并不好玩......


开到真的好题了!

正解

cpp 复制代码
//Method 1
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 100005;
int n;
vector<pair<int, int>> e[N];
int d1[N], d2[N];
int s, t;
int mx, pos;
void dfs1(int u, int fa, int dis){
    d1[u] = dis;
    if (dis > mx){
        mx = dis;
        pos = u;
    }
    for (auto tmp : e[u]){
        if (tmp.first == fa)
            continue;
        dfs1(tmp.first, u, dis + tmp.second);
    }
}
void dfs2(int u, int fa, int dis){
    d2[u] = dis;
    if (dis > mx){
        mx = dis;
        pos = u;
    }
    for (auto tmp : e[u]){
        if (tmp.first == fa)
            continue;
        dfs2(tmp.first, u, dis + tmp.second);
    }
}
void init(){
    for (int i = 1; i <= n; i++){
        e[i].clear();
    }
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    while (cin >> n){
        init();
        for (int i = 2, v, w; i <= n; i++){
            cin >> v >> w;
            e[i].push_back({v, w});
            e[v].push_back({i, w});
        }
        mx = 0xc0c0c0c0c0c0c0c0;
        dfs1(1, 0, 0);
        s = pos;
        mx = 0xc0c0c0c0c0c0c0c0;
        dfs1(s, 0, 0);
        t = pos;
        mx = 0xc0c0c0c0c0c0c0c0;
        dfs2(t, 0, 0);
        for (int i = 1; i <= n; i++){
            cout << max(d1[i], d2[i]) << '\n';
        }
    }
}
cpp 复制代码
\\Method 2
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 10005;
int n;
vector<pair<int, int>> e[N];
int d[N], dd[N];
int dp[N];
int son[N];
void dfs1(int u, int fa){
	for (auto tmp : e[u]){
		if (tmp.first == fa)
			continue;
		dfs1(tmp.first, u);
		if (d[u] < d[tmp.first] + tmp.second){
			dd[u] = d[u];
			d[u] = d[tmp.first] + tmp.second;
			son[u] = tmp.first;
		}
		else if (dd[u] < d[tmp.first] + tmp.second){
			dd[u] = d[tmp.first] + tmp.second;
		}
	}
}
void dfs2(int u, int fa){
	for (auto tmp : e[u]){
		if (tmp.first == fa)
			continue;
		if (son[u] != tmp.first)
			dp[tmp.first] = max(d[u], dp[u]) + tmp.second;
		else
			dp[tmp.first] = max(dd[u], dp[u]) + tmp.second;
		dfs2(tmp.first, u); 
	}
}
void init(){
	memset(dp, 0, sizeof(dp));
	memset(d, 0, sizeof(d));
	memset(dd, 0, sizeof(dd));
    for (int i = 1; i <= n; i++){
        e[i].clear();
    }
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	while (cin >> n){
		init();
		for (int i = 2, v, w; i <= n; i++){
			cin >> v >> w;
			e[i].push_back({v, w});
			e[v].push_back({i, w});
		}
		dfs1(1, 0);
		dfs2(1, 0);
		for (int i = 1; i <= n; i++){
			cout << max(d[i], dp[i]) << '\n';
		}
	}
}

AT_arc231_d ARC231D Choose Your Role

原题链接:D - Choose Your Role

分析

我们只需要维护这个对称性就可以了......

为什么呢?因为后手并不自由,这是什么理由呢?

也就是相当于下模仿棋......所以,后手必胜......

这都是啥啊😭


翻译了一下官方题解:

考虑给石子染色。假设堆 (i) 对于颜色 (1,2,\cdots,N) 中除颜色 (i) 外的每种颜色,都恰好有一颗该颜色的石子,也就是共有 (N-1) 颗石子。

在初始状态,以下对称性成立:

对于 (i \ne j),堆 (i) 中是否有颜色 (j) 的石子,与堆 (j) 中是否有颜色 (i) 的石子相同。

当先手从对称性成立的状态走一步后,对称性不再成立。作为后手,我们的目标是让自己每走一步之后,对称性都重新成立。

假设先手从堆 (i) 中移除了颜色为 (c_1,c_2,\cdots,c_j) 的石子。如果在先手移动之前上述对称性保持成立,那么我们可以从堆 (c_1,c_2,\cdots,c_j) 中各移除一颗颜色为 (i) 的石子。并且在这步之后,对称性再次成立。

在最终没有石子剩余的状态下,对称性成立。因此,通过按上述方式移动,后手可以获胜。

我们可以直接实现这个策略。通过用集合或类似数据结构来管理每堆石子的颜色,可以例如在 (O(N^2 \log N)) 时间内解决。

为啥我的过不了?


就是,你发现 n × ( n − 1 ) n\times (n-1) n×(n−1) 必然是一个偶数,而每次先手最多取 n − 1 n-1 n−1,我们还可以在剩下的取这么多,所以,就解决了!

维护对称性显然是正确的。

正解

cpp 复制代码
#include <iostream>
#include <set>
#include <vector>
using namespace std;
int main(){
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  int n, k;
  cin >> n >> k;
  cout << "Second" << endl;
  vector<set<int>> stone(n);
  for(int i = 0; i < n; i++){
    for(int j = 0; j < n; j++){
      if(i == j) continue;
      stone[i].insert(j);
    }
  }
  while(true){
    int s, t;
    cin >> s >> t;
    if(s == 0){
      return 0;
    }
    s--;
    cout << t << endl;
    for(int i = 0; i < t; i++){
      int c = *stone[s].begin();
      stone[s].erase(c);
      stone[c].erase(s);
      cout << c + 1 << " ";
    }
    cout << endl;
  }
}

难道交互库不认识 '\n'?

相关推荐
Nil2083 小时前
leetcode 279完全平方数
算法·leetcode·职场和发展
longlongzihan3 小时前
前缀积与后缀积 —— LeetCode 238. 除了自身以外数组的乘积
开发语言·c++·leetcode
All for pursuit.3 小时前
【动态规划-1】121.买卖股票的最佳时机
数据结构·c++·算法·leetcode·动态规划
名不经传的养虾人3 小时前
从0到1:企业级AI项目迭代日记 Vol.117|学会忘记,学会证明,学会设边界
大数据·人工智能·算法·企业ai·多agent协作
水饺编程4 小时前
第1章,[Win32 章节]:API 及 内存管理模式
c语言·c++·windows·visual studio
weixin_307779134 小时前
C++代码实现MATLAB中的cvpartition函数功能
开发语言·c++·算法·matlab
(Charon)4 小时前
【C++面试】线程同步机制:互斥锁、条件变量、原子操作与读写锁
开发语言·c++·算法·面试
七牛云行业应用4 小时前
刚刚!GPT-6.1 Sol Ultrafast 上线:速度、价格、API 接入全指南
算法
Omics Pro4 小时前
研究证实AI虚拟细胞可用于药物靶点发现
数据库·人工智能·算法·机器学习·自然语言处理