2018冬令营模拟测试赛(七)
[Problem A]物品购买
试题描述
小X在一个商店准备购买物品,这个商店一共有 (n) 个物品,第 (i) 个物品价钱为 (a_i) 元,价值为 (b_i)
一共有 (m) 天时间,小X每天都去那个商店购买物品,但是每天带的钱数不一样,第 (i) 天带 (c_i) 元钱,所以小X想用手里的钱买到价值尽量高的物品。
这个商店虽然会进 (n) 个物品,但是这 (n) 个物品不是每天都能随随便便买到的,物品 (1) 特殊,每天能够买到,除物品 (1) 外,每一个物品都依赖于另一个物品。
如果物品 (a) 依赖于物品 (b),则说明如果物品 (a) 能够在商店买到,则物品 (b) 一定能买到。
当然依赖是可以传递的,如果 (a) 依赖于 (b),(b) 依赖于 (c),则 (a) 依赖于 (c)。
但是小X打听到了一些信息,这段信息一共有 (m) 条,每条信息有一个数 (d_i),表示已经确定能够买到的物品编号。但是剩下的物品就不知道能不能买到,所以就有了不确定性。
小X不想对那些不确定性进行讨论,他只想知道在最坏的情况下,这 (m) 天能买到的物品最大价值分别为多少。
为了处理起来方便,小X已经对编号进行了处理,保证编号大的依赖于编号小的物品。
输入
第一行两个正整数 (n, m) 表示物品数和天数
接下来 (n) 行,每行两个数字描述每一个物品的价钱和价值。
接下来 (n-1) 行,描述从第 (2) 个物品到第 (n) 个物品的依赖
接下来 (m) 行,每行一个正整数,表示小X每天带的钱数。
接下来 (m) 行表示这 (m) 天中,一定能够买到的物品。
输出
(m) 行,表示 (m) 天的预算结果。
输入示例
5 5
2 15
15 8
9 7
12 5
16 16
1
2
2
1
2
1
8
19
13
1
2
3
4
2
输出示例
15
0
60
135
90
数据规模及约定
(n, m le 5000),所有数小于 (32768)。
题解
卡空间,我们努力做到只维护 (mathrm{log}n) 层数组。
建出分治树,如果当前分治树的根是编号最小的节点,那么一定能求得它的 dp 信息(要么这个节点是 (1),要么可以从之前分治树的祖先更新过来,后者就遍历一下原树中和自己连着的边然后找到已知的节点更新自己);否则我们先递归到最小编号所在的那个分治子树。
求得一个节点的 dp 信息之后,遍历一下原树中和自己连着的边更新未知的分治树上的祖先。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 5010
#define maxm 10010
#define maxlog 15
#define maxv 32768
int n, q, vol[maxn], val[maxn], qV[maxn];
struct Graph {
int m, head[maxn], nxt[maxm], to[maxm];
void AddEdge(int a, int b) {
to[++m] = b; nxt[m] = head[a]; head[a] = m;
swap(a, b);
to[++m] = b; nxt[m] = head[a]; head[a] = m;
return ;
}
} tr, sol;
int ToT, head[maxn], nxt[maxn], qid[maxn];
void AddQue(int u, int id) {
qid[++ToT] = id; nxt[ToT] = head[u]; head[u] = ToT;
return ;
}
int root, size, f[maxn], siz[maxn];
bool vis[maxn];
void getrt(int u, int fa) {
siz[u] = 1; f[u] = 0;
for(int e = tr.head[u]; e; e = tr.nxt[e]) if(tr.to[e] != fa && !vis[tr.to[e]]) {
getrt(tr.to[e], u);
f[u] = max(f[u], siz[tr.to[e]]);
siz[u] += siz[tr.to[e]];
}
f[u] = max(f[u], size - siz[u]);
if(f[root] > f[u]) root = u;
return ;
}
void build(int u) {
vis[u] = 1;
for(int e = tr.head[u]; e; e = tr.nxt[e]) if(!vis[tr.to[e]]) {
f[root = 0] = size = siz[tr.to[e]]; getrt(tr.to[e], u);
sol.AddEdge(u, root);
build(root);
}
return ;
}
int dep[maxn], mns[maxn];
void getdep(int u, int fa) {
mns[u] = n + 1;
for(int e = sol.head[u]; e; e = sol.nxt[e]) if(sol.to[e] != fa) {
dep[sol.to[e]] = dep[u] + 1;
getdep(sol.to[e], u);
mns[u] = min(mns[u], min(mns[sol.to[e]], sol.to[e]));
}
return ;
}
int F[maxlog][maxv], Ans[maxn];
bool getIt[maxn];
void upd(int& a, int b) {
if(a < b) a = b;
return ;
}
#define me F[dep[u]]
#define To F[dep[tr.to[e]]]
void solve(int u, int fa) {
if(u == 1) {
rep(i, 0, maxv - 1 - vol[u]) upd(me[i+vol[u]], me[i] + val[u]), upd(me[i+1], me[i]);
for(int e = head[u]; e; e = nxt[e]) Ans[qid[e]] = me[qV[qid[e]]];
getIt[u] = 1;
}
for(int e = tr.head[u]; e; e = tr.nxt[e]) if(getIt[tr.to[e]]) {
memcpy(me, F[dep[tr.to[e]]], sizeof(me));
rep(i, 0, maxv - 1 - vol[u]) upd(me[i+vol[u]], me[i] + val[u]), upd(me[i+1], me[i]);
for(int e = head[u]; e; e = nxt[e]) Ans[qid[e]] = me[qV[qid[e]]];
getIt[u] = 1;
break;
}
for(int e = sol.head[u]; e; e = sol.nxt[e]) if(sol.to[e] != fa && min(mns[sol.to[e]], sol.to[e]) == mns[u]) solve(sol.to[e], u);
for(int e = sol.head[u]; e; e = sol.nxt[e]) if(sol.to[e] != fa && min(mns[sol.to[e]], sol.to[e]) != mns[u]) solve(sol.to[e], u);
if(getIt[u]) {
for(int e = tr.head[u]; e; e = tr.nxt[e]) if(!getIt[tr.to[e]] && dep[tr.to[e]] < dep[u]) {
memcpy(To, me, sizeof(me));
rep(i, 0, maxv - 1 - vol[tr.to[e]]) upd(To[i+vol[tr.to[e]]], To[i] + val[tr.to[e]]), upd(To[i+1], To[i]);
for(int E = head[tr.to[e]]; E; E = nxt[E]) Ans[qid[E]] = To[qV[qid[E]]];
getIt[tr.to[e]] = 1;
}
}
return ;
}
int main() {
n = read(); q = read();
rep(i, 1, n) vol[i] = read(), val[i] = read();
rep(i, 2, n) {
int u = read();
tr.AddEdge(u, i);
}
f[root = 0] = size = n; getrt(1, 0); build(root);
rep(i, 1, q) qV[i] = read();
rep(i, 1, q) AddQue(read(), i);
memset(vis, 0, sizeof(vis));
f[root = 0] = size = n; getrt(1, 0);
getdep(root, 0);
solve(root, 0);
rep(i, 1, q) printf("%d
", Ans[i]);
return 0;
}
[Problem B]序列操作
试题描述
你要维护一个长度为 (n) 的序列,进行操作。
对于这个序列,(233) 之类的数不能出现,也就是说 (233,2333,23333,233333, cdots) 这一系列的数不能在序列中出现
1 i
输出第 (i) 个元素
2 a b x
将 ([a,b]) 区间的序列赋值为 (x)
3 a b x
将 ([a,b]) 区间的序列加上 (x)
4 a b
将区间 ([a,b]) 赋值为 ([a,b]) 中的最大值
5 a b
将区间 ([a,b]) 赋值为 ([a,b]) 中的最小值
6 a b
将区间 ([a,b]) 赋值为 ([a,b]) 中的平均值,平均值向下取整
如果在操作后区间中出现了 (233) 之类的数,则需要将你刚才操作的那个区间的数全部加一,如果再出现,就再加一,直到不出现为止
初始不会出现可怕的数字
输入
第一行两个数 (n),(q) 表示序列长度和操作次数
第二行 (n) 个数,表示序列初始值
接下来 (q) 行,描述每一次操作
输出
对于每一次询问,输出结果
输入示例
4 11
3 6 232 1
3 1 4 1
1 3
2 1 4 66
3 3 3 100
1 2
6 1 3
1 2
5 1 2
1 2
4 1 4
1 3
输出示例
234
66
99
99
99
数据规模及约定
(n, q le 100000),所有数在 (10^9 + 7) 范围内,且过程中不会爆 long long。
题解
用 splay 维护区间和、最大、最小值,除此之外对所有权值分一下区间 ([0, 233], [234, 2333], [2334, 23333], cdots),维护一下一个数到它所在区间的右端点的距离,如果变负就“升级”,如果变 (0) 就加 (1)。在区间赋值操作时将一段区间缩成一个点,这样可以保证复杂度。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 100010
#define ool (1ll << 60)
#define LL long long
const LL Grade[] = {233, 2333, 23333, 233333, 2333333, 23333333, 233333333, 2333333333ll, 23333333333ll, 233333333333ll, 2333333333333ll, 23333333333333ll};
int n, A[maxn];
void getGrade(LL& a, LL b) {
int t = lower_bound(Grade, Grade + 12, b) - Grade;
a = Grade[t] - b;
return ;
}
struct Node {
LL mnd, sumv, maxv, minv, setv, addv, val;
int mnp, len, siz;
Node(): setv(-1) {}
} ns[maxn];
int rt, ch[maxn][2], fa[maxn], rec[maxn], rcnt;
void maintain(int o) {
if(!o) return ;
ns[o].siz = ns[o].len;
getGrade(ns[o].mnd, ns[o].val); ns[o].mnp = o;
ns[o].sumv = ns[o].val * ns[o].len; ns[o].minv = ns[o].maxv = ns[o].val;
int l = ch[o][0], r = ch[o][1];
if(l) ns[o].siz += ns[l].siz;
if(r) ns[o].siz += ns[r].siz;
if(ns[o].setv >= 0) {
getGrade(ns[o].mnd, ns[o].setv + ns[o].addv);
ns[o].mnp = o;
ns[o].sumv = ns[o].addv * ns[o].siz;
ns[o].maxv = ns[o].minv = ns[o].setv + ns[o].addv;
return ;
}
ns[0].mnd = ool;
if(ns[l].mnd < ns[r].mnd && ns[l].mnd - ns[o].addv < ns[o].mnd) ns[o].mnd = ns[l].mnd - ns[o].addv, ns[o].mnp = ns[l].mnp;
else if(ns[r].mnd - ns[o].addv < ns[o].mnd) ns[o].mnd = ns[r].mnd - ns[o].addv, ns[o].mnp = ns[r].mnp;
ns[0].sumv = 0;
ns[o].sumv += ns[l].sumv + ns[r].sumv + ns[o].addv * (ns[o].siz - ns[o].len);
ns[0].maxv = -ool; ns[0].minv = ool;
ns[o].maxv = max(ns[o].maxv, max(ns[l].maxv, ns[r].maxv) + ns[o].addv);
ns[o].minv = min(ns[o].minv, min(ns[l].minv, ns[r].minv) + ns[o].addv);
return ;
}
void build(int& o, int l, int r) {
if(l > r){ o = 0; return ; }
int mid = l + r >> 1; ns[o = mid].val = A[mid]; ns[o].len = 1;
build(ch[o][0], l, mid - 1); build(ch[o][1], mid + 1, r);
if(ch[o][0]) fa[ch[o][0]] = o;
if(ch[o][1]) fa[ch[o][1]] = o;
return maintain(o);
}
int Find(int o, int pre) {
if(!o) return 0;
int ls = ch[o][0] ? ns[ch[o][0]].siz : 0;
if(ls <= pre && pre < ls + ns[o].len) return o;
if(pre < ls) return Find(ch[o][0], pre);
return Find(ch[o][1], pre - ls - ns[o].len);
}
void rotate(int u) {
int y = fa[u], z = fa[y], l = 0, r = 1;
if(z) ch[z][ch[z][1]==y] = u;
if(ch[y][1] == u) swap(l, r);
fa[u] = z; fa[y] = u; fa[ch[u][r]] = y;
ch[y][l] = ch[u][r]; ch[u][r] = y;
maintain(y);
return ;
}
void pushdown(int o) {
int l = ch[o][0], r = ch[o][1];
if(ns[o].setv >= 0) {
if(l) {
ns[l].val = ns[o].setv;
ns[l].setv = ns[o].setv; ns[l].addv = 0;
getGrade(ns[l].mnd, ns[o].addv); ns[l].mnp = l;
ns[l].sumv = ns[o].setv * ns[l].siz;
ns[l].maxv = ns[l].minv = ns[o].setv;
}
if(r) {
ns[r].val = ns[o].setv;
ns[r].setv = ns[o].setv; ns[r].addv = 0;
getGrade(ns[r].mnd, ns[o].addv); ns[r].mnp = r;
ns[r].sumv = ns[o].setv * ns[r].siz;
ns[r].maxv = ns[r].minv = ns[o].setv;
}
ns[o].setv = -1;
}
if(ns[o].addv > 0) {
if(l) {
ns[l].val += ns[o].addv;
ns[l].addv += ns[o].addv;
ns[l].mnd -= ns[o].addv;
ns[l].sumv += ns[o].addv * ns[l].siz;
ns[l].maxv += ns[o].addv; ns[l].minv += ns[o].addv;
}
if(r) {
ns[r].val += ns[o].addv;
ns[r].addv += ns[o].addv;
ns[r].mnd -= ns[o].addv;
ns[r].sumv += ns[o].addv * ns[r].siz;
ns[r].maxv += ns[o].addv; ns[r].minv += ns[o].addv;
}
ns[o].addv = 0;
}
return ;
}
int S[maxn], top;
void splay(int u) {
if(!u) return ;
int t = u; S[++top] = u;
while(fa[t]) S[++top] = fa[t], t = fa[t];
while(top) pushdown(S[top--]);
while(fa[u]) {
int y = fa[u], z = fa[y];
if(z) {
if(ch[y][0] == u ^ ch[z][0] == y) rotate(u);
else rotate(y);
}
rotate(u);
}
maintain(u);
return maintain(u);
}
int rins;
void Insert(int& o, int lft, LL val, int len) {
if(!o) {
ns[o = rec[rcnt--]].val = val; ns[o].len = len;
ch[o][0] = ch[o][1] = 0;
rins = o;
return maintain(o);
}
pushdown(o);
int ls = ch[o][0] ? ns[ch[o][0]].siz : 0;
if(lft < ls + ns[o].len) Insert(ch[o][0], lft, val, len), fa[ch[o][0]] = o;
else Insert(ch[o][1], lft - ls - ns[o].len, val, len), fa[ch[o][1]] = o;
return ;
}
void split(int& lrt, int& mrt, int& rrt, int ql, int qr) {
splay(1);
int u = Find(1, ql - 1), ls; splay(u);
ls = ch[u][0] ? ns[ch[u][0]].siz : 0;
if(ls < ql - 1) {
int olen = ns[u].len;
ns[u].len = ql - 1 - ls; maintain(u);
Insert(u, ql - 1, ns[u].val, olen - ns[u].len); splay(rins);
}
if(qr < n) {
splay(1); u = Find(1, qr); splay(u);
ls = ch[u][0] ? ns[ch[u][0]].siz : 0;
if(ls < qr) {
int olen = ns[u].len;
ns[u].len = qr - ls; maintain(u);
Insert(u, qr, ns[u].val, olen - ns[u].len); splay(rins);
}
}
splay(1); u = Find(1, ql - 1); splay(u);
lrt = ch[u][0]; mrt = u;
ch[u][0] = fa[lrt] = 0; maintain(u);
u = Find(u, qr - ql + 1); splay(u);
int t = ch[u][0]; rrt = u;
ch[u][0] = fa[t] = 0; maintain(u);
return splay(mrt);
}
int merge(int a, int b) {
if(!a) return b;
if(!b) return a;
while(ch[a][1]) a = ch[a][1];
splay(a); ch[a][1] = b; fa[b] = a;
return maintain(a), a;
}
void Add(int o, LL v) {
ns[o].addv += v;
ns[o].val += v;
ns[o].mnd -= v;
ns[o].sumv += v * ns[o].siz;
ns[o].maxv += v; ns[o].minv += v;
return ;
}
void recycle(int& o) {
if(!o) return ;
rec[++rcnt] = o;
recycle(ch[o][0]); recycle(ch[o][1]);
ns[o].setv = -1; ns[o].addv = 0;
o = fa[o] = 0;
return ;
}
void Set(int o, LL v) {
ns[o].setv = -1; ns[o].addv = 0;
ns[o].val = v; ns[o].len = ns[o].siz;
getGrade(ns[o].mnd, v); ns[o].mnp = o;
ns[o].sumv = v * ns[o].siz;
ns[o].maxv = ns[o].minv = v;
recycle(ch[o][0]); recycle(ch[o][1]);
return ;
}
int main() {
n = read(); int q = read();
rep(i, 1, n) A[i] = read();
build(rt, 1, n);
while(q--) {
int tp = read();
if(tp == 1) {
int i = read(), u; if(i > n) i = n;
splay(1); u = Find(1, i - 1); splay(u);
printf("%lld
", ns[u].val);
}
if(tp == 2) {
int ql = read(), qr = read(); LL x = read(), t; getGrade(t, x);
if(!t) x++;
int lrt, mrt, rrt;
split(lrt, mrt, rrt, ql, qr);
Set(mrt, x);
merge(merge(lrt, mrt), rrt);
}
if(tp == 3) {
int ql = read(), qr = read(), x = read();
int lrt, mrt, rrt;
split(lrt, mrt, rrt, ql, qr);
Add(mrt, x);
while(ns[mrt].mnd <= 0) {
if(ns[mrt].mnd == 0) Add(mrt, 1);
else mrt = ns[mrt].mnp, splay(mrt);
}
merge(merge(lrt, mrt), rrt);
}
if(tp == 4) {
int ql = read(), qr = read();
int lrt, mrt, rrt;
split(lrt, mrt, rrt, ql, qr);
Set(mrt, ns[mrt].maxv);
merge(merge(lrt, mrt), rrt);
}
if(tp == 5) {
int ql = read(), qr = read();
int lrt, mrt, rrt;
split(lrt, mrt, rrt, ql, qr);
Set(mrt, ns[mrt].minv);
merge(merge(lrt, mrt), rrt);
}
if(tp > 5) {
int ql = read(), qr = read();
if(ql > qr) continue;
int lrt, mrt, rrt;
split(lrt, mrt, rrt, ql, qr);
LL x = ns[mrt].sumv / ns[mrt].siz, t; getGrade(t, x);
if(!t) x++;
Set(mrt, x);
merge(merge(lrt, mrt), rrt);
}
}
return 0;
}
[Problem C]最优连通图
试题描述
给定一个连通图,你要选取一些边组成一棵生成树,使得选取边的代价最小。
每一条边有两个属性,颜色和权值。
设 (L) 为颜色的总数,(V) 为权值的总值,代价为 (L imes V)
可能有重边,但没有自环。
输入
road0.in
-road9.in
已给定,你需要给出 road0.out
-road9.out
第一行两个正整数 (n) 和 (m),表示点数和边数
接下来 (m) 行,每行四个数,(a,b,c,d) 表示 (a) 和 (b) 有连边,权值为 (c),颜色为(d)
输出
(n-1) 行,表示你要选择边的编号
输入示例
3 4
1 2 3 1
1 2 1 2
2 3 4 1
2 3 2 3
输出示例
2
4
数据规模及约定
设标准输出的代价为 (std) 你输出的代价为 (out),则分数为 (10 cdot frac{std}{out})
题解
直接贪心按权值排序可以得 (90+)……
有些点需要暴力枚举哪些颜色然后最小生成树……
由于给的 checker.cpp
,知道答案,就可以枚举约数然后推测颜色个数,枚举时就变快了。
// 略(不要打我 QwQ)