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'?