[Baltic2004]Sequence 题解
题目地址:洛谷:【P4331】[BOI2004]Sequence 数字序列 – …
May all the beauty be blessed.
题目地址:洛谷:【P3261】[JLOI2015]城池攻占 – 洛谷、BZOJ:Problem 4003. — [JLOI2015]城池攻占
小铭铭最近获得了一副新的桌游,游戏中需要用 m 个骑士攻占 n 个城池。这 n 个城池用 1 到 n 的整数表示。除 1 号城池外,城池 i 会受到另一座城池 fi 的管辖,其中 fi < i。也就是说,所有城池构成了一棵有根树。这 m 个骑士用 1 到 m 的整数表示,其中第 i 个骑士的初始战斗力为 si,第一个攻击的城池为 ci。
每个城池有一个防御值 hi,如果一个骑士的战斗力大于等于城池的生命值,那么骑士就可以占领这座城池;否则占领失败,骑士将在这座城池牺牲。占领一个城池以后,骑士的战斗力将发生变化,然后继续攻击管辖这座城池的城池,直到占领 1 号城池,或牺牲为止。
除 1 号城池外,每个城池 i 会给出一个战斗力变化参数 ai;vi。若 ai =0,攻占城池 i 以后骑士战斗力会增加 vi;若 ai =1,攻占城池 i 以后,战斗力会乘以 vi。注意每个骑士是单独计算的。也就是说一个骑士攻击一座城池,不管结果如何,均不会影响其他骑士攻击这座城池的结果。
现在的问题是,对于每个城池,输出有多少个骑士在这里牺牲;对于每个骑士,输出他攻占的城池数量。
输入格式:
第 1 行包含两个正整数 n;m,表示城池的数量和骑士的数量。
第 2 行包含 n 个整数,其中第 i 个数为 hi,表示城池 i 的防御值。
第 3 到 n +1 行,每行包含三个整数。其中第 i +1 行的三个数为 fi;ai;vi,分别表示管辖这座城池的城池编号和两个战斗力变化参数。
第 n +2 到 n + m +1 行,每行包含两个整数。其中第 n + i 行的两个数为 si;ci,分别表示初始战斗力和第一个攻击的城池。
输出格式:
输出 n + m 行,每行包含一个非负整数。其中前 n 行分别表示在城池 1 到 n 牺牲的骑士数量,后 m 行分别表示骑士 1 到 m 攻占的城池数量。
输入样例#1:
5 5 50 20 10 10 30 1 1 2 2 0 5 2 0 -10 1 0 10 20 2 10 3 40 4 20 4 35 5
输出样例#1:
2 2 0 0 0 1 1 3 1 1
对于 100% 的数据,1 <= n;m <= 300000; 1 <= fi<i; 1 <= ci <= n; -10^18 <= hi,vi,si <= 10^18;ai等于1或者2;当 ai =1 时,vi > 0;保证任何时候骑士战斗力值的绝对值不超过 10^18。
考虑对城池构成的树进行DFS转移。维护一个可并小根堆,表示能够到达该节点的骑士,开始时将骑士加入开始节点的左偏树,DFS时合并儿子节点的左偏树,并且将那些战斗力达不到的骑士pop出,并计入该城池牺牲骑士数以及骑士牺牲的位置。本题输入比较大,所以临时换了个fread板子。
// Code by KSkun, 2018/2
#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;
register int 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 = 300005;
int dis[MAXN], rt[MAXN], ch[MAXN][2];
LL val[MAXN], add[MAXN], mul[MAXN];
inline void pushdown(int x) {
if(ch[x][0]) {
val[ch[x][0]] *= mul[x];
val[ch[x][0]] += add[x];
mul[ch[x][0]] *= mul[x];
add[ch[x][0]] *= mul[x];
add[ch[x][0]] += add[x];
}
if(ch[x][1]) {
val[ch[x][1]] *= mul[x];
val[ch[x][1]] += add[x];
mul[ch[x][1]] *= mul[x];
add[ch[x][1]] *= mul[x];
add[ch[x][1]] += add[x];
}
mul[x] = 1;
add[x] = 0;
}
inline int merge(int x, int y) {
if(!x) return y;
if(!y) return x;
if(val[x] > val[y]) std::swap(x, y);
pushdown(x);
pushdown(y);
ch[x][1] = merge(ch[x][1], y);
if(dis[ch[x][0]] < dis[ch[x][1]]) std::swap(ch[x][0], ch[x][1]);
dis[x] = dis[ch[x][1]] + 1;
return x;
}
struct Edge {
int to, nxt;
} gra[MAXN];
int head[MAXN], tot = 1;
inline void addedge(int u, int v) {
gra[tot].to = v;
gra[tot].nxt = head[u];
head[u] = tot++;
}
int n, m, f, a[MAXN], c[MAXN], dep[MAXN], die[MAXN], end[MAXN];
LL h[MAXN], v[MAXN];
inline void dfs(int u) {
for(int i = head[u]; i; i = gra[i].nxt) {
int v = gra[i].to;
dep[v] = dep[u] + 1;
dfs(v);
rt[u] = merge(rt[u], rt[v]);
}
while(rt[u] && val[rt[u]] < h[u]) {
pushdown(rt[u]);
die[u]++;
end[rt[u]] = u;
rt[u] = merge(ch[rt[u]][0], ch[rt[u]][1]);
}
if(!a[u]) {
val[rt[u]] += v[u];
add[rt[u]] += v[u];
} else {
val[rt[u]] *= v[u];
add[rt[u]] *= v[u];
mul[rt[u]] *= v[u];
}
}
int main() {
dis[0] = -1;
n = readint();
m = readint();
for(int i = 1; i <= n; i++) {
h[i] = readint();
}
for(int i = 2; i <= n; i++) {
f = readint();
a[i] = readint();
v[i] = readint();
addedge(f, i);
}
for(int i = 1; i <= m; i++) {
val[i] = readint();
c[i] = readint();
mul[i] = 1;
rt[c[i]] = merge(rt[c[i]], i);
}
dep[1] = 1;
dfs(1);
for(int i = 1; i <= n; i++) {
printf("%d\n", die[i]);
}
for(int i = 1; i <= m; i++) {
printf("%d\n", dep[c[i]] - dep[end[i]]);
}
return 0;
}
题目地址:洛谷:【P3273】[SCOI2011]棘手的操作 – 洛谷、BZOJ:Problem 2333. — [SCOI2011]棘手的操作
有N个节点,标号从1到N,这N个节点一开始相互不连通。第i个节点的初始权值为a[i],接下来有如下一些操作:
U x y
: 加一条边,连接第x个节点和第y个节点A1 x v
: 将第x个节点的权值增加vA2 x v
: 将第x个节点所在的连通块的所有节点的权值都增加vA3 v
: 将所有节点的权值都增加vF1 x
: 输出第x个节点当前的权值F2 x
: 输出第x个节点所在的连通块中,权值最大的节点的权值F3
: 输出所有节点中,权值最大的节点的权值输入格式:
输入的第一行是一个整数N,代表节点个数。接下来一行输入N个整数,a[1], a[2], …, a[N],代表N个节点的初始权值。再下一行输入一个整数Q,代表接下来的操作数。最后输入Q行,每行的格式如题目描述所示。
输出格式:
对于操作F1, F2, F3,输出对应的结果,每个结果占一行。
输入样例#1:
3 0 0 0 8 A1 3 -20 A1 2 20 U 1 3 A2 1 10 F1 3 F2 3 A3 -10 F3
输出样例#1:
-10 10 10
对于30%的数据,保证 N<=100,Q<=10000
对于80%的数据,保证 N<=100000,Q<=100000
对于100%的数据,保证 N<=300000,Q<=300000
对于所有的数据,保证输入合法,并且 -1000<=v, a[1], a[2], …, a[N]<=1000
考虑离线解决这个题,我们可以把操作中同一连通块的元素连续地排列在一个序列中,这样连通块操作就变成了区间操作,这个可以用线段树维护区间最大值。
至于如何连续排列,可以考虑用链表来存储当前连通块序列,然后合并连通块的时候合并链表,这样就自然成为连续的一段了。最后再遍历链表给节点加编号就好。
使用可并堆做这个题目就很裸了,也不需要离线。这里我写的是左偏树。
分开管理两类可并堆,一类是联通块的大根堆,一类是所有联通块堆顶元素的大根堆(换句话说,这是一个维护全局的堆)。
U
操作:将两个节点所在的堆合并,删去全局堆中消失的那个堆的对顶。
A1
操作:将x所在堆中x元素删去,更新x元素的值,再插回去。
A2
操作:将x所在堆全局增量加上v。堆全局增量可以在堆顶维护,需要统计这个结点的真实值的时候从该点一路上跳,跳到一个父亲就将该父亲的增量加入真实值。(因为堆并过程中增量仍然在被合并的堆顶)
A3
操作:维护总全局增量加v。
F1
操作:查询x的真实值。
F2
操作:查询x所在堆堆顶的真实值。
F3
操作:查询全局堆堆顶的真实值。
这个解法的代码我借鉴了远航之曲的实现思路。
// Code by KSkun, 2018/2
#include <cstdio>
#include <algorithm>
struct io {
char buf[1 << 26], *op;
io() {
fread(op = buf, 1, 1 << 26, stdin);
}
inline int readint() {
register int res = 0, neg = 1;
while(*op < '0' || *op > '9') if(*op++ == '-') neg = -1;
while(*op >= '0' && *op <= '9') res = res * 10 + *op++ - '0';
return res * neg;
}
inline char readchar() {
return *op++;
}
} ip;
#define readint ip.readint
#define readchar ip.readchar
inline bool isop(char c) {
return c == 'A' || c == 'U' || c == 'F';
}
inline char readop() {
char c;
while(!isop(c = readchar()));
return c;
}
int n, a[300005], q, opop1[300005], opx[300005], opy[300005], idx[300005], inv[300005], tot = 1;
char opop[300005];
// Linked List
int pre[300005], nxt[300005], head[300005], tail[300005];
// Union-Find
int fa[300005];
inline int find(int x) {
int r = x;
while(fa[r] != r) r = fa[r];
while(fa[x] != x) {
int ofa = fa[x];
fa[x] = r;
x = ofa;
}
return r;
}
// Seg Tree
#define lch o << 1
#define rch o << 1 | 1
#define mid ((l + r) >> 1)
int tree[1200005], lazy[1200005];
inline void build(int o, int l, int r) {
if(l == r) {
tree[o] = a[inv[l]];
return;
}
build(lch, l, mid);
build(rch, mid + 1, r);
tree[o] = std::max(tree[lch], tree[rch]);
}
inline void pushdown(int o) {
if(lazy[o] != 0) {
lazy[lch] += lazy[o];
lazy[rch] += lazy[o];
tree[lch] += lazy[o];
tree[rch] += lazy[o];
lazy[o] = 0;
}
}
inline void add(int o, int l, int r, int ll, int rr, int v) {
if(l == ll && r == rr) {
tree[o] += v;
lazy[o] += v;
return;
}
pushdown(o);
if(rr <= mid) {
add(lch, l, mid, ll, rr, v);
} else if(ll > mid) {
add(rch, mid + 1, r, ll, rr, v);
} else {
add(lch, l, mid, ll, mid, v);
add(rch, mid + 1, r, mid + 1, rr, v);
}
tree[o] = std::max(tree[lch], tree[rch]);
}
inline int query(int o, int l, int r, int ll, int rr) {
if(l == ll && r == rr) {
return tree[o];
}
pushdown(o);
if(rr <= mid) {
return query(lch, l, mid, ll, rr);
} else if(ll > mid) {
return query(rch, mid + 1, r, ll, rr);
} else {
return std::max(query(lch, l, mid, ll, mid), query(rch, mid + 1, r, mid + 1, rr));
}
}
int main() {
n = readint();
for(int i = 1; i <= n; i++) {
a[i] = readint();
fa[i] = head[i] = tail[i] = i;
}
q = readint();
for(int i = 1; i <= q; i++) {
opop[i] = readop();
if(opop[i] == 'U') {
opx[i] = readint();
opy[i] = readint();
int f1 = find(opx[i]), f2 = find(opy[i]);
if(f1 != f2) {
fa[f2] = f1;
pre[head[f2]] = tail[f1];
nxt[tail[f1]] = head[f2];
tail[f1] = tail[f2];
}
} else if(opop[i] == 'A') {
opop1[i] = readint();
if(opop1[i] < 3) {
opx[i] = readint();
}
opy[i] = readint();
} else if(opop[i] == 'F') {
opop1[i] = readint();
if(opop1[i] < 3) {
opx[i] = readint();
}
}
}
for(int i = 1; i <= n; i++) {
if(!pre[i]) {
for(int j = i; j; j = nxt[j]) {
idx[j] = tot++;
inv[idx[j]] = j;
}
}
}
build(1, 1, n);
for(int i = 1; i <= n; i++) {
fa[i] = head[i] = tail[i] = i;
}
for(int i = 1; i <= q; i++) {
if(opop[i] == 'U') {
int f1 = find(idx[opx[i]]), f2 = find(idx[opy[i]]);
if(f1 != f2) {
fa[f2] = f1;
if(head[f1] < head[f2]) {
tail[f1] = tail[f2];
} else {
head[f1] = head[f2];
}
}
} else if(opop[i] == 'A') {
if(opop1[i] == 1) {
add(1, 1, n, idx[opx[i]], idx[opx[i]], opy[i]);
} else if(opop1[i] == 2) {
int f = find(idx[opx[i]]);
add(1, 1, n, head[f], tail[f], opy[i]);
} else {
add(1, 1, n, 1, n, opy[i]);
}
} else if(opop[i] == 'F') {
if(opop1[i] == 1) {
printf("%d\n", query(1, 1, n, idx[opx[i]], idx[opx[i]]));
} else if(opop1[i] == 2) {
int f = find(idx[opx[i]]);
printf("%d\n", query(1, 1, n, head[f], tail[f]));
} else {
printf("%d\n", query(1, 1, n, 1, n));
}
}
}
return 0;
}
// Code by KSkun, 2018/2
#include <cstdio>
#include <queue>
struct io {
char buf[1 << 26], *op;
io() {
fread(op = buf, 1, 1 << 26, stdin);
}
inline int readint() {
register int res = 0, neg = 1;
while(*op < '0' || *op > '9') if(*op++ == '-') neg = -1;
while(*op >= '0' && *op <= '9') res = res * 10 + *op++ - '0';
return res * neg;
}
inline char readchar() {
return *op++;
}
} ip;
#define readint ip.readint
#define readchar ip.readchar
inline bool isop(char c) {
return c == 'U' || c == 'A' || c == 'F';
}
inline void readop(char* str) {
char c;
while(!isop(c = readchar()));
str[0] = c;
if(c == 'A' || c == 'F') str[1] = readchar();
}
int n, m, x, y, z, add = 0;
char op[5];
struct LeftistTree {
int dis[300005], fa[300005], ch[300005][2], val[300005], add[300005], root;
inline int toadd(int x) {
int res = 0;
while(x = fa[x]) res += add[x];
return res;
}
inline void clear(int x) {
fa[x] = ch[x][0] = ch[x][1] = 0;
}
inline void pushdown(int x) {
if(ch[x][0]) {
val[ch[x][0]] += add[x];
add[ch[x][0]] += add[x];
}
if(ch[x][1]) {
val[ch[x][1]] += add[x];
add[ch[x][1]] += add[x];
}
add[x] = 0;
}
inline int merge(int x, int y) {
if(!x) return y;
if(!y) return x;
if(val[x] < val[y]) std::swap(x, y);
pushdown(x);
ch[x][1] = merge(ch[x][1], y);
fa[ch[x][1]] = x;
if(dis[ch[x][0]] < dis[ch[x][1]]) std::swap(ch[x][0], ch[x][1]);
dis[x] = dis[ch[x][1]] + 1;
return x;
}
inline int froot(int x) {
while(fa[x]) x = fa[x];
return x;
}
inline void delet(int x) {
pushdown(x);
int f = merge(ch[x][0], ch[x][1]);
fa[f] = fa[x];
if(fa[x]) ch[fa[x]][ch[fa[x]][0] == x ? 0 : 1] = f;
int t = fa[x];
if(!t) {
root = f;
return;
}
while(t) {
if(dis[ch[t][0]] < dis[ch[t][1]]) std::swap(ch[t][0], ch[t][1]);
if(dis[t] == dis[ch[t][1]] + 1) return;
dis[t] = dis[ch[t][1]] + 1;
if(!fa[t]) root = t;
t = fa[t];
}
}
inline void addtree(int x, int v) {
int rt = froot(x);
val[rt] += v;
add[rt] += v;
}
inline int addpoint(int x, int v) {
int rt = froot(x);
if(rt == x) {
if(!ch[x][0] && !ch[x][1]) {
val[x] += v;
return x;
} else {
rt = ch[x][0] ? ch[x][0] : ch[x][1];
}
}
delet(x);
val[x] += v + toadd(x);
clear(x);
return merge(froot(rt), x);
}
inline void build() {
std::queue<int> que;
for(int i = 1; i <= n; i++) {
que.push(i);
}
while(que.size() > 1) {
int x = que.front();
que.pop();
int y = que.front();
que.pop();
que.push(merge(x, y));
}
root = que.front();
}
};
LeftistTree lt1, lt2;
int main() {
n = readint();
for(int i = 1; i <= n; i++) {
lt1.val[i] = lt2.val[i] = readint();
}
lt1.dis[0] = lt2.dis[0] = -1;
lt2.build();
m = readint();
for(int i = 1; i <= m; i++) {
readop(op);
if(op[0] == 'U') {
x = readint();
y = readint();
int fx = lt1.froot(x), fy = lt1.froot(y), rt;
if(fx != fy) {
rt = lt1.merge(fx, fy);
if(rt == fx) lt2.delet(fy);
if(rt == fy) lt2.delet(fx);
}
}
if(op[0] == 'A') {
if(op[1] == '1') {
x = readint();
y = readint();
int fx = lt1.froot(x), rt;
lt2.delet(fx);
rt = lt1.addpoint(x, y);
lt2.val[rt] = lt1.val[rt];
lt2.clear(rt);
lt2.root = lt2.merge(lt2.root, rt);
}
if(op[1] == '2') {
x = readint();
y = readint();
int fx = lt1.froot(x);
lt2.delet(fx);
lt1.val[fx] += y;
lt1.add[fx] += y;
lt2.val[fx] = lt1.val[fx];
lt2.clear(fx);
lt2.root = lt2.merge(lt2.root, fx);
}
if(op[1] == '3') {
x = readint();
add += x;
}
}
if(op[0] == 'F') {
if(op[1] == '1') {
x = readint();
printf("%d\n", lt1.val[x] + lt1.toadd(x) + add);
}
if(op[1] == '2') {
x = readint();
printf("%d\n", lt1.val[lt1.froot(x)] + add);
}
if(op[1] == '3') {
printf("%d\n", lt2.val[lt2.root] + add);
}
}
}
return 0;
}
因为经常WA,手写了一个数据生成器,偶尔会崩掉不要介意qwq,可自由调整n和q的取值。
// Code by KSkun, 2018/2
#include <cstdio>
#include <cstdlib>
#include <ctime>
int n, q;
char str[10][5] = {"U", "A1", "A2", "A3", "F1", "F2", "F3"};
int main() {
freopen("p3273.in", "w", stdout);
srand(time(NULL));
n = rand() % 50;
q = rand() % 50;
printf("%d\n", n);
for(int i = 1; i <= n; i++) {
printf("%d ", rand() - RAND_MAX / 2);
}
printf("\n");
printf("%d\n", q);
for(int i = 1; i <= q; i++) {
int op = rand() % 7;
printf("%s ", str[op]);
if(op == 0) {
printf("%d %d ", rand() % n + 1, rand() % n + 1);
}
if(op == 1 || op == 2 || op == 4 || op == 5) {
printf("%d ", rand() % n + 1);
}
if(op == 1 || op == 2 || op == 3) {
printf("%d ", rand() - RAND_MAX / 2);
}
printf("\n");
}
return 0;
}