作者: KSkun

[SCOI2014]方伯伯的OJ 题解

[SCOI2014]方伯伯的OJ 题解

题目地址:洛谷:【P3285】[SCOI2014]方伯伯的OJ – 洛谷、BZOJ:Problem 3595. — [Scoi2014]方伯伯的Oj

题目描述

方伯伯正在做他的Oj。现在他在处理Oj上的用户排名问题。Oj上注册了n个用户,编号为1~n“,一开始他们按照编号排名。
方伯伯会按照心情对这些用户做以下四种操作,修改用户的排名和编号:

1.操作格式为1 x y,意味着将编号为x的用户编号改为y,而排名不变,执行完该操作后需要输出该用户在队列中的位置,数据保证x必然出现在队列中,同时,1是一个当前不在排名中的编号。
2.操作格式为2 x,意味着将编号为x的用户的排名提升到第一位,执行完该操作后需要输出执行该操作前编号为x用户的排名。
3.操作格式为3 x,意味着将编号为x的用户的排名降到最后一位,执行完该操作后需要输出执行该操作前编号为x用户的排名。
4.操作格式为4 k,意味着查询当前排名为k的用户编号,执行完该操作后需要输出当前操作用户的编号。

但同时为了防止别人监听自己的工作,方伯伯对他的操作进行了加密,即将四种操作的格式分别改为了:

  • 1 x+a y+a
  • 2 x+a
  • 3 x+a
  • 4 k+a

其中a为上一次操作得到的输出,一开始a=0。
例如:上一次操作得到的输出是5这一次操作的输入为:1 13 15因为这个输入是经过加密后的,所以你应该处理的操作是1 8 10现在你截获了方伯伯的所有操作,希望你能给出结果。

输入输出格式

输入格式:
输入的第1行包含2个用空格分隔的整数n和m,表示初始用户数和操作数。此后有m行,每行是一个询问,询问格式如上所示。

输出格式:
输出包含m行。每行包含一个整数,其中第i行的整数表示第i个操作的输出。

输入输出样例

输入样例#1:

10 10
1 2 11
3 13
2 5
3 7
2 8
2 10
2 11
3 14
2 18
4 9

输出样例#1:

2
2
2
4
3
5
5
7
8
11

说明

对于 100% 的数据,1 <= n <= 10^8,1 <= m <= 10^5
输入保证对于所有的操作 1,2,3,x 必然已经出现在队列中,同时对于所有操作 1,1 <= y <= 2 * 10^8,并且y 没有出现在队列中。
对于所有操作 4,保证 1 <= k <= n。

题解

处理编号与splay节点编号之间的对应关系,我们可以考虑使用map。
第一位和最后一位的处理方法类似[ZJOI2006]书架 题解 | KSkun’s Blog
改编号的操作,只需要把原来的编号从map中删去再加入新的编号即可。
但是这个n的数据范围有点吓人呀,我们考虑用一个节点表示一段连续区间,等到用到这个区间中某个值的时候再把它割成[l, i-1]、[i, i]、[i+1, r]三段,这样的话每次操作前要先割区间,然后继续。map存的也不再是编号-splay节点的关系了,而是区间左端点-splay节点,这样我们每次找到小于等于要处理编号的节点即可。

代码

// Code by KSkun, 2018/4
#include <cstdio>
#include <cstring>

#include <algorithm>
#include <map>

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 = 1000005, INF = 1e9;

std::map<int, int> spn;

struct Node {
    int fa, ch[2], l, r, siz;
} tr[MAXN];
int rt, tot;

inline void update(int p) {
    tr[p].siz = tr[tr[p].ch[0]].siz + tr[tr[p].ch[1]].siz + tr[p].r - tr[p].l + 1;
}

inline bool isleft(int p) {
    return tr[tr[p].fa].ch[0] == p;
}

inline void rotate(int p) {
    bool t = !isleft(p); int fa = tr[p].fa, ffa = tr[fa].fa;
    tr[p].fa = ffa; if(ffa) 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);
    if(!tr[p].fa) rt = p;
}

inline void splay(int p, int tar) {
    for(int fa = tr[p].fa; fa != tar; rotate(p), fa = tr[p].fa) {
        if(tr[fa].fa != tar) rotate(isleft(p) == isleft(fa) ? fa : p);
    }
    update(p);
}

inline int queryn(int p, int rk) {
    if(rk <= tr[tr[p].ch[0]].siz) {
        return queryn(tr[p].ch[0], rk);
    }
    rk -= tr[tr[p].ch[0]].siz;
    if(rk <= tr[p].r - tr[p].l + 1) {
        splay(p, 0);
        return tr[p].l + rk - 1;
    }
    rk -= tr[p].r - tr[p].l + 1;
    return queryn(tr[p].ch[1], rk);
}

inline int querypre() {
    int p = tr[rt].ch[0];
    while(tr[p].ch[1]) p = tr[p].ch[1];
    return p;
}

inline int querynxt() {
    int p = tr[rt].ch[1];
    while(tr[p].ch[0]) p = tr[p].ch[0];
    return p;
}

inline void split(int p, int id) {
    if(tr[p].l < id) {
        int q = ++tot;
        tr[q].ch[0] = tr[p].ch[0];
        tr[tr[q].ch[0]].fa = q;
        tr[p].ch[0] = q;
        tr[q].l = tr[p].l; tr[q].r = id - 1;
        tr[q].fa = p;
        update(q);
        spn[tr[q].l] = q;
    }
    if(tr[p].r > id) {
        int q = ++tot;
        tr[q].ch[1] = tr[p].ch[1];
        tr[tr[q].ch[1]].fa = q;
        tr[p].ch[1] = q;
        tr[q].l = id + 1; tr[q].r = tr[p].r;
        tr[q].fa = p;
        update(q);
        spn[tr[q].l] = q;
    }
    tr[p].l = tr[p].r = id;
    update(p);
    spn[id] = p;
}

inline int top(int p) {
    splay(p, 0);
    int res = tr[tr[p].ch[0]].siz + 1;
    if(!tr[p].ch[0]) return res;
    if(!tr[p].ch[1]) {
        std::swap(tr[p].ch[0], tr[p].ch[1]);
        return res;
    }
    int q = querynxt();
    split(q, tr[q].l);
    splay(q, p);
    tr[q].ch[0] = tr[p].ch[0];
    tr[tr[q].ch[0]].fa = q;
    tr[p].ch[0] = 0;
    update(p); update(q);
    return res;
}

inline int bottom(int p) {
    splay(p, 0);
    int res = tr[tr[p].ch[0]].siz + 1;
    if(!tr[p].ch[1]) return res;
    if(!tr[p].ch[0]) {
        std::swap(tr[p].ch[0], tr[p].ch[1]);
        return res;
    }
    int q = querypre();
    split(q, tr[q].r);
    splay(q, p);
    tr[q].ch[1] = tr[p].ch[1];
    tr[tr[q].ch[1]].fa = q;
    tr[p].ch[1] = 0;
    update(p); update(q);
    return res;
}

int n, m, op, x, y, ans;

int main() {
    n = readint(); m = readint();
    tot = 1;
    tr[1].l = 1; tr[1].r = n; update(1); spn[1] = 1; rt = 1;
    while(m--) {
        op = readint(); x = readint() - ans; if(op == 1) y = readint() - ans;
        int p;
        if(op != 4) {
            std::map<int, int>::iterator it = --spn.upper_bound(x);
            p = it->second;
            split(p, x);
        }
        if(op == 1) {
            splay(p, 0);
            ans = tr[tr[p].ch[0]].siz + 1;
            spn.erase(tr[p].l);
            tr[p].l = tr[p].r = y; spn[y] = p;
        } else if(op == 2) {
            ans = top(p);
        } else if(op == 3) {
            ans = bottom(p);
        } else {
            ans = queryn(rt, x);
        }
        printf("%d\n", ans);
    }
    return 0;
}
[NOI2014]魔法森林 题解

[NOI2014]魔法森林 题解

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

[IOI2011]Race 题解

题目地址:洛谷:【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 – 博客园
考虑点分治的做法,类似于洛谷点分治模板题。但是这个题有点卡常,你需要做一些调整。

  1. 你不可能像洛谷模板题那样O(n^2)组合了,因此我们可以计算一个cnt数组,为到重心的每个距离的最小边数,每次到一棵子树计算距离的时候,先更新全局答案,再计算该子树对cnt的更改,最后还要用DFS将cnt数组还原至默认值。毕竟K范围那么大,每次都memset也不现实。
  2. dis超过K的DFS可以剪枝剪掉。
  3. 其实上面更新答案的方法还可以用双指针法,严格O(n)更快。这位仁兄写的就是双指针:bzoj2599: [IOI2011]Race(点分治) – mzl120918 – 博客园

搜索+剪枝还是跑的不太慢的。

代码

// 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;
}