标签:

[Baltic2004]Sequence 题解

[Baltic2004]Sequence 题解

题目地址:洛谷:【P4331】[BOI2004]Sequence 数字序列 – 洛谷、BZOJ:Problem 1367. — [Baltic2004]sequence

题目描述

1367 1 - [Baltic2004]Sequence 题解

输入输出格式

输入格式:
1367 2 - [Baltic2004]Sequence 题解

输出格式:
第一行输出最小的绝对值之和。
第二行输出序列 bi ,若有多种方案,只需输出其中一种。

输入输出样例

输入样例#1:

5
2 5 46 12 1

输出样例#1:

47
2 5 11 12 13

说明

【数据范围】
40%的数据 n≤5000
60%的数据 n≤300000
100%的数据 n≤1000000 , 0≤a_i≤2×10^9

题解

考虑当所求序列$z_i$是不降的情况应该如何解决。我们有两种情况:

  1. $t_1 \leq t_2 \leq \cdots \leq t_n$:$z_i=t_i$即可
  2. $t_1 \geq t_2 \geq \cdots \geq t_n$:取这些值的中位数作为所有的$z_i$值,这是由于取中位数的时候可以证明答案最小

因此,我们可以将$t$数列分为若干区间,每个区间的答案即其中位数,从而解决这个问题。
然而题目让我们求一个严格递增的序列,我们考虑将$t_i$处理成$t_i – i$,就可以用跟上面一致的方法解决这个问题,因为处理完毕后的数列不降等价于原数列单增。
实现上,我们对每个元素建一个可并堆,然后向左合并左边元素构成的可并堆(即左边的区间),直到这个区间的中位数不小于左边相邻区间的中位数。中位数可以利用大根堆维护,让堆的大小不大于$\left\lceil \frac{|S|}{2} \right\rceil$再取堆顶即可。
复杂度$O(n \log n)$。

代码

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

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

const int MAXN = 1000005;

int n, a[MAXN];

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

inline int merge(int x, int y) {
    if(!x || !y) return x + y;
    if(tr[x].val < tr[y].val) std::swap(x, y);
    tr[x].ch[1] = merge(tr[x].ch[1], y);
    tr[x].siz = tr[tr[x].ch[0]].siz + tr[tr[x].ch[1]].siz + 1;
    tr[tr[x].ch[1]].fa = x;
    if(tr[tr[x].ch[0]].dis < tr[tr[x].ch[1]].dis) std::swap(tr[x].ch[0], tr[x].ch[1]);
    tr[x].dis = tr[tr[x].ch[1]].dis + 1;
    return x;
}

int main() {
    n = readint();
    for(int i = 1; i <= n; i++) {
        a[i] = readint() - i;
    }
    for(int i = 1; i <= n; i++) {
        htot++; rt[htot] = i;
        l[htot] = r[htot] = i;
        tr[i].val = a[i]; tr[i].siz = 1;
        while(htot > 1 && tr[rt[htot]].val < tr[rt[htot - 1]].val) {
            htot--;
            rt[htot] = merge(rt[htot], rt[htot + 1]);
            r[htot] = r[htot + 1];
            while(tr[rt[htot]].siz * 2 > r[htot] - l[htot] + 2) {
                rt[htot] = merge(tr[rt[htot]].ch[0], tr[rt[htot]].ch[1]);
            }
        }
    }
    LL ans = 0;
    for(int i = 1; i <= htot; i++) {
        int w = tr[rt[i]].val;
        for(int j = l[i]; j <= r[i]; j++) {
            ans += abs(w - a[j]);
        }
    }
    printf("%lld\n", ans);
    for(int i = 1; i <= htot; i++) {
        int w = tr[rt[i]].val;
        for(int j = l[i]; j <= r[i]; j++) {
            printf("%d ", w + j);
        }
    }
    return 0;
}
[NOI2010]航空管制 题解

[NOI2010]航空管制 题解

题目地址:洛谷:【P1954】[NOI2010]航空管制 – 洛谷、BZOJ:Problem 2535. — [Noi2010]Plane 航空管制2

题目描述

世博期间,上海的航空客运量大大超过了平时,随之而来的航空管制也频频发生。最近,小X就因为航空管制,连续两次在机场被延误超过了两小时。对此,小X表示很不满意。
在这次来烟台的路上,小X不幸又一次碰上了航空管制。于是小X开始思考关于航空管制的问题。
假设目前被延误航班共有n个,编号为1至n。机场只有一条起飞跑道,所有的航班需按某个顺序依次起飞(称这个顺序为起飞序列)。定义一个航班的起飞序号为该航班在起飞序列中的位置,即是第几个起飞的航班。
起飞序列还存在两类限制条件:

  • 第一类(最晚起飞时间限制):编号为i的航班起飞序号不得超过ki;
  • 第二类(相对起飞顺序限制):存在一些相对起飞顺序限制(a, b),表示航班a的起飞时间必须早于航班b,即航班a的起飞序号必须小于航班b的起飞序号。

小X思考的第一个问题是,若给定以上两类限制条件,是否可以计算出一个可行的起飞序列。第二个问题则是,在考虑两类限制条件的情况下,如何求出每个航班在所有可行的起飞序列中的最小起飞序号。

输入输出格式

输入格式:
输入文件plane.in第一行包含两个正整数n和m,n表示航班数目,m表示第二类限制条件(相对起飞顺序限制)的数目。
第二行包含n个正整数k1, k2, …, kn。
接下来m行,每行两个正整数a和b,表示一对相对起飞顺序限制(a, b),其中1≤a,b≤n, 表示航班a必须先于航班b起飞。

输出格式:
输出文件plane.out由两行组成。
第一行包含n个整数,表示一个可行的起飞序列,相邻两个整数用空格分隔。输入数据保证至少存在一个可行的起飞序列。如果存在多个可行的方案,输出任意一个即可。
第二行包含n个整数t1, t2, …, tn,其中ti表示航班i可能的最小起飞序号,相邻两个整数用空格分隔。

输入输出样例

输入样例#1:

5 5
4 5 2 5 4
1 2
3 2
5 1
3 4
3 1

输出样例#1:

3 5 1 4 2
3 4 1 2 1

输入样例#2:

5 0
3 3 3 5 5

输出样例#2:

3 2 1 5 4
1 1 1 4 4

说明

【样例说明】
在样例1 中:
起飞序列3 5 1 4 2满足了所有的限制条件,所有满足条件的起飞序列有:
3 4 5 1 2 3 5 1 2 4 3 5 1 4 2 3 5 4 1 2
5 3 1 2 4 5 3 1 4 2 5 3 4 1 2
由于存在(5, 1)和(3, 1)两个限制,航班1只能安排在航班5和3之后,故最早起飞时间为3,其他航班类似。
在样例2 中:
虽然航班4、5没有相对起飞顺序限制,但是由于航班1、2、3都必须安排在前3个起飞,所以4、5最早只能安排在第4个起飞。
【数据范围】
对于30%数据:n≤10;
对于60%数据:n≤500;
对于100%数据:n≤2,000,m≤10,000。

题解

第一问,考虑把限制条件建个图,一定是一个DAG,直接进行拓扑排序,只不过拓扑的队列要改成优先队列,优先级按最晚时间限制从小到大排序,这样出来的顺序就是正确的了。
但是考虑第二问的时候,发现就算建反图DFS还可能出现其他需要满足的条件,例如样例2中前三个飞机必须在前三飞。此时我们考虑倒着找,建反图按限制从大到小的顺序拓扑,我们枚举每个飞机u,每次都拓扑一遍,遇到u的时候直接把它设置为不可以被加入队列中,这样就可以无视掉u的前置,直到条件不能被满足位置。倒着拓扑的意义,实质上是在求满足了u以后剩下最多的点。用n减去这个数字就得到了我们想要的答案。
这个做法的复杂度是O(n^2 \log n)的,在洛谷得开O2才能跑过。

代码

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

#include <algorithm>
#include <queue>
#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];
int deg[MAXN];

inline void addedge(int u, int v) {
    gra[u].push_back(v); deg[v]++;
}

int n, m, lim[MAXN];

struct Node {
    int u, lim;
    inline bool operator<(const Node &rhs) const {
        return lim < rhs.lim;
    }
};

int now[MAXN];
int ans[MAXN];

inline void toposort() {
    std::priority_queue<Node> pq;
    memcpy(now, deg, sizeof(deg));
    for(int i = 1; i <= n; i++) {
        if(!now[i]) pq.push(Node {i, lim[i]});
    }
    int cnt = n;
    while(!pq.empty()) {
        int u = pq.top().u; pq.pop();
        ans[cnt--] = u;
        for(int i = 0; i < gra[u].size(); i++) {
            int v = gra[u][i];
            if(!--now[v]) {
                pq.push(Node {v, lim[v]});
            }
        }
    }
}

inline int toposort1(int uu) {
    std::priority_queue<Node> pq;
    memcpy(now, deg, sizeof(deg));
    now[uu] = n;
    for(int i = 1; i <= n; i++) {
        if(!now[i]) pq.push(Node {i, lim[i]});
    }
    for(int i = n; i; i--) {
        if(pq.empty() || pq.top().lim < i) return i;
        int u = pq.top().u; pq.pop();
        for(int i = 0; i < gra[u].size(); i++) {
            int v = gra[u][i];
            if(!--now[v]) pq.push(Node {v, lim[v]});
        }
    }
}

int main() {
    n = readint(); m = readint();
    for(int i = 1; i <= n; i++) {
        lim[i] = readint();
    }
    for(int i = 1, u, v; i <= m; i++) {
        u = readint(); v = readint();
        addedge(v, u);
    }
    toposort();
    for(int i = 1; i <= n; i++) {
        printf("%d ", ans[i]);
    }
    printf("\n");
    for(int i = 1; i <= n; i++) {
        printf("%d ", toposort1(i));
    }
    return 0;
}
[JSOI2011]任务调度 题解

[JSOI2011]任务调度 题解

题目地址:BZOJ:Problem 5179. — [Jsoi2011]任务调度

题目描述

一台超级计算机共有N颗CPU。现在这台超级计算机有M个任务要做,但同时还要考虑到不能让CPU过热。所幸的是这台超级计算机已经将任务安排好了,现在要做的只是请你根据安排好的指令来模拟它的工作过程。一开始,这N颗CPU都没有被分配任何的任务。之后,会给你以下几类指令(CPU的编号为1到N的整数,任务的编号为1到M的整数)
指令格式 作用
ADD n k w 将 k 号任务(权值为 w)分配给 n 号 CPU
DEC n k w 将 k 号任务的权值减少 w(已知 k 号任务被分配给了 n 号 CPU)
TRANS n1 n2 将分配给 n1 号 CPU 的任务全部转移给 n2 号 CPU
MIN n 输出分配给 n 号 CPU 的任务中权值最小的任务的权值
WORK n w 将分配给 n 号 CPU 的任务中权值最小的任务的权值加上 w,如果权值最小的任务不唯一,则不更改权值,并输出一行“ ERROR”

输入输出格式

输入格式:
包含N+1行。
第1行包含三个正整数N、M、K,分别表示CPU的数目、任务数和指令数。
第2行到K+1行,每行包含一条指令。
N≤500, M≤300000, K≤300000。
保证任务的权值在 32 位有符号整型的范围内。
保证一个任务只会被分配一次(即至多被 ADD 一次)。
保证 ADD 指令、DEC 指令和 WORK 指令中的 w 是非负整数。
保证 TRANS 指令的两个参数不相同。

输出格式:
若干行,其中包括MIN语句的输出和“ERROR”输出,每个输出占一行

输入输出样例

输入样例#1:

2 3 13
ADD 1 2 100
ADD 1 1 90
MIN 1
WORK 1 20
TRANS 1 2
MIN 2
ADD 1 3 105
TRANS 2 1
MIN 1
DEC 1 3 200
MIN 1
DEC 1 1 205
WORK 1 15

输出样例#1:

90
100
100
-95
ERROR

题解

考虑左偏树来做这个题。ADD即新建节点并且合并到该CPU堆。DEC即先删点,改完权值以后再并回去。TRANS即合并两个堆。MIN即询问堆中最小值。WORK同DEC。

代码

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

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

inline char readop() {
    char c;
    while(!isalpha(c = fgc()));
    return c;
}

const int MAXN = 300005;

struct Node {
    int fa, val, dis, ch[2];
} tr[MAXN];

inline int merge(int u, int v) {
    if(!u || !v) return u + v;
    if(tr[u].val > tr[v].val) std::swap(u, v);
    tr[u].ch[1] = merge(tr[u].ch[1], v);
    tr[tr[u].ch[1]].fa = u;
    if(tr[tr[u].ch[0]].dis < tr[tr[u].ch[1]].dis) std::swap(tr[u].ch[0], tr[u].ch[1]);
    tr[u].dis = tr[tr[u].ch[1]].dis + 1;
    return u;
}

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

inline void erase(int p) {
    tr[tr[p].ch[0]].fa = tr[tr[p].ch[1]].fa = 0;
    int q = merge(tr[p].ch[0], tr[p].ch[1]);
    if(q) tr[q].fa = tr[p].fa; if(tr[p].fa) tr[tr[p].fa].ch[!isleft(p)] = q;
    for(int i = tr[p].fa; i; i = tr[i].fa) {
        if(tr[tr[i].ch[0]].dis < tr[tr[i].ch[1]].dis) std::swap(tr[i].ch[0], tr[i].ch[1]);
        if(tr[i].dis == tr[tr[i].ch[1]].dis + 1) break;
        tr[i].dis = tr[tr[i].ch[1]].dis + 1;
    }
    tr[p].fa = tr[p].ch[0] = tr[p].ch[1] = tr[p].dis = 0;
}

inline int poprt(int p) {
    tr[tr[p].ch[0]].fa = tr[tr[p].ch[1]].fa = 0;
    int q = merge(tr[p].ch[0], tr[p].ch[1]);
    tr[p].fa = tr[p].ch[0] = tr[p].ch[1] = tr[p].dis = 0;
    return q;
}

int n, m, k, x, y, z, rt[MAXN];
char op;

int main() {
    tr[0].dis = -1;
    n = readint(); m = readint(); k = readint();
    while(k--) {
        op = readop();
        if(op == 'A') {
            x = readint(); y = readint(); z = readint();
            tr[y].val = z;
            rt[x] = merge(y, rt[x]);
        } else if(op == 'D') {
            x = readint(); y = readint(); z = readint();
            if(!tr[y].fa) rt[x] = poprt(y);
            else erase(y);
            tr[y].val -= z;
            rt[x] = merge(rt[x], y);
        } else if(op == 'T') {
            x = readint(); y = readint();
            rt[y] = merge(rt[x], rt[y]);
            rt[x] = 0;
        } else if(op == 'M') {
            x = readint();
            printf("%d\n", tr[rt[x]].val);
        } else {
            x = readint(); y = readint(); int p = rt[x];
            if((tr[p].ch[0] && tr[tr[p].ch[0]].val == tr[p].val)
                || (tr[p].ch[1] && tr[tr[p].ch[1]].val == tr[p].val)) {
                puts("ERROR");
                continue;
            }
            rt[x] = poprt(p);
            tr[p].val += y;
            rt[x] = merge(rt[x], p);
        }
    }
    return 0;
}