Educational Codeforces Round 4

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

A - The Text Splitting

题意:给出n,x,y,求一组解(a,b),使得a和b都是自然数,且ax+by=n。

题解:数据量太小,枚举即可,否则可以使用扩展欧几里得算法,算出一组特解,然后根据通解的构造规律尝试构造两个数都是自然数的一组通解。

B - HDD is Outdated Technology

比A题更无聊。

int pos[200005];

void test_case() {
    int n;
    scanf("%d", &n);
    for(int i = 1, x; i <= n; ++i) {
        scanf("%d", &x);
        pos[x] = i;
    }
    ll sum = 0;
    for(int i = 2; i <= n; ++i)
        sum += abs(pos[i] - pos[i - 1]);
    printf("%lld\n", sum);
}

C - Replace To Make Regular Bracket Sequence

题意:有4种括号,其中一次修改可以把一种左括号换成任意一种左括号,或者把一种右括号换成任意一种右括号。问最少的操作次数使得整个串合法。

题解:贪心,每次都修改必须修改的右括号。

char s[1000005];
char stk[1000005];
int top;

void test_case() {
    scanf("%s", s + 1);
    int n = strlen(s + 1);
    if(n % 2 == 1) {
        puts("Impossible");
        return;
    }
    int cnt = 0;
    for(int i = 1; i <= n; ++i) {
        if(s[i] == '(' || s[i] == '<' || s[i] == '[' || s[i] == '{')
            ++cnt;
        else {
            --cnt;
            if(cnt < 0) {
                puts("Impossible");
                return;
            }
        }
    }
    if(cnt != 0) {
        puts("Impossible");
        return;
    }
    top = 0;
    int ans = 0;
    for(int i = 1; i <= n; ++i) {
        if(s[i] == '(' || s[i] == '<' || s[i] == '[' || s[i] == '{')
            stk[++top] = s[i];
        else {
            if(stk[top] == '(' && s[i] == ')' || stk[top] == '<' && s[i] == '>'
                    || stk[top] == '[' && s[i] == ']' || stk[top] == '{' && s[i] == '}')
                ;
            else
                ++ans;
            --top;
        }
    }
    printf("%d\n", ans);
}

*D - The Union of k-Segments

题意:x轴上有n条线段,某些点是“满意的”,当且仅当其被至少k条线段覆盖。找一个最小的线段集合,使得所有“满意的”的点都在其中,而没有其他的点。

题解:阅读理解?还好我技高一筹,这里题目要找的“线段集合”并不是输入给的线段。假如坐标比较小可以直接差分。不过这个思路给了个启发:直接分别左端点排序和右端点排序,然后双指针推一推。

不过细节还是蛮多的。最后是用三个指针实现的。双指针分别指向最左的左端点和最左的右端点,取出最小的那个更新cur。根据离散介值定理恰好在更新某点时cnt==k,把这时的cur(一定有cur等于某个左端点)作为答案的左端点。要注意仅1个点的线段也是线段,所以重合的点中要处理完所有的左端点再处理右端点。否则要么左端点已经用完了,要么最左的左端点还比右端点大,这时必须取一个右端点更新cur,更新结束之后再把cnt-1,然后在cnt-1之前判断cnt是否恰好为k,若此时恰好为k则包含cur在内的这条线段就是答案的右端点。

别开小数组,数清楚有几个零。

int l[1000005];
int r[1000005];
int ansl[1000005];
int ansr[1000005];
int atop;

void test_case() {
    int n, k;
    scanf("%d%d", &n, &k);
    for(int i = 1; i <= n; ++i)
        scanf("%d%d", &l[i], &r[i]);
    sort(l + 1, l + 1 + n);
    sort(r + 1, r + 1 + n);
    atop = 0;
    int p1 = 1, p2 = 1;
    int cnt = 0, cur = -INF, L = -INF;
    while(1) {
        if(cur == r[n]) {
            if(cnt >= k) {
                ++atop;
                ansl[atop] = L;
                ansr[atop] = r[n];
            }
            break;
        }
        if(p1 <= n && l[p1] <= r[p2]) {
            cur = l[p1];
            ++p1;
            ++cnt;
            if(cnt == k)
                L = cur;
            continue;
        }
        //p1>n||l[p1]>r[p2]
        cur = r[p2];
        ++p2;
        if(cnt == k) {
            ++atop;
            ansl[atop] = L;
            ansr[atop] = cur;
        }
        --cnt;
    }
    printf("%d\n", atop);
    for(int i = 1; i <= atop; ++i)
        printf("%d %d\n", ansl[i], ansr[i]);
}

标签:双指针

官方题解有更简单的做法,把左右端点分别设置为两个事件,然后一起排序,位于同一个端点的事件,左端点优先,这样就不需要双指针来比较下一个事件是左端点还是右端点了。

启示:这种题目,区间端点会发生事件是显然的,以后不要把几种事件分别排序然后弄几个指针比来比去,直接放在一起排序。

E - Square Root of Permutation

题意:给一个排列,求他的其中一个平方根。设两个排列p和q,称p=q^2,当对于所有i,都有p[i]=q[q[i]]成立。一个排列可以没有平方根,也可能有多个平方根。多个平方根输出任意一个。

这个排列套排列的形式让人联想到置换群,但是不知道具体怎么搞。打了个表,没发现什么规律。

int p[15], q[15];

bool check(int n) {
    for(int i = 1; i <= n; ++i) {
        if(q[q[i]] != p[i])
            return false;
    }
    return true;
}

void test_case() {
    for(int n = 2; n <= 4; ++n) {
        for(int i = 1; i <= n; ++i)
            p[i] = i;
        do {
            printf("p=\n ");
            for(int i = 1; i <= n; ++i)
                printf("%d%c", p[i], " \n"[i == n]);

            for(int i = 1; i <= n; ++i)
                q[i] = i;
            int suc = 0;
            do {
                if(check(n)) {
                    printf("q=\n ");
                    for(int i = 1; i <= n; ++i)
                        printf("%d%c", q[i], " \n"[i == n]);
                    suc = 1;
                }
            } while(next_permutation(q + 1, q + 1 + n));
            if(!suc)
                puts("-1");
            puts("---\n");
        } while(next_permutation(p + 1, p + 1 + n));
    }
}

题解:貌似这种类似置换群/排列嵌套的题目都可以转换成一个有向图:考虑一个排列q,对于每个q[i],连接有向边<i,q[i]>。由于这是一个排列,所以终点是互不相同的,得到的不是基环树(或者说,非常特殊的基环树,没有树的部分),而是几个互相分离的环。显而易见某个排列q就是每个点i沿图上走一步。排列q^2就是每个点沿图上走两步。假如某个图走两步的结果就是p,那么这个图是其中一种答案。

既然看到了这里,可以观察到一个显然的事实,无论走多少步,都是环上的点自己在走,

对某个环进行“走两步压缩”,奇数长度的环会得到:
1->2->3->4->5(->1)变成1->3->5->2->4(->1)。
偶数长度的环会得到:
1->2->3->4(->1)变成1->3(->1)和2->4(->2)。

意思就是奇数长度的会发生轮换,偶数长度的会分裂成两个等大的。

那么求出排列p的这个图,也是得到了几个环。那么奇数长度的环就可以直接构造出来,或者和一个等大的奇数长度的环合并成一个大的偶数长度环,比如:
1->2->3->4->5(->1),构造答案为1->4->2->5->3(->1),构造的顺序是,先把1放在最后面(第6个数),然后把1的前一个数5放在第4个数,再把5的前一个数放在第2个数……

偶数长度的就只能够和等大的环合并成更大的偶数长度环了,直接交错即可。

int n;
int nxt[1000005];
bool vis[1000005];

int dfs(int u, int siz) {
    vis[u] = 1;
    if(vis[nxt[u]] == 0)
        return dfs(nxt[u], siz + 1);
    return siz;
}

int q[1000005];

void Merge(int r1, int r2, int siz) {
    while(siz--) {
        q[r1] = r2;
        r1 = nxt[r1];
        q[r2] = r1;
        r2 = nxt[r2];
    }
}

int tmp[1000005], top;

void Solve(int r, int siz) {
    top = 0;
    while(siz--) {
        tmp[++top] = r;
        r = nxt[r];
    }
    tmp[top + 1] = tmp[1];
    int mid = (top + 1) / 2;
    for(int j = 1, i = mid + 1; i <= top + 1; ++j, ++i) {
        q[tmp[j]] = tmp[i];
        q[tmp[i]] = tmp[j + 1];
    }
}

pii SizID[1000005];
int stop;

void test_case() {
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &nxt[i]);

    stop = 0;
    for(int i = 1; i <= n; ++i) {
        if(vis[i] == 0) {
            int siz = dfs(i, 1);
            SizID[++stop] = {siz, i};
        }
    }

    sort(SizID + 1, SizID + 1 + stop);

    for(int i = 1; i <= stop; ++i) {
        if(SizID[i].first % 2 == 1)
            Solve(SizID[i].second, SizID[i].first);
        else {
            if(i + 1 <= stop && SizID[i].first == SizID[i + 1].first) {
                Merge(SizID[i].second, SizID[i + 1].second, SizID[i].first);
                ++i;
            } else {
                puts("-1");
                return;
            }
        }
    }

    for(int i = 1; i <= n; ++i)
        printf("%d%c", q[i], " \n"[i == n]);
}

好像观察到奇偶性貌似并不影响构造,假如把两个偶数串前后连接,也可以使用奇数的构造方法。

posted @ 2020-03-12 10:52  KisekiPurin2019  阅读(163)  评论(0编辑  收藏  举报