【解题总结】NWERC 2018(Codeforces Gym 102483)

我解决的:K、B(得知算法后写出)、J(2 WA)。

没看的:D(不会做)。

我旁观的:A、C、H、I、G、E(我可能能做出来,但是估计要写一会)。

看了不会做的:F。

K Kleptography

简单题,略。我还想了一会才写出来,代码能力属实不行。

I Inflation

简单题,略。

H Hard Drive

应该是简单题?略。

J Jinxed Betting

题意(转化后):给定一些数,每次可以对这些数进行操作,一次操作为:设最大的数有 a a 个,将最大的 a 2 \lfloor \frac{a}{2} \rfloor 个数,以及其他所有非最大的数均加 1。再给定一个目标 t t ,问至多操作几次,可以保证这些数中的最大者不超过 t t

手算一下可以发现,如果某一时刻最大的数 x 1 x_1 a 1 a_1 个,第二大的数 x 2 x_2 a 2 a_2 个,那么 ( x 1 x 2 ) ( log 2 a 1 + 1 ) (x_1 - x_2) (\lfloor \log_2 a_1 \rfloor + 1) 轮后最大的数变成了 a 1 + a 2 a_1 + a_2 个,即原本第二大的数全部长成了最大。

于是我们可以模拟这种合并的过程,通过一些简单的计算就可以得到答案。时间复杂度为 O ( n log n ) O(n \log n) ,因为要排序。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
ll targ, a[100005];
stack<pair<ll, int> > st;
int logg[100005];
int main(){
    scanf("%d%lld", &n, &targ);
    --n;
    for (int i = 1; i <= n; ++i)
        scanf("%lld", &a[i]);
    sort(a + 1, a + n + 1);
    for (int i = 1, j; i <= n; i = j){
        j = i;
        while (j <= n && a[i] == a[j])
            ++j;
        st.push(make_pair(a[i], j - i));
    }

    logg[1] = 1;
    for (int i = 2; i <= n; ++i)
        logg[i] = logg[i >> 1] + 1;
    
    ll tot_round = 0;
    // 由于不能批量加法,因此认为每一个数的实际值要加上 tot_round
    while (st.size() > 1){
        ll ori_x1 = st.top().first + tot_round, x1 = ori_x1;
        int a1 = st.top().second;
        st.pop();
        ll x2 = st.top().first + tot_round;
        int a2 = st.top().second;
        st.pop();

        ll round_needed = (x1 - x2) * logg[a1];
        ll x1_added = round_needed - (x1 - x2);
        x1 += x1_added;
        if (x1 > targ){
            // 在这里停下,结算
            ll round_before = ((targ - ori_x1) / (logg[a1] - 1)) * logg[a1];
            ll ori_x1_added = ((targ - ori_x1) / (logg[a1] - 1)) * (logg[a1] - 1);
            printf("%lld\n", tot_round + round_before + 
                (targ - ori_x1 - ori_x1_added));
            return 0;
        } else {
            tot_round += round_needed;
            x1 -= tot_round;
            st.push(make_pair(x1, a1 + a2));
        }
    }

    ll x1 = st.top().first + tot_round;
    ll round_before = ((targ - x1) / (logg[n] - 1)) * logg[n];
    ll x1_added = ((targ - x1) / (logg[n] - 1)) * (logg[n] - 1);
    printf("%lld\n", tot_round + round_before + 
        (targ - x1 - x1_added));
    return 0;
}

B Brexit Negotiations

题意:给一个 DAG,最小化:每个点点权与其在拓扑序内的编号之和的最大值。

反向建图,然后拓扑排序即可。用一个堆代替队列,每次取出点权最小的点进行扩展。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, d[400005], du[400005] = {0};
vector<int> G[400005];
priority_queue<pair<int, int>, vector<pair<int, int> >, 
    greater<pair<int, int> > > pq;
int main(){
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i){
        int t, to;
        scanf("%d%d", &d[i], &t);
        for (int j = 1; j <= t; ++j){
            scanf("%d", &to);
            G[i].push_back(to);
            ++du[to];
        }
    }
    for (int i = 1; i <= n; ++i){
        if (!du[i]) pq.push(make_pair(d[i], i));
    }
    int ans = 0, tot = n;
    while (!pq.empty()){
        int h = pq.top().second;
        pq.pop();
        --tot;
        ans = max(ans, d[h] + tot);
        for (int v: G[h]){
            --du[v];
            if (!du[v]) pq.push(make_pair(d[v], v));
        }
    }
    printf("%d\n", ans);
    return 0;
}

E Equality Control

题意:给定两个经某些操作得到的序列,操作有拼接、shuffle、排序等。问最后得到的两个序列是否有相同的概率分布。

写一个 parser,然后比对两个序列是否相同即可。发现有三点:

  1. 如果是 shuffle 或者排序,那么这两个操作造成的影响不会交叉,且外层的影响会覆盖内层的影响。例如 shuffle 一个部分排序的序列,内部的排序就没有任何用处了。
  2. shuffle 只含一种元素的序列不会有任何影响,可以认为没有这个操作。
  3. shuffle 后的分布相同当且仅当 shuffle 的对象完全相同。
#include <bits/stdc++.h>
#define MOD 1000000007
using namespace std;
inline int read(int& l, char *s){
    int x = 0;
    char c = s[l];
    while (c >= '0' && c <= '9')
        x = x * 10 + c - '0', c = s[++l];
    return x; 
}
char s1[1000005], s2[1000005];
int l1, l2;
int lis1[1000005], lis2[1000005], tot1, tot2;
int r1[1000005][2] = {0}, r2[1000005][2] = {0};
int ps(int l, int tp, int& tot, char s[], int lis[], int r[][2]){
    if (s[l] == '['){
        // atomic
        ++l;
        for (; ; ){
            lis[++tot] = read(l, s);
            if (s[l] == ']') break;
            else ++l;
        }
        return l;
    }else if (s[l] == 's'){
        // sorted or shuffle
        int old_tot = tot;
        int new_tp = (s[l + 1] == 'h' ? 2: 1);
        int rp = ps(l + 6 + new_tp, tp == 0 ? new_tp : tp, tot, s, lis, r);
        // r: 操作作用域,于左端点记录该操作的右端点
        if (tp == 0){
            r[old_tot + 1][0] = tot;
            r[old_tot + 1][1] = new_tp;
        }
        return rp + 1;
    }else{
        // concat
        int rp = ps(l + 7, tp, tot, s, lis, r);
        int rp2 = ps(rp + 2, tp, tot, s, lis, r);
        return rp2 + 1;
    }
    return 0;
}
void deal(int tot, int lis[], int r[][2]){
    for (int i = 1; i <= tot; ++i){
        if (r[i][1] != 0){
            sort(lis + i, lis + r[i][0] + 1);
            if (r[i][1] == 1) r[i][0] = r[i][1] = 0;
            else {
                bool flag = false;
                for (int j = i + 1; j <= r[i][0]; ++j)
                    if (lis[j] != lis[j - 1]){
                        flag = true;
                        break;
                    }
                if (!flag) r[i][0] = r[i][1] = 0;
            }
        }
    }
}
void init(){
    scanf("%s%s", s1, s2);
    l1 = strlen(s1), l2 = strlen(s2);

    tot1 = tot2 = 0;
    ps(0, 0, tot1, s1, lis1, r1), deal(tot1, lis1, r1);
    ps(0, 0, tot2, s2, lis2, r2), deal(tot2, lis2, r2);
}
void solve(){
    if (tot1 != tot2){
        printf("not equal\n");
        return ;
    }
    for (int i = 1; i <= tot1; ++i){
        if (r1[i][1] != 0 || r2[i][1] != 0){
            // shuffle?
            if (r1[i][1] == 0 || r2[i][1] == 0 || r1[i][0] != r2[i][0]){
                printf("not equal\n");
                return ;
            }
            int rb = r1[i][0];
            while (i <= rb){
                if (lis1[i] != lis2[i]){
                    printf("not equal\n");
                    return ;
                }
                ++i;
            }
            --i;
        }else {
            if (lis1[i] != lis2[i]){
                printf("not equal\n");
                return ;
            }
        }
    }
    printf("equal\n");
}
int main(){
    init();
    solve();
    return 0;
}

C Circuit Design

题意:给定一个树,将其画在二维平面上,要求边不能交叉,边的欧几里得长度固定为 1,点之间不能离得太近。

直接从原点向外面扩展就行了,每一个子树按照子树大小划分角度范围即可。由于只有 1000 个点,因此点之间的距离可以得到保证。

A Access Points

题意:给定平面上 n n 个点 p 1 , , p n p_1, \cdots, p_n ,找到 n n 个点 q 1 , , q n q_1, \cdots, q_n ,使得对于任意 i i q i q_i 的横坐标不大于 q i + 1 q_{i+1} 的横坐标、 q i q_i 的纵坐标不大于 q i + 1 q_{i+1} 的纵坐标,且最小化 i = 1 n p i q i 2 2 \sum_{i=1}^{n} \| p_i - q_i \|_2^2

可以单独对横、纵坐标分别求解,然后把答案加起来。这样就转化为了一维的情况。

一维的问题是:给定 a 1 , , a n a_1, \cdots, a_n ,找出 x 1 x n x_1 \le \cdots \le x_n ,最小化 i = 1 n ( a i x i ) 2 \sum_{i=1}^{n} (a_i - x_i)^2

会发现:当 x x 全部取相同值时, x x a a 的均值最优。然后结论就是:最优的 x x 是成段的,每一段中 x x 的值都是 a a 中同位置一段的平均值

说实话,这个结论我是不知道怎么推出来的。

这个可以用一个单调栈维护。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, x[100005], y[100005];
ll sum[100005];
int cnt[100005], top;
inline double sqr(double xx){
    return xx * xx;
}
double calc(int a[]){
    top = 0;
    for (int i = 1; i <= n; ++i){
        int cur_cnt = 1;
        ll cur_sum = a[i];
        while (top > 0 && sum[top] * cur_cnt > cur_sum * cnt[top]){
            cur_cnt += cnt[top];
            cur_sum += sum[top];
            --top;
        }
        sum[++top] = cur_sum, cnt[top] = cur_cnt;
    }
    double res = 0;
    for (int t = 1, i = 1; t <= top; ++t){
        double val = sum[t] / (1.0 * cnt[t]);
        for (int j = 1; j <= cnt[t]; ++j, ++i)
            res += sqr(val - a[i]);
    }
    return res;
}
void init(){
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d%d", &x[i], &y[i]);
}
void solve(){
    double res_x = calc(x);
    double res_y = calc(y);
    printf("%.20lf\n", res_x + res_y);
}
int main(){
    init();
    solve();
    return 0;
}

G Game Design

题意:给定一个小球的移动序列,需要构造一个迷宫的方案,使得小球按照你自行指定的起点按照移动序列移动后到达 ( 0 , 0 ) (0, 0)

首先要发现:无解的充要条件是末尾三个移动是 LRLUDU 这种形式。

然后从前往后构造,每次让迷宫的边界扩大一定的大小(如 2)。如果一次向一个方向移动后又要向反方向移动就不扩大迷宫。最后将到达点和所有障碍物平移,使得到达点变成 ( 0 , 0 ) (0, 0) 即可。

std 在移动方向的两侧也构建了障碍物,我觉得这个做法没有上面的好。

本题从后往前构造似乎会比较麻烦。

#include <bits/stdc++.h>
using namespace std;
char s[25];
vector<pair<int, int> > ans;
int mp[300] = {0};
int main(){
    scanf("%s", s);
    int l = strlen(s);
    mp['L'] = mp['R'] = 0;
    mp['U'] = mp['D'] = 1;
    if (l >= 3 && s[l - 1] == s[l - 3] && mp[s[l - 1]] == mp[s[l - 2]]){
        printf("impossible\n");
        return 0;
    }

    int bd = 3, x = 0, y = 0;
    for (int i = 0; i < l; ++i){
        if (s[i] == 'L') x = -bd, ans.emplace_back(x - 1, y);
        if (s[i] == 'R') x = bd, ans.emplace_back(x + 1, y);
        if (s[i] == 'U') y = bd, ans.emplace_back(x, y + 1);
        if (s[i] == 'D') y = -bd, ans.emplace_back(x, y - 1);
        if (i + 1 < l && mp[s[i]] != mp[s[i + 1]])
            bd += 2;
    }
    sort(ans.begin(), ans.end());
    printf("%d %d\n", -x, -y);
    int n = unique(ans.begin(), ans.end()) - ans.begin();
    printf("%d\n", n);
    for (int i = 0; i < n; ++i)
        printf("%d %d\n", ans[i].first - x, ans[i].second - y);
    return 0;
}

F Fastest Speedrun

待补。。。

D Date Pickup

待补。。。

猜你喜欢

转载自blog.csdn.net/zqy1018/article/details/108129454