[HNOI2013]游走 题解
题目地址:洛谷:【P3232】[HNOI2013]游走 – 洛谷、BZOJ:P …
May all the beauty be blessed.
题目地址:洛谷:【P1131】[ZJOI2007]时态同步 – 洛谷、BZOJ:Problem 1060. — [ZJOI2007]时态同步
小Q在电子工艺实习课上学习焊接电路板。一块电路板由若干个元件组成,我们不妨称之为节点,并将其用数字1,2,3….进行标号。电路板的各个节点由若干不相交的导线相连接,且对于电路板的任何两个节点,都存在且仅存在一条通路(通路指连接两个元件的导线序列)。
在电路板上存在一个特殊的元件称为“激发器”。当激发器工作后,产生一个激励电流,通过导线传向每一个它所连接的节点。而中间节点接收到激励电流后,得到信息,并将该激励电流传向与它连接并且尚未接收到激励电流的节点。最终,激烈电流将到达一些“终止节点”——接收激励电流之后不再转发的节点。
激励电流在导线上的传播是需要花费时间的,对于每条边e,激励电流通过它需要的时间为te,而节点接收到激励电流后的转发可以认为是在瞬间完成的。现在这块电路板要求每一个“终止节点”同时得到激励电路——即保持时态同步。由于当前的构造并不符合时态同步的要求,故需要通过改变连接线的构造。目前小Q有一个道具,使用一次该道具,可以使得激励电流通过某条连接导线的时间增加一个单位。请问小Q最少使用多少次道具才可使得所有的“终止节点”时态同步?
有一棵$n$个点有边权的树,你可以每次给一条边边权+1,求最小的加边权次数,使得根到每个叶子的路径边权和相等。
输入格式:
第一行包含一个正整数N,表示电路板中节点的个数。第二行包含一个整数S,为该电路板的激发器的编号。接下来N-1行,每行三个整数a , b , t。表示该条导线连接节点a与节点b,且激励电流通过这条导线需要t个单位时间。
输出格式:
仅包含一个整数V,为小Q最少使用的道具次数。
输入样例#1:
3 1 1 2 1 1 3 3
输出样例#1:
2
N ≤ 500000,te ≤ 1000000
对于一个子树,我们求出根到叶子的路径和最大值,再把不满最大值的根的出边加满即可,可以证明,如此做得到的就是最优解。这是因为,如果叶子路径和都不同,不在这个子树根加成相同的话,就得在更深的位置加成相同的,操作的边数会大于子树根的出边数,显然不够优。
复杂度$O(n)$。
// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <vector>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1; register char c = fgc();
for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
for(; isdigit(c); c = fgc()) res = res * 10 + c - '0';
return res * neg;
}
inline char readsingle() {
register char c;
while(!isgraph(c = fgc())) {}
return c;
}
const int MAXN = 500005;
int n, s;
struct Edge {
int to, w;
};
std::vector<Edge> gra[MAXN];
int mx[MAXN]; LL ans;
void dfs(int u, int fa) {
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i].to;
if(v == fa) continue;
dfs(v, u);
mx[u] = std::max(mx[u], mx[v] + gra[u][i].w);
}
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i].to;
if(v == fa) continue;
ans += mx[u] - (mx[v] + gra[u][i].w);
}
}
int main() {
n = readint(); s = readint();
for(int i = 1, u, v, w; i < n; i++) {
u = readint(); v = readint(); w = readint();
gra[u].push_back(Edge {v, w});
gra[v].push_back(Edge {u, w});
}
dfs(s, 0);
printf("%lld", ans);
return 0;
}
题目地址:洛谷:【P2495】[SDOI2011]消耗战 – 洛谷、BZOJ:Problem 2286. — [Sdoi2011]消耗战
在一场战争中,战场由n个岛屿和n-1个桥梁组成,保证每两个岛屿间有且仅有一条路径可达。现在,我军已经侦查到敌军的总部在编号为1的岛屿,而且他们已经没有足够多的能源维系战斗,我军胜利在望。已知在其他k个岛屿上有丰富能源,为了防止敌军获取能源,我军的任务是炸毁一些桥梁,使得敌军不能到达任何能源丰富的岛屿。由于不同桥梁的材质和结构不同,所以炸毁不同的桥梁有不同的代价,我军希望在满足目标的同时使得总代价最小。
侦查部门还发现,敌军有一台神秘机器。即使我军切断所有能源之后,他们也可以用那台机器。机器产生的效果不仅仅会修复所有我军炸毁的桥梁,而且会重新随机资源分布(但可以保证的是,资源不会分布到1号岛屿上)。不过侦查部门还发现了这台机器只能够使用m次,所以我们只需要把每次任务完成即可。
给你一棵包含$n$个点的带边权的树,有$m$次询问,每次给你一个大小为$k_i$的点集,你可以花费边权代价切断树上的一些边,使得这个点集中的点与树根$1$不连通,求满足上述条件的最小切边代价。
输入格式:
第一行一个整数n,代表岛屿数量。
接下来n-1行,每行三个整数u,v,w,代表u号岛屿和v号岛屿由一条代价为c的桥梁直接相连,保证1<=u,v<=n且1<=c<=100000。
第n+1行,一个整数m,代表敌方机器能使用的次数。
接下来m行,每行一个整数ki,代表第i次后,有ki个岛屿资源丰富,接下来k个整数h1,h2,…hk,表示资源丰富岛屿的编号。
输出格式:
输出有m行,分别代表每次任务的最小代价。
输入样例#1:
10 1 5 13 1 9 6 2 1 19 2 4 8 2 3 91 5 6 8 7 5 4 7 8 31 10 7 9 3 2 10 6 4 5 7 8 3 3 9 4 6
输出样例#1:
12 32 22
【数据规模和约定】
对于10%的数据,2<=n<=10,1<=m<=5,1<=ki<=n-1
对于20%的数据,2<=n<=100,1<=m<=100,1<=ki<=min(10,n-1)
对于40%的数据,2<=n<=1000,m>=1,sigma(ki)<=500000,1<=ki<=min(15,n-1)
对于100%的数据,2<=n<=250000,m>=1,sigma(ki)<=500000,1<=ki<=n-1
如果只有一次询问,我们可以进行一次DFS处理出$mn[u]$代表$u$点到$1$点链上的最小边,显然,断开它是切断$u$和$1$连接的最优方案。我们用$dp[u]$表示切断$u$子树中所有点与$1$的连接的最小代价,则转移为
$$ dp[u] = \sum_{v \in \mathrm{son}(u)} \min \{ dp[v], mn[v] \} $$
这个转移表示要么切断$v$到$u$的这条边,要么切断其子树内的边。DP初值设置为对于每一个点集里的点,$dp[u]$初始为$mn[u]$,这样在DP求和的时候,如果一个父亲自己也是点集中的点,权值也会被计算进去。显然,如果两个点到$1$路径上的最优边是同一条,那么一定可以在这条最优边处向上转移时只计算一次权值,这是由于$\min$的存在。
然而这个算法是$O(n)$的,也就是说,总复杂度为$O(mn)$,并不能通过本题。
我们考虑建立虚树解决这个问题。虚树是指一棵只包含指定点即它们两两间LCA的辅助用树,这样做可以减少树上点的数量,降低DP的复杂度至$O(k_i \log k_i)$。
具体而言,如果我们处理出树的DFS序,那么对DFS序相邻的两点求LCA,一定可以得到所有点两两的LCA,这是由于,在不同子树内的点的LCA可以在处理到子树DFS序区间端点之间的LCA的时候被求出来。但是,LCA和儿子的边的关系并不明确,直接建边似乎是不明智的选择。我们考虑一种特殊的树的遍历序:欧拉序,它是指DFS遍历时,进入DFS栈的时候将该点加入序列,出栈时也加入一次。如果对于上面的所有点及其LCA的集合,分别插入出入栈点用欧拉序排序,就可以利用这一顺序来模拟DFS了,省去了建边的过程。
模拟DFS的时候,只需要在出栈的时候进行操作,这是因为,此时出栈点$u$子树的DP已经计算完毕,只需要去更新它父亲的DP值即可,而出栈后的栈顶元素就是它的父亲,直接按照上面的方程去转移即可。
使用欧拉序的虚树算法的复杂度是单次$O(n \log n)$的,达到这个复杂度的操作是查询LCA和排序。这里的$n$指的是虚树的节点数,它的规模是$O(k_i \log k_i)$的。
如此,总复杂度终于变得科学了不少。不过本题有点卡常,稍微卡一卡也可以卡进去。
// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <vector>
typedef long long LL;
inline LL min(LL a, LL b) {
return a < b ? a : b;
}
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1; register char c = fgc();
for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
for(; isdigit(c); c = fgc()) res = res * 10 + c - '0';
return res * neg;
}
const int MAXN = 500005;
int n, m;
struct Edge {
int to, w;
};
std::vector<Edge> gra[MAXN];
int anc[MAXN][20], mn[MAXN], dep[MAXN], dfn[MAXN], clk;
void dfs(int u, int fa) {
dfn[u] = ++clk;
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i].to;
if(v == fa) continue;
anc[v][0] = u;
dep[v] = dep[u] + 1;
mn[v] = std::min(mn[u], gra[u][i].w);
dfs(v, u);
}
dfn[u + n] = ++clk;
}
inline void calanc() {
for(int i = 1; i <= 19; i++) {
for(int j = 1; j <= n; j++) {
anc[j][i] = anc[anc[j][i - 1]][i - 1];
}
}
}
inline int querylca(int u, int v) {
if(dep[u] > dep[v]) std::swap(u, v);
int del = dep[v] - dep[u];
for(int i = 19; i >= 0; i--) {
if(del & (1 << i)) v = anc[v][i];
}
if(u == v) return u;
for(int i = 19; i >= 0; i--) {
if(anc[u][i] != anc[v][i]) {
u = anc[u][i];
v = anc[v][i];
}
}
return anc[u][0];
}
inline bool cmp(int a, int b) {
return dfn[a] < dfn[b];
}
LL dp[MAXN];
bool vis[MAXN];
std::vector<int> vec;
int sta[MAXN], stop;
int main() {
n = readint();
for(int i = 1, u, v, w; i < n; i++) {
u = readint(); v = readint(); w = readint();
gra[u].push_back(Edge {v, w});
gra[v].push_back(Edge {u, w});
}
mn[1] = 1e9;
dfs(1, 0);
calanc();
m = readint();
while(m--) {
vec.clear();
int k = readint();
for(int i = 1; i <= k; i++) {
int t = readint();
vis[t] = true; dp[t] = mn[t]; vec.push_back(t);
}
std::sort(vec.begin(), vec.end(), cmp);
for(int i = 1; i < k; i++) {
int l = querylca(vec[i - 1], vec[i]);
if(!vis[l]) {
vis[l] = true;
vec.push_back(l);
}
}
if(!vis[1]) vec.push_back(1);
for(int i = 0; vec[i] <= n; i++) {
vec.push_back(vec[i] + n);
}
std::sort(vec.begin(), vec.end(), cmp);
for(int i = 0; i < vec.size(); i++) {
if(vec[i] <= n) {
sta[stop++] = vec[i];
} else {
int u = sta[--stop];
if(u != 1) {
int fa = sta[stop - 1]; dp[fa] += min(mn[u], dp[u]);
} else {
printf("%lld\n", dp[u]);
}
dp[u] = 0; vis[u] = false;
}
}
}
return 0;
}
题目地址:洛谷:【P3825】[NOI2017]游戏 – 洛谷、BZOJ:Problem 4945. — [Noi2017]游戏
狂野飙车是小 L 最喜欢的游戏。与其他业余玩家不同的是,小 L 在玩游戏之余,还精于研究游戏的设计,因此他有着与众不同的游戏策略。
小 L 计划进行 n 场游戏,每场游戏使用一张地图,小 L 会选择一辆车在该地图上完成游戏。
小 L 的赛车有三辆,分别用大写字母A、B、C表示。地图一共有四种,分别用小写字母x、a、b、c表示。其中,赛车A不适合在地图a上使用,赛车B不适合在地图b上使用,赛车C不适合在地图c上使用,而地图x则适合所有赛车参加。适合所有赛车参加的地图并不多见,最多只会有d张。
n 场游戏的地图可以用一个小写字母组成的字符串描述。例如:S=xaabxcbc表示小 L 计划进行 8 场游戏,其中第 1 场和第 5 场的地图类型是x,适合所有赛车,第 2 场和第 3 场的地图是a,不适合赛车A,第 4 场和第 7 场的地图是b,不适合赛车B,第 6 场和第 8 场的地图是c,不适合赛车C。
小 L 对游戏有一些特殊的要求,这些要求可以用四元组 (i,hi,j,hj) 来描述,表示若在第 i 场使用型号为 hi 的车子,则第 j 场游戏要使用型号为 hj 的车子。
你能帮小 L 选择每场游戏使用的赛车吗?如果有多种方案,输出任意一种方案。如果无解,输出 “-1’’(不含双引号)。
你正在玩一个赛车游戏,一共有3种车型,每一场有不适合的车型,有的比赛没有不适合的车型,这种比赛的数量$\leq 8$。有一些限制条件,表示某场比赛如果使用某种车型,那么另一场比赛必须使用另种车型。求是否存在每场比赛采用的车型的安排符合上述所有条件。
输入格式:
输入第一行包含两个非负整数 n,d 。
输入第二行为一个字符串 S 。 n,d,S 的含义见题目描述,其中 S 包含 n 个字符,且其中恰好 d 个为小写字母 x 。
输入第三行为一个正整数 m ,表示有 m 条用车规则。接下来 m 行,每行包含一个四元组 i,hi,j,hj ,其中 i,j 为整数, hi,hj 为字符a、b或c,含义见题目描述。
输出格式:
输出一行。
若无解输出 “-1’’(不含双引号)。
若有解,则包含一个长度为 n 的仅包含大写字母A、B、C的字符串,表示小 L 在这 n 场游戏中如何安排赛车的使用。如果存在多组解,输出其中任意一组即可。
输入样例#1:
3 1 xcc 1 1 A 2 B
输出样例#1:
ABA
本题可以使用2-SAT解决。
如果不存在可以使用三种的游戏,那么我们按如下方式建图。
跑完2-SAT输出方案就好。对于可以使用三种车型的游戏,则枚举它不能使用某种车型,就转变为类似普通游戏的模型,枚举到一个方案判断一次即可。
// Code by KSkun, 2018/7
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)
? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1; register char c = fgc();
for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
return res * neg;
}
inline char readsingle() {
char c;
while(!isgraph(c = fgc())) {}
return c;
}
const int MAXN = 100005;
int n, m;
char s[MAXN], s1[MAXN];
struct Info {
int i, j;
char hi, hj;
} infos[MAXN];
struct Edge {
int to, nxt;
} gra[MAXN << 1], gran[MAXN << 1];
int head[MAXN], headn[MAXN], tot, totn;
inline void addedge(int u, int v) {
gra[tot] = Edge {v, head[u]}; head[u] = tot++;
}
inline void addedgen(int u, int v) {
gran[totn] = Edge {v, headn[u]}; headn[u] = totn++;
}
inline void reset() {
tot = totn = 0;
memset(head, -1, sizeof(head));
memset(headn, -1, sizeof(headn));
}
int dfn[MAXN], low[MAXN], clk, sno[MAXN], scc, op[MAXN];
std::stack<int> sta;
bool insta[MAXN];
void tarjan(int u) {
dfn[u] = low[u] = ++clk;
sta.push(u); insta[u] = true;
for(int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to;
if(!dfn[v]) {
tarjan(v);
low[u] = std::min(low[u], low[v]);
} else if(insta[v]) {
low[u] = std::min(low[u], dfn[v]);
}
}
if(low[u] == dfn[u]) {
scc++;
int p;
do {
p = sta.top(); sta.pop();
insta[p] = false;
sno[p] = scc;
} while(p != u);
}
}
int deg[MAXN];
std::queue<int> que;
int mark[MAXN];
void dfs_mark(int u) {
if(mark[u]) return;
mark[u] = -1;
for(int i = headn[u]; ~i; i = gran[i].nxt) {
int v = gran[i].to;
dfs_mark(v);
}
}
inline void toposort() {
for(int i = 1; i <= scc; i++) {
if(!deg[i]) que.push(i);
}
while(!que.empty()) {
int u = que.front(); que.pop();
if(mark[u]) continue;
mark[u] = 1;
dfs_mark(op[u]);
for(int i = headn[u]; ~i; i = gran[i].nxt) {
int v = gran[i].to;
if(!--deg[v]) que.push(v);
}
}
}
inline int id(int x, char y) {
if(s1[x] == 'a') {
if(y == 'B') return x;
else return x + n;
} else {
if(y == 'A') return x;
else return x + n;
}
}
inline char choice(int x) {
int xx = x > n ? x - n : x;
if(s1[xx] != 'a' && id(xx, 'A') == x) return 'A';
else if(s1[xx] != 'b' && id(xx, 'B') == x) return 'B';
else if(s1[xx] != 'c' && id(xx, 'C') == x) return 'C';
}
inline int oppo(int x) {
if(x > n) return x - n;
else return x + n;
}
inline bool work() {
reset();
for(int i = 1; i <= m; i++) {
Info x = infos[i];
if(s1[x.i] == tolower(x.hi)) {
continue;
} else if(s1[x.j] == tolower(x.hj)) {
addedge(id(x.i, x.hi), oppo(id(x.i, x.hi)));
} else {
addedge(id(x.i, x.hi), id(x.j, x.hj));
addedge(oppo(id(x.j, x.hj)), oppo(id(x.i, x.hi)));
}
}
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low));
for(int i = 1; i <= n * 2; i++) {
if(!dfn[i]) tarjan(i);
}
for(int i = 1; i <= n; i++) {
if(sno[i] == sno[i + n]) return false;
op[sno[i]] = sno[i + n];
op[sno[i + n]] = sno[i];
}
memset(deg, 0, sizeof(deg));
for(int u = 1; u <= n * 2; u++) {
for(int i = head[u]; ~i; i = gra[i].nxt) {
int v = gra[i].to;
if(sno[u] != sno[v]) {
addedgen(sno[v], sno[u]);
deg[sno[u]]++;
}
}
}
toposort();
for(int i = 1; i <= n; i++) {
if(mark[sno[i]] == 1) putchar(choice(i));
else putchar(choice(i + n));
}
return true;
}
bool dfs_s(int step) {
if(step > n) return work();
if(s[step] != 'x') {
s1[step] = s[step];
if(dfs_s(step + 1)) return true;
} else {
s1[step] = 'a';
if(dfs_s(step + 1)) return true;
s1[step] = 'b';
if(dfs_s(step + 1)) return true;
s1[step] = 'c';
if(dfs_s(step + 1)) return true;
}
}
int main() {
n = readint(); readint();
for(int i = 1; i <= n; i++) {
s[i] = readsingle();
}
m = readint();
for(int i = 1; i <= m; i++) {
infos[i].i = readint();
infos[i].hi = readsingle();
infos[i].j = readint();
infos[i].hj = readsingle();
}
if(!dfs_s(1)) puts("-1");
return 0;
}