Codeforces Round #507 (Div. 2, based on Olympiad of Metropolises)

题目链接:https://codeforces.com/contest/1040

A - Palindrome Dance

int x[1005];
void test_case() {
    int n, a, b;
    scanf("%d%d%d", &n, &a, &b);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &x[i]);
    int sum = 0;
    for(int i = 1; i <= n / 2; ++i) {
        if(x[i] == 0) {
            if(x[n + 1 - i] == 1) {
                puts("-1");
                return;
            } else if(x[n + 1 - i] == 2)
                sum += a;
        } else if(x[i] == 1) {
            if(x[n + 1 - i] == 0) {
                puts("-1");
                return;
            } else if(x[n + 1 - i] == 2)
                sum += b;
        } else {
            if(x[n + 1 - i] == 0)
                sum += a;
            else if(x[n + 1 - i] == 1)
                sum += b;
            else
                sum += 2 * min(a, b);
        }
    }
    if(n % 2 == 1 && x[(n + 1) / 2] == 2)
        sum += min(a, b);
    printf("%d\n", sum);
}

B - Shashlik Cooking

题意:给一个长度为 \(n\) 的01串,初始全为0。给定长度 \(k\) ,每次选一个位置 \(x\) ,翻转包含 \(x\) 在内的 \([x-k,x+k]\) ,翻转最少的次数使得全部为1。

题解:单次翻转可以翻转 \(2k+1\) 个位置,所以最少需要 \(\lceil\frac{n}{2k+1}\rceil\) 次。设 \(c=\lceil\frac{n}{2k+1}\rceil\) ,那么最长是 \(c(2k+1)\) ,最短是 \((c-2)(2k+1)+2(k+1)\) ,恰好覆盖,所以中间一定存在一种构造。下面是字典序最小的构造。

void test_case() {
    int n, k;
    scanf("%d%d", &n, &k);
    int c = (n + (2 * k + 1 - 1)) / (2 * k + 1);
    int b;
    for(b = 1;; ++b) {
        if(b + k + (c - 1) * (2 * k + 1) >= n)
            break;
    }
    printf("%d\n", c);
    for(int i = 1; i <= c; ++i)
        printf("%d%c", b + (i - 1) * (2 * k + 1), " \n"[i == c]);
}

*C - Timetable

这个C题特别有收获。

题意:有一个长度为 \(n(1\leq n \leq 200000)\) 的严格单调上升的正整数序列 \(a(1\leq a_i \leq 10^{18})\) ,和一个大正整数 \(t(1\leq t \leq 10^{18})\) 。又给一个长度为 \(n\) 的序列 \(x\) ,其中 \(x_i\) 表示 \(a_i\) 最大能够匹配的是 \(b_{x_i}\)\(a_i\)\(b_j\) 匹配是指 \(a_i+t\leq b_j\) 。要求构造单调递增的序列 \(b\)

错误算法1:首先序列 \(x\) 应该是非严格单调上升的,因为一个出发更晚的车可以选的范围肯定是只有右边的一段。所以先验证序列 \(x\) 合法。然后有个猜测,就是连续相同的一段 \([x_i,x_j]\) 是可以互换的,要求他们可以互换则要 \(b_i\geq a_j+t\) ,注意这些都要让 \(a_{j+1}\) 到不了(不参与互换),因为假如 \(a_{j+1}\) 能到的话,可以让 \(a_{j+1}\)\(b_{j}\)\(a_{j}\) 可以去 \(b_{j+1}\) ,与 \([x_i,x_j]\) 是最长的连续矛盾。

所以还要让 \(b_j<a_{j+1}+t\)

如:

3 10
4 6 8
2 2 3

可恰好构造为:

16 17 18

这样一来 8 不能到 16 和 17 ,就只能去 18 ,18 被 8 占领了之后 4 和 6 当然就不能去 18 ,要保证 4 和 6 可互换所以第一个数最小要构造为 6+10=16 ,而这时候恰好可以放下一个 17 。总之就是要让 17 不能被 8 到,所以这段要尽可能小。

所以就按尽可能小来构造,然后验证每段 \([x_i,x_j]\) 是不能到达前一段的。

但是很可惜这个算法是有问题的,首先要加一个验证就是 \(x_i>=i\) ,然后会在这组数据翻车:

10 200
100 101 103 104 106 107 109 111 113 114
2   2   4   4   7   7   7   8   12  12

输出

Yes
301 302 304 305 309 310 311 311 314 315

错误算法2:发现问题就是让311重复了,原因是 106~109 这段是从 309 开始构造的,但是实际上可以从 308 开始构造,因为“连续相同的一段 \([x_i,x_j]\) 是可以互换的”是个假命题,实际上是不需要互换的,只需要循环左移就可以让 106 取得 x=7 。所以这个连续段中除去最后一个的每个 \(b_i\) 只需让 \(a_{i+1}\) 能到就行。

不知道为什么错。

ll a[200005];
int x[200005];
ll b[200005];

void test_case() {
    int n;
    ll t;
    scanf("%d%lld", &n, &t);
    for(int i = 1; i <= n; ++i)
        scanf("%lld", &a[i]);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &x[i]);

    for(int i = 2; i <= n; ++i) {
        if(x[i] < x[i - 1] || x[i] < i) {
            puts("No");
            return;
        }
    }
    for(int i = 1, nxt; i <= n; i = nxt) {
        for(nxt = i + 1; nxt <= n && x[nxt] == x[i]; ++nxt);
        for(int j = i; j < nxt - 1; ++j)
            b[j] = a[j + 1] + t;
        if(nxt <= n)
            b[nxt - 1] = a[nxt] + t - 1;
        else
            b[n] = b[n - 1] + 1;
    }
    for(int i = 2; i <= n; ++i) {
        if(b[i] <= b[i - 1]) {
            puts("No");
            return;
        }
    }
    puts("Yes");
    for(int i = 1; i <= n; ++i)
        printf("%lld%c", b[i], " \n"[i == n]);
}

错误算法3:首先要验证几个命题。

命题1:对所有的 \(i\) 满足 \(1\leq i \leq n-1\) ,都满足 \(x_i \leq x_{i+1}\) 。简单来说就是序列 \(x\) 非降序。

证明:反证法,若存在某些 \(i\) 满足 \(1\leq i \leq n-1\) ,但不满足 \(x_i \leq x_{i+1}\)

\(i\) 是第一个不满足的位置,那么有 \(x_{i}>x_{i+1}\) 。由题目输入限制显然有 \(i>1\) ,由假设可知 \(a_{i-1}\) 能够到达 \(b_{i-1}\)\(a_{i-1}+t\leq b_{i-1}\) ;且 \(a_{i}\) 不能够到达 \(b_{i}\)\(a_{i}+t > b_{i}\) ,但可以到达某个 \(b_{j}<b_{i}\)\(a_{i}+t \leq b_{j}\) 。所以有 \(a_{i}+t \leq b_{j} < b_{i}\)\(a_{i}+t < b_{i}\)\(a_{i}+t > b_{i}\) 矛盾。

命题2:对所有的 \(i\) 满足 \(1\leq i \leq n\) ,都满足 \(x_i \geq i\)

证明:反证法,若存在某些 \(i\) 满足 \(1\leq i \leq n\) ,但不满足 \(x_i \geq i\)

\(i\) 是第一个不满足的位置,那么有 \(x_{i}<i\) 。由题目输入限制显然有 \(i>1\) ,这个 \(i\) 要占用 \([1,i-1]\) 的其中一个,那么前面就必定有一个要去到 \([i,n]\) ,这样就与命题1矛盾。

命题3:某个连续相等的 \(x\) 段,使得序列 \(b\) 尽可能小的构造是循环左移

直观感觉?要使得第一个位置L能够换去最后一个位置R,那么R要换去[L,R-1],这时只换去R-1是最好的。这样除了最后一个位置以外,每个位置只受到 \(b_i\geq a_{i+1}+t\) 的限制,其他的置换限制会更多。

命题4:两个相邻的连续相等的 \(x\) 段,断层处必定满足 \(b_{x_{1R}}<a_{x_{2L}}+t\)

证明:否则 \(x_{2L}\) 就可以来 \(x_{1R}\) 了,而 \(x_{1R}\) 肯定也能去 \(x_{2L}\) ,这样就不是断层了。

所以最末尾的 \(b_n\) 几乎不受任何限制,取最大值,然后每次断层会导致 \(b_i\) 突然下降。同时连续段内的除了最后一个元素之外的 \(b_i\) 还要受到 \(b_i \geq a_{i+1}+t\) 的限制用来循环左移,非连续段内(也就是断层)就是 \(b_i \geq a_i+t\)\(b_i < a_{i+1}+t\) 。后者出现矛盾时无解。

题解:理解错题意了,根本不是这个意思。只有两种位置:1、 \(x_i=i\) 的位置,即连续的段的末尾。这个时候若后面还有数,则不能从 \(a_{i+1}\) 转移,为了让 \(b\) 尽可能递增所以贪心取为 \(a_{i+1}+t-1\) ;否则后面没有数,可以取为 \(a_{n}+t+1\) 到无穷大之间的数 (一定要+1,因为倒数第二个数假如是type2的话就会相等)。2、其他位置。那么后面必定有对应长度的连续段,只需要下一班车 \(i+1\) 可以停过来就可以了,为了让 \(b\) 尽可能递增取为 \(a_{i+1}+t\) 。这样取完之后能够保证每个数至少能够取到 \(x_i\) ,但是不能保证 \(b\) 一定是递增的也不能保证恰好能取到 \(x_i\) (有可能会继续往右边延伸,因为这样构造的 \(b\) 有可能有位置是相等的,也有可能 \(b\) 是严格单调递增的,但是 \(a_i\) 确实被某些 \(>x_i\) 的位置蔓延到)

终于想明白这道题的本质了!

某个位置假如能够被后车占了,则这个位置是“可被后车占领位置”,自己就可以把车开到这一段“可被后车占领位置”的最后一个的下一个位置(这些“可被后车占领位置”逐个被后车占领,自己开去最后一个);假如某个 \(x_i \neq i\) 那么这个位置就必须是连续位置,至少要有 \(b_i \geq a_{i+1}+t\) ;这样只能保证自己是一个可以往后开的,但是能开多远是 无法保证 的。要在最后确认。

否则,这个位置不能被后车占了,那么必须有 \(b_i < a_{i+1}+t\) ,即至多 \(b_i \leq a_{i+1}+t-1\)

由贪心法,是从左往右构造,对于每个“至少”的条件要恰好满足,而“至多”的条件取满的就不容易和“至少”的条件碰在一起。

ll a[200005];
int x[200005];
ll b[200005];

void test_case() {
    int n;
    ll t;
    scanf("%d%lld", &n, &t);
    for(int i = 1; i <= n; ++i)
        scanf("%lld", &a[i]);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &x[i]);

    for(int i = 1; i <= n; ++i) {
        if(x[i] < x[i - 1] || x[i] < i) {
            puts("No");
            return;
        }
    }

    b[n] = a[n] + t + 1;
    for(int i = n - 1; i >= 1; --i) {
        b[i] = a[i + 1] + t - (x[i] == i);
        assert(b[i] <= b[i + 1]);
        if(b[i] == b[i + 1]) {
            puts("No");
            return;
        }
    }
    for(int i = n, lst = n; i >= 1; --i) {
        assert(x[i] <= lst);
        if(x[i] != lst) {
            puts("No");
            return;
        }
        if(b[i - 1] - a[i] < t)
            lst = i - 1;
    }

    puts("Yes");
    for(int i = 1; i <= n; ++i)
        printf("%lld%c", b[i], " \n"[i == n]);
}

D - Subway Pursuit

题意:地铁铁轨是一个长度为 \(n(1\leq n \leq 10^{18})\) 的数轴,在上面找一辆失控的车,它每次都会出现在整数位置。每次询问可以问一个区间,jury回答车是否在区间里面,注意车每次会向左或者向右移动至多 \(k(0\leq k \leq 10)\) 个单位长度,不会越界,询问至多4500次。

题解:先用大概60~70次“二分”就可以定位车在一个长度大约 \(4k\) 的区间里,然后随机抽一个数字进行询问。开始询问的条件应该是再“二分”不会使得区间有显著减少时,也就是 \(\frac{len}{2}+2k\geq len\) ,简单起见可以直接取50。注意到每个数平均会被问40次(在问一次之后下一次一般就要进行“二分”,所以有一半的次数在“二分”),所以不成功的概率应该不高。

char s[1005];
bool query(ll l, ll r) {
    printf("%lld %lld\n", l, r);
    fflush(stdout);
    scanf("%s", s);
    if(s[0] == 'Y')
        return 1;
    return 0;
}

void test_case() {
    srand(time(0));
    ll n;
    int k;
    scanf("%lld%d", &n, &k);
    ll L = 1, R = n, C = 4 * k + 4;
    while(1) {
        if(R - L + 1 >= C) {
            ll M = (L + R) / 2;
            if(query(L, M)) {
                L = max(1ll, L - k);
                R = min(n, M + k);
            } else {
                L = max(1ll, M + 1 - k);
                R = min(n, R + k);
            }
        } else {
            int rnd = rand() % (R - L + 1);
            if(query(L + rnd, L + rnd))
                return;
            else {
                L = max(1ll, L - k);
                R = min(n, R + k);
            }
        }
    }
}
posted @ 2020-01-31 16:45  KisekiPurin2019  阅读(151)  评论(0编辑  收藏  举报