前言:最近做了好多树上的类似dp的东西,今天是整理正好做个总结
共同点:都是一种可以说是贪心的思想,能在子树中解决的都在子树中解决,解决不了的交给该子树的父节点的那颗子树中解决
虎
题目概述:
这题太虎了,所以没有背景。
给你一棵树,边有黑白两种颜色,你每次可以选择两个点,把这两个点之间的唯一简单路径上的所有边颜色取反,某些边要求最终颜色必须是黑色,还有些边没有要求,问最少操作多少次能达到目的
第一行一个整数n,代表点数
接下来 (n - 1) 行,每行三个数 (x, y, z),代表点 (i) 与 (x) 之间有一条边,若 (y) 为 0 代表初始为白色,否则为黑色,若 (z) 为 0 代表不对最终颜色做要求,否则代表要求为黑色。
思路
很显然的对于一个子树跟节点而言,与它相连的所有的边(通向子节点),要是不做要求,那么有可能会被翻转,要是要求变黑并且原来是白色的,那么一定不会被翻转,如果一开始就是黑色的,那么一定不翻转。
考虑哪些情况在子树中找不到配对的可以上传父节点:
1:该子树与父节点的连边原本就需要翻转
2:该子树与父节点的连边可以翻转也可以不翻转(不做要求)
然后我们就dfs解决就好了
代码实现
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 1e6 + 50;
inline int read () {
int x = 0, f = 1; char ch = getchar();
for (;!isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
return x * f;
}
int n;
struct Edge {
int to, next, judge, special;
} edge[maxn << 1];
int tot, head[maxn];
void addedge (int a, int b, int col, int spc) {
edge[++tot].to = b;
edge[tot].next = head[a];
head[a] = tot;
if (col == 1) {
edge[tot].judge = 1;
} else if (spc == 1) {
edge[tot].special = 1;
}
}
int siz[maxn];
int cnt;
int fa[maxn];
void dfs (int u, int f, int opt, int judge) {
for (register int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (v == f) continue;
fa[v] = u;
if (edge[i].judge == 1) {
siz[u]++;
}
if (edge[i].judge == 1) {
dfs (v, u, 1, 0);
} else if (edge[i].special == 1) {
dfs (v, u, 0, 1);
} else {
dfs (v, u, 0, 0);
}
}
cnt += siz[u] / 2;
siz[u] %= 2;
if (siz[u] == 0) {
} else if (siz[u] == 1 && opt == 1) {
} else if (opt == 0 && judge == 1){
siz[fa[u]]++;
} else if (siz[u] == 1 && opt == 0 && judge == 0) {
cnt++;
}
}
int main () {
n = read();
int x, y, z;
for (register int i = 2; i <= n; i++) {
x = read(), y = read(), z = read();
if (z == 0) {
addedge (i, x, 0, 1);
addedge (x, i, 0, 1);
} else if (y == 0 && z != 0) {
addedge (i, x, 1, 0);
addedge (x, i, 1, 0);
} else {
addedge (i, x, 0, 0);
addedge (x, i, 0, 0);
}
}
dfs (1, 0, 0, 0);
cout<<cnt<<endl;
return 0;
}
Graph
给定一张 (n) 个点, m条边的无向图,每条边连接两个顶点,保证无重边自环,不保证联通。
你想在这张图上进行若干次旅游,每次旅游可以任选一个点作为起点,再走到一个与 (x) 直接有边相连的点 (y) , 再走到一个与 (y) 有边相连的点 (z) 并结束本次旅游。
作为一个旅行爱好者,你不希望任意一条边超过一次, 注意一条边不能正向走一次,反向走一次,点可以经过多次,满足该情况你希望尽可能多次的旅游,请计算出最多的旅行次数,并任意输出一种方案。
思路
很显然对于一个联通块来说答案就是边数 % 3, 你就可以发现一种最有的贪心策略,可以在字数中找到配对的就在子树中解决,找不到的就传到父节点中,在父节点找配对的边,注意要优先解决这种在子树中找不到配对的边,因为如果上传父节点之后还是没有配对,那么这条边就不可以再次上传了,并且这种边一定可以在上传父节点时找到配对。
用 (vector) 随便维护一下就好了
代码实现
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn = 1e5 + 50;
inline int read () {
int x = 0, f = 1; char ch = getchar();
for (;!isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
return x * f;
}
int n, m;
struct Edge {
int from, to, next;
bool judge;
} edge[maxn << 2];
int tot, head[maxn];
void addedge (int a, int b) {
edge[++tot].to = b;
edge[tot].from = a;
edge[tot].next = head[a];
head[a] = tot;
}
bool vis[maxn];
int cnt;
struct Node {
int fro, midd, too;
}node[maxn];
vector<pair<int, int> > vec[maxn];
int fa[maxn];
void dfs (int u) {
vis[u] = true;
for (register int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (edge[i].judge == true) continue;
edge[i].judge = true;
if (edge[i + 1].from == edge[i].to && edge[i + 1].to == edge[i].from) {
edge[i + 1].judge = true;
} else {
edge[i - 1].judge = true;
}
if (vis[v]) {
vec[u].push_back(make_pair(u, v));
continue;
}
fa[v] = u;
dfs (v);
vec[u].push_back(make_pair(u, v));
}
int siz = vec[u].size();
for (register int i = 0; i < vec[u].size(); i++) {
int x = vec[u][i].first, y = vec[u][i].second;
if (x != u && y != u){
int xx = vec[u][i + 1].first, yy = vec[u][i + 1].second;
if (xx == x) {
node[++cnt] = (Node) {yy, xx, y};
} else if (yy == x) {
node[++cnt] = (Node) {xx, yy, y};
} else if (xx == y){
node[++cnt] = (Node) {x, xx, yy};
} else {
node[++cnt] = (Node) {x, yy, xx};
}
i++;
} else if (i + 1 < siz) {
int xx = vec[u][i + 1].first, yy = vec[u][i + 1].second;
if (xx == u) {
node[++cnt] = (Node) {y, x, yy};
i++;
}
else {
vec[u].push_back(make_pair(x, y));
siz++;
}
} else {
vec[fa[u]].push_back(make_pair(x, y));
}
}
}
int main () {
n = read(), m = read();
int x, y;
for (register int i = 1; i <= m; i++) {
x = read(), y = read();
addedge (x, y), addedge (y, x);
}
for (register int i = 1; i <= n; i++) {
if (vis[i] == false) dfs(i);
}
cout<<cnt<<endl;
for (register int i = 1; i <= cnt; i++) {
printf("%d %d %d
", node[i].fro, node[i].midd, node[i].too);
}
return 0;
}
凉宫春日的消失
在观察凉宫和你相处的过程中, (Yoki) 产生了一个叫做爱的 (bugfeature) ,将自己变成了一个没有特殊能力的普通女孩并和你相遇。但你仍然不能扔下凉宫,准备利用 (Yoki) 留下的紧急逃脱程序回到原来的世界。这个紧急逃脱程序的关键就是将线索配对。
为了简化问题,我们将可能的线索间的关系用一棵n个点的树表示,两个线索的距离定义为其在树上唯一最短路径的长度。因为你不知道具体的线索是什么,你需要进行 (q) 次尝试,每次尝试都会选中一个大小为偶数的线索集合 (V) ,你需要将线索两两配对,使得配对线索的距离之和不超过 (n) 。如果这样的方案不存在,输出 (No) 。
思路
一眼看到选关键点,显然可以用虚树搞,并且很显然有一个性质,该条件只要关键点数是偶数,那么一定存在方案。一个类似贪心的思想,可以在一颗子树中找到配对的就在一颗子树中解决,并且一颗子树中最多只会有一个点没有找到配对,那么把当前点扔到父节点中找配对,并且这个点选最靠上的
代码实现
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int maxn = 2e5 + 50;
inline int read () {
int x = 0, f = 1; char ch = getchar();
for (;!isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
return x * f;
}
int n;
struct Edge {
int from, to, next;
} edge[maxn << 1];
int tot, head[maxn];
inline void addedge (int a, int b) {
edge[++tot].to = b;
edge[tot].from = a;
edge[tot].next = head[a];
head[a] = tot;
}
deque<int> que[maxn];
bool col[maxn];
int f[maxn];
int son[maxn], siz[maxn], deep[maxn];
void dfs1 (int u) {
siz[u] = 1;
for (register int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (v == f[u]) continue;
f[v] = u;
deep[v] = deep[u] + 1;
dfs1 (v);
siz[u] += siz[v];
if (siz[v] > siz[son[u]]) son[u] = v;
}
}
int dfn[maxn], dfn_clock;
int top[maxn];
void dfs2 (int u) {
dfn[u] = ++dfn_clock;
if (son[u]) {
top[son[u]] = top[u];
dfs2 (son[u]);
for (register int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (v != f[u] && v != son[u]) {
top[v] = v;
dfs2 (v);
}
}
}
}
inline int lca (int x, int y) {
while (top[x] != top[y]) {
if (deep[top[x]] > deep[top[y]]) {
x = f[top[x]];
} else {
y = f[top[y]];
}
}
if (deep[x] < deep[y]) return x;
return y;
}
int tp;
int stk[maxn];
inline void ins(int x)
{
if (tp == 0)
{
stk[tp = 1] = x;
return;
}
int ance = lca(stk[tp], x);
while ((tp > 1) && (deep[ance] < deep[stk[tp - 1]]))
{
addedge(stk[tp - 1], stk[tp]);
--tp;
}
if (deep[ance] < deep[stk[tp]]) addedge(ance, stk[tp--]);
if ((!tp) || (stk[tp] != ance)) stk[++tp] = ance;
stk[++tp] = x;
}
int a[maxn];
inline void dfs (int u) {
stk[++tp] = u;
for (register int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
dfs(v);
if (!que[v].empty()) {
int a = que[v].front();
que[v].pop_front();
que[u].push_back(a);
}
}
if (col[u]) que[u].push_front(u);
while (!que[u].empty()) {
int a = que[u].back();
que[u].pop_back();
if (!que[u].empty()) {
int b = que[u].back();
que[u].pop_back();
printf("%d %d
", a, b);
} else {
que[u].push_back(a);
break;
}
}
}
bool cmp (int a, int b) {
return dfn[a] < dfn[b];
}
int main () {
n = read();
int x, y;
for (register int i = 1; i < n; i++) {
x = read(), y = read();
addedge (x, y), addedge (y, x);
}
int s;
dfs1 (1);
dfs2 (1);
memset (head, 0, sizeof head);
tot = 0;
while (1) {
s = read();
if (s == 0) return 0;
for (register int i = 1; i <= s; i += 1) {
a[i] = read();
col[a[i]] = true;
}
printf("Yes
");
sort (a + 1, a + 1 + s, cmp);
if (a[1] != 1) {
stk[tp = 1] = 1;
}
for (register int i = 1; i <= s; i++) {
ins (a[i]);
}
if (tp) {
while (--tp) {
addedge (stk[tp], stk[tp + 1]);
}
}
tp = 0;
dfs (1);
for (register int i = 1; i <= tp + 1; i++) {
head[stk[i]] = 0;
col[stk[i]] = false;
}
tp = 0;
tot = 0;
}
return 0;
}
完结散花(QwQ), (csp rp++)