月度归档: 2018 年 7 月

[ZJOI2007]时态同步 题解

[ZJOI2007]时态同步 题解

题目地址:洛谷:【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;
}
[SDOI2011]消耗战 题解

[SDOI2011]消耗战 题解

题目地址:洛谷:【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;
}
[NOI2017]游戏 题解

[NOI2017]游戏 题解

题目地址:洛谷:【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

说明

game noi17 - [NOI2017]游戏 题解

题解

本题可以使用2-SAT解决。
如果不存在可以使用三种的游戏,那么我们按如下方式建图。

  1. 如果$h_i$是$i$不能用的车型,则不作任何操作;
  2. 如果不满足1,且$h_j$是$j$不能用的车型,则标注$(i, h_i)$为“不可能选择”的方案,即连接它与$i$的另外一个选项;
  3. 如果不满足1和2,则有两种选择,即选$(i, h_i)$必须选$(j, h_j)$,选$j$的另外一个选项必须选$i$的另外一个选项,如此建边即可。

跑完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;
}