[SDOI2014]旅行 题解
题目地址:洛谷:【P3313】[SDOI2014]旅行 – 洛谷、BZOJ:P …
May all the beauty be blessed.
题目地址:洛谷:【P3292】[SCOI2016]幸运数字 – 洛谷、BZOJ:Problem 4568. — [Scoi2016]幸运数字
A 国共有 n 座城市,这些城市由 n-1 条道路相连,使得任意两座城市可以互达,且路径唯一。每座城市都有一个幸运数字,以纪念碑的形式矗立在这座城市的正中心,作为城市的象征。
一些旅行者希望游览 A 国。旅行者计划乘飞机降落在 x 号城市,沿着 x 号城市到 y 号城市之间那条唯一的路径游览,最终从 y 城市起飞离开 A 国。在经过每一座城市时,游览者就会有机会与这座城市的幸运数字拍照,从而将这份幸运保存到自己身上。然而,幸运是不能简单叠加的,这一点游览者也十分清楚。他们迷信着幸运数字是以异或的方式保留在自己身上的。
例如,游览者拍了 3 张照片,幸运值分别是 5,7,11,那么最终保留在自己身上的幸运值就是 9(5 xor 7 xor 11)。
有些聪明的游览者发现,只要选择性地进行拍照,便能获得更大的幸运值。例如在上述三个幸运值中,只选择 5 和 11 ,可以保留的幸运值为 14 。现在,一些游览者找到了聪明的你,希望你帮他们计算出在他们的行程安排中可以保留的最大幸运值是多少。
给一棵树,每个点有权值,求两点间路径上所有点的权值取若干个求异或和的最大值。
输入格式:
第一行包含 2 个正整数 n ,q,分别表示城市的数量和旅行者数量。
第二行包含 n 个非负整数,其中第 i 个整数 Gi 表示 i 号城市的幸运值。
随后 n-1 行,每行包含两个正整数 x ,y,表示 x 号城市和 y 号城市之间有一条道路相连。
随后 q 行,每行包含两个正整数 x ,y,表示这名旅行者的旅行计划是从 x 号城市到 y 号城市。N<=20000,Q<=200000,Gi<=2^60
输出格式:
输出需要包含 q 行,每行包含 1 个非负整数,表示这名旅行者可以保留的最大幸运值。
输入样例#1:
4 2 11 5 7 9 1 2 1 3 1 4 2 3 1 4
输出样例#1:
14 11
把线性基搞树上去了,我们自然要在树上维护线性基。我们考虑使用倍增法求LCA,并把一个点到它所有倍增父亲这一段路径的线性基预处理出来,在倍增上跳的过程中就是O(\log n)次合并线性基的过程。
倍增LCAO(\log n),维护线性基O(\log n),合并线性基O(\log^2 n),因此总复杂度O(n \log^3 n)。
// Code by KSkun, 2018/5
#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();
while(!isdigit(c)) {
if(c == '-') neg = -1;
c = fgc();
}
while(isdigit(c)) {
res = (res << 1) + (res << 3) + c - '0';
c = fgc();
}
return res * neg;
}
const int MAXN = 20005;
std::vector<int> gra[MAXN];
struct LinearBasis {
LL mat[65];
LinearBasis() {
memset(mat, 0, sizeof(mat));
}
inline void insert(LL x) {
for(int i = 60; i >= 0; i--) {
if(x & (1ll << i)) {
if(!mat[i]) {
mat[i] = x; break;
} else {
x ^= mat[i];
}
}
}
}
inline void merge(LinearBasis &x) {
for(int i = 60; i >= 0; i--) {
if(x.mat[i]) insert(x.mat[i]);
}
}
inline LL max() {
LL res = 0;
for(int i = 60; i >= 0; i--) {
res = std::max(res, res ^ mat[i]);
}
return res;
}
} lb[MAXN][20];
int anc[MAXN][20], dep[MAXN];
LL w[MAXN];
inline void dfs(int u) {
for(int i = 1; i < 15; i++) {
anc[u][i] = anc[anc[u][i - 1]][i - 1];
lb[u][i].merge(lb[u][i - 1]);
lb[u][i].merge(lb[anc[u][i - 1]][i - 1]);
}
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
if(v == anc[u][0]) continue;
anc[v][0] = u;
dep[v] = dep[u] + 1;
lb[v][0].insert(w[u]);
dfs(v);
}
}
inline LL query(int x, int y) {
LinearBasis res;
if(x == y) {
res.insert(w[x]); return res.max();
}
if(dep[x] > dep[y]) std::swap(x, y);
int del = dep[y] - dep[x];
for(int i = 14; i >= 0; i--) {
if(del & (1 << i)) {
res.merge(lb[y][i]);
y = anc[y][i];
}
}
if(x == y) return res.max();
for(int i = 14; i >= 0; i--) {
if(anc[x][i] != anc[y][i]) {
res.merge(lb[x][i]);
res.merge(lb[y][i]);
x = anc[x][i];
y = anc[y][i];
}
}
res.merge(lb[x][0]);
res.insert(w[y]);
return res.max();
}
int n, q, x, y;
int main() {
n = readint(); q = readint();
for(int i = 1; i <= n; i++) {
w[i] = readint();
lb[i][0].insert(w[i]);
}
for(int i = 1; i < n; i++) {
x = readint(); y = readint();
gra[x].push_back(y);
gra[y].push_back(x);
}
dfs(1);
while(q--) {
x = readint(); y = readint();
printf("%lld\n", query(x, y));
}
return 0;
}
题目地址:洛谷:【P2387】[NOI2014]魔法森林 – 洛谷、BZOJ:Problem 3669. — [Noi2014]魔法森林
为了得到书法大家的真传,小 E 同学下定决心去拜访住在魔法森林中的隐 士。魔法森林可以被看成一个包含 n 个节点 m 条边的无向图,节点标号为 1,2,3,…,n,边标号为 1,2,3,…,m。初始时小 E 同学在 1 号节点,隐士则住在 n 号节点。小 E 需要通过这一片魔法森林,才能够拜访到隐士。
魔法森林中居住了一些妖怪。每当有人经过一条边的时候,这条边上的妖怪 就会对其发起攻击。幸运的是,在 1 号节点住着两种守护精灵:A 型守护精灵与 B 型守护精灵。小 E 可以借助它们的力量,达到自己的目的。
只要小 E 带上足够多的守护精灵,妖怪们就不会发起攻击了。具体来说,无 向图中的每一条边 ei 包含两个权值 ai 与 bi 。若身上携带的 A 型守护精灵个数不 少于 ai ,且 B 型守护精灵个数不少于 bi ,这条边上的妖怪就不会对通过这条边 的人发起攻击。当且仅当通过这片魔法森林的过程中没有任意一条边的妖怪向 小 E 发起攻击,他才能成功找到隐士。
由于携带守护精灵是一件非常麻烦的事,小 E 想要知道,要能够成功拜访到 隐士,最少需要携带守护精灵的总个数。守护精灵的总个数为 A 型守护精灵的 个数与 B 型守护精灵的个数之和。
输入格式:
输入文件的第 1 行包含两个整数 n,m,表示无向图共有 n 个节点,m 条边。 接下来 m 行,第i+ 1 行包含 4 个正整数 Xi,Yi,ai,bi,描述第i条无向边。 其中Xi与 Yi为该边两个端点的标号,ai 与 bi 的含义如题所述。 注意数据中可能包含重边与自环。
输出格式:
输出一行一个整数:如果小 E 可以成功拜访到隐士,输出小 E 最少需要携 带的守护精灵的总个数;如果无论如何小 E 都无法拜访到隐士,输出“-1”(不含引号)。
输入样例#1:
4 5 1 2 19 1 2 3 8 12 2 4 12 15 1 3 17 8 3 4 1 17
输出样例#1:
32
输入样例#2:
3 1 1 2 1 1
输出样例#2:
-1
2<=n<=50000, 0<=m<=100000
本题可以使用LCT维护最小生成树动态加边的办法。一条边有两个边权,不好处理,我们将所有的边按照一个边权a排序,依次插入,并每次都用当前答案更新答案。遇到该边的两个点已是连通状态,如果这条路径上存在比当前边的边权b还大的边,就删去路径上的该边,否则不插入这条边。我们可以把边权放在点上,插入一条边变成使边的两端点向代表边的点连接。
// Code by KSkun, 2018/4
#include <cstdio>
#include <algorithm>
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;
char c = fgc();
while(c < '0' || c > '9') {
if(c == '-') neg = -1;
c = fgc();
}
while(c >= '0' && c <= '9') {
res = res * 10 + c - '0';
c = fgc();
}
return res * neg;
}
const int MAXN = 150005, INF = 1e9;
struct Node {
int val, mx, ch[2], fa;
bool rev;
} tr[MAXN];
inline bool isleft(int p) {
return tr[tr[p].fa].ch[0] == p;
}
inline bool isroot(int p) {
int fa = tr[p].fa;
return tr[fa].ch[0] != p && tr[fa].ch[1] != p;
}
inline void update(int p) {
int lch = tr[p].ch[0], rch = tr[p].ch[1];
tr[p].mx = p;
if(tr[tr[lch].mx].val > tr[tr[p].mx].val) tr[p].mx = tr[lch].mx;
if(tr[tr[rch].mx].val > tr[tr[p].mx].val) tr[p].mx = tr[rch].mx;
}
inline void reverse(int p) {
std::swap(tr[p].ch[0], tr[p].ch[1]);
tr[p].rev ^= 1;
}
inline void pushdown(int p) {
int lch = tr[p].ch[0], rch = tr[p].ch[1];
if(tr[p].rev) {
if(lch) reverse(lch);
if(rch) reverse(rch);
tr[p].rev ^= 1;
}
}
int sta[MAXN], stop;
inline void pushto(int p) {
stop = 0;
while(!isroot(p)) {
sta[stop++] = p;
p = tr[p].fa;
}
sta[stop++] = p;
while(stop) {
pushdown(sta[--stop]);
}
}
inline void rotate(int p) {
bool t = !isleft(p); int fa = tr[p].fa, ffa = tr[fa].fa;
tr[p].fa = ffa; if(!isroot(fa)) tr[ffa].ch[!isleft(fa)] = p;
tr[fa].ch[t] = tr[p].ch[!t]; tr[tr[fa].ch[t]].fa = fa;
tr[fa].fa = p; tr[p].ch[!t] = fa;
update(fa);
}
inline void splay(int p) {
pushto(p);
for(int fa = tr[p].fa; !isroot(p); rotate(p), fa = tr[p].fa) {
if(!isroot(fa)) rotate(isleft(p) == isleft(fa) ? fa : p);
}
update(p);
}
inline void access(int p) {
for(int q = 0; p; q = p, p = tr[p].fa) {
splay(p);
tr[p].ch[1] = q;
update(p);
}
}
inline int findrt(int p) {
access(p);
splay(p);
while(tr[p].ch[0]) p = tr[p].ch[0];
return p;
}
inline void makert(int p) {
access(p);
splay(p);
reverse(p);
}
inline void split(int u, int v) {
makert(u);
access(v);
splay(v);
}
inline void link(int u, int v) {
split(u, v);
tr[u].fa = v;
}
inline void cut(int u, int v) {
split(u, v);
if(tr[v].ch[0] != u || tr[u].ch[1]) return;
tr[v].ch[0] = tr[u].fa = 0;
}
inline int query(int u, int v) {
split(u, v);
return tr[v].mx;
}
int n, m, ans = INF;
inline void updateans(int a) {
if(findrt(1) == findrt(n)) {
ans = std::min(ans, a + tr[query(1, n)].val);
}
}
struct Edge {
int u, v, a, b;
inline bool operator<(const Edge &rhs) const {
return a < rhs.a;
}
} edge[MAXN];
int main() {
n = readint(); m = readint();
for(int i = 1; i <= m; i++) {
edge[i].u = readint(); edge[i].v = readint();
edge[i].a = readint(); edge[i].b = readint();
}
std::sort(edge + 1, edge + m + 1);
for(int i = 1; i <= m; i++) {
int u = edge[i].u, v = edge[i].v, a = edge[i].a, b = edge[i].b,
urt = findrt(u), vrt = findrt(v);
if(urt == vrt) { // alrealy connected
int t = query(u, v);
if(tr[t].val > b) {
cut(t, edge[t - n].u);
cut(t, edge[t - n].v);
} else {
updateans(a);
continue;
}
}
tr[i + n].val = b; tr[i + n].mx = i + n;
link(u, i + n); link(v, i + n);
updateans(a);
}
printf("%d", ans != INF ? ans : -1);
return 0;
}
题目地址:洛谷:【P4149】[IOI2011]Race – 洛谷、BZOJ:Problem 2599. — [IOI2011]Race
给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.
输入格式:
第一行 两个整数 n, k
第二..n行 每行三个整数 表示一条无向边的两端和权值 (注意点的编号从0开始)
输出格式:
一个整数 表示最小边数量 如果不存在这样的路径 输出-1
输入样例#1:
4 3 0 1 1 1 2 2 1 3 4
输出样例#1:
2
N <= 200000, K <= 1000000
参考资料:bzoj千题计划160:bzoj2599: [IOI2011]Race – TRTTG – 博客园
考虑点分治的做法,类似于洛谷点分治模板题。但是这个题有点卡常,你需要做一些调整。
搜索+剪枝还是跑的不太慢的。
// Code by KSkun, 2018/4
#include <cstdio>
#include <cstring>
inline int max(int a, int b) {
return a > b ? a : b;
}
inline int min(int a, int 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 int readint() {
register int res = 0;
register char c = fgc();
while(c < '0' || c > '9') {
c = fgc();
}
while(c >= '0' && c <= '9') {
res = res * 10 + c - '0';
c = fgc();
}
return res;
}
const int MAXN = 200005, INF = 1e9;
int n, k, mn = INF, siz[MAXN], msz[MAXN], rt;
bool vis[MAXN];
struct Edge {
int to, w, nxt;
} gra[MAXN << 1];
int head[MAXN], tot;
inline void addedge(int u, int v, int w) {
gra[tot] = Edge {v, w, head[u]}; head[u] = tot++;
gra[tot] = Edge {u, w, head[v]}; head[v] = tot++;
}
inline void findrt(int u, int f, int tot) {
siz[u] = 1; msz[u] = 0;
for(register int i = head[u]; ~i; i = gra[i].nxt) {
register int v = gra[i].to;
if(v == f || vis[v]) continue;
findrt(v, u, tot);
siz[u] += siz[v];
msz[u] = max(msz[u], siz[v]);
}
msz[u] = max(msz[u], tot - siz[u]);
if(msz[u] < msz[rt]) rt = u;
}
int cnt[1000005];
inline void dfs(int u, int f, int dis, int dep, int fun) {
if(fun == 2 && dis == k) mn = min(mn, dep);
if(dis >= k) return;
if(fun == 2) cnt[dis] = cnt[dis] ? min(cnt[dis], dep) : dep;
else if(fun == 1) { if(cnt[k - dis]) mn = min(mn, dep + cnt[k - dis]); }
else cnt[dis] = 0;
for(register int i = head[u]; ~i; i = gra[i].nxt) {
register int v = gra[i].to, w = gra[i].w;
if(v == f || vis[v]) continue;
dfs(v, u, dis + w, dep + 1, fun);
}
}
inline void work(int u) {
for(register int i = head[u]; ~i; i = gra[i].nxt) {
register int v = gra[i].to, w = gra[i].w;
if(vis[v]) continue;
dfs(v, u, w, 1, 1);
dfs(v, u, w, 1, 2);
}
for(register int i = head[u]; ~i; i = gra[i].nxt) {
register int v = gra[i].to, w = gra[i].w;
if(vis[v]) continue;
dfs(v, u, w, 1, 3);
}
}
inline void divide(int u) {
work(u);
vis[u] = true;
for(register int i = head[u]; ~i; i = gra[i].nxt) {
register int v = gra[i].to;
if(vis[v]) continue;
rt = 0; findrt(v, 0, siz[v]);
divide(rt);
}
}
int u, v, w;
int main() {
memset(head, -1, sizeof(head));
n = readint(); k = readint();
for(register int i = 1; i < n; i++) {
u = readint() + 1; v = readint() + 1; w = readint();
addedge(u, v, w);
}
msz[0] = INF; rt = 0; findrt(1, 0, n);
divide(rt);
printf("%d", mn != INF ? mn : -1);
return 0;
}