信息学奥赛一本通1738:圆桌聚会

原题链接:https://ybt.ssoier.cn/problem_show.php?pid=1738

【题目描述】
同学们举行了圆桌聚会,Capo所在的信息组的男女比例达到了1:1, n 个男生和n个女生,男生编号为0,2,4,...,2n−2 ,女生编号为1,3,5,...,2n−1 ,他们按照编号坐成一圈,Capo的老师有个要求,编号为x的顺时针方向下一个人编号必须是2x,2x+1,2x−2n,2x+1−2n中的一个。

现在请你告诉Capo该如何坐座位。

【输入】
多组数据,每行一个整数n ,如题意所述。

【输出】
每行输出一组数据,输出空格隔开的2n
个整数,表示顺时针方向圆桌上每个人的编号。

请使用尽可能快速的输出方式。

【输入样例】
1
4
【输出样例】
0 1
0 1 2 5 3 7 6 4
【提示】
【数据规模与约定】

对于前10%的数据:n≤5;

对于前20%的数据:n≤10 ;

对于前30%的数据:n≤20 ;

对于额外15%的数据:n=2^k,k≤10;

对于额外15%的数据:n=2^k,k≤18 ;

对于100%的数据:1≤n≤5×105。

本题有Special Judge。

讲这题之前有时间的读者可以做一下https://leetcode.cn/problems/cracking-the-safe/description/?envType=problem-list-v2&envId=eulerian-circuit
这两题很相似,明白了上题对做这题很有帮助

首先,2x,2x+1,2x−2n,2x+1−2n这四个数,很显然,我们可以把他们转换成 2x mod 2n 和 2x+1 mod 2n,而(2x+1)mod 2n = (2x) mod 2n + 1,为形式统一,2x mod 2n = 2x mod 2n + 1;

这里题目的特殊数据和我们推出来的东西都提醒我们,要用二进制的方法思考,不难发现,上面的两个转移的结果,都是 (2x mod 2n) + 1or0,那么不妨把x的连边看做点(2x mod 2n) 连上边权为 0or1 的边,用一个点+一条边表示一个数,这其实就建立了一个x到(2x) mod 2n的映射,那么我们这条边的终点就是(2(2x mod 2n) + 1or0) mod 2n,也就是映射后的点。

但是我们这时发现会有n个点,4*n条边,多了一倍的边怎么回事?如果你打印了这个图会发现,前n个点的连边和后n个点的连边是完全一样的!那么我们就可以大胆的把后一半的边全部删掉,这样就能正常运行了

这样在这图里就有2*n条边,每个边代表一个不同数字,直接跑Hierholzer,找欧拉路径,然后要注意这里ans记录的是边的边权,也就是01值

就不配图了,读者可以自己手写一下

其实这里应该证明一下正确性的,但是我是个蒟蒻,而且这种不就跟贪心一样,需要用自然智慧想明白的吗?(其实就是我懒)

最后看一下code,不理解的去看上面我放的题,里面的题解比我讲的好,我就不啰嗦了

#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<cstring>
#include<set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const int mod = 1000000007;
const int N = 1000001;

int read() {
    int k = 0;
    char c = getchar();
    while (c < '0' || c>'9')c = getchar();
    while (c >= '0' && c <= '9')k = (k << 1) + (k << 3) + (c ^ 48), c = getchar();
    return k;
}

int n;
vector<int>G[N];
vector<int>ans;
int cur[N];

void dfs(int u) {
    for (int i = cur[u];i<G[u].size();i = cur[u]) {
        int v = G[u][i];
        cur[u]++;
        dfs(v);
        ans.push_back(i);
    }
}

int main() {
    while (cin>>n && n) {
        for (int i = 0;i < (n << 1);i++)G[i].clear();
        for (int i = 0;i < (n << 1);i++)cur[i] = 0;
        for (int i = 0;i < (n << 1);i++) {
            if (G[(i << 1) % (n << 1)].size() == 2)continue;
            G[(i << 1) % (n << 1)].push_back(((((i << 1) + 0) % (n << 1)) << 1) % (n << 1));
            G[(i << 1) % (n << 1)].push_back(((((i << 1) + 1) % (n << 1)) << 1) % (n << 1));
        }
        ans.clear();
        dfs(0);
        reverse(ans.begin(), ans.end());
        int num = 0;
        for (int i = 0;i < ans.size();i++) {
            num = ((num << 1) + ans[i]) % (n << 1);
            printf("%d ", num);
        }
        printf("\n");
    }
}

PS,虽然我写了快读但是这里不能用快读,本地能跑但oj上会死循环

posted @ 2026-08-25 22:45  DK_orz  阅读(2)  评论(0)    收藏  举报