[2020 SWERC G] Decoration

https://codeforces.com/gym/103081/problem/G

题意:
转换下题意,其实就是定义\(f(x)\)\(x\)\(x\)的约数个数和,并对其取模\(n\)。让你构造一个序列\(a\),使得\(f(a_1) = a_2, f(a_2) = a3,\dots, f(a_{k - 1}) = a_k\), \(0 \leq a_i \leq n - 1\),且互不相同。若存在多个序列,则选择和最小的。

思路:
\(f(x)\)只对应一个解。对于这种式子的多次迭代,可以参考倍增模型,就能很快求出对应\(x\)迭代\(k\)次的结果。我们在处理的时候,也能把\(k\)次和都求出来。

考虑什么情况会造成重复。我们对\(x\) -> \(f(x)\)画图,有时会形成一个环。如果这个环没有比\(k\)大,就会产生重复。那么我们建个图,\(dfs\)时把这些环筛出来,对其上的每个点都进行标记,后续算答案时就不以这些点作为起点。除此之外,环旁边的一些离它较近的点也要做标记。

题目内存卡得比较紧,这里我把\(sum\)数组分成两部分才卡过去。正解能把空间复杂度降到\(O(n)\),但是我完全看不懂。有没有聚聚浇浇QAQ。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int N = 1e6 + 7;
int nxt[N]; 

int n, k;
int tur[N];
int cir[N], dfn[N], tot, id;
bool bad[N];
int son[N][20];
int sum1[N][10];
ll sum2[N][10];
int fen = 9;
vector<int> G[N];

void getCir(int u, int h) {
    cir[u] = h;
    int v = nxt[u];
    while (v != u) {
        cir[v] = h;
        v = nxt[v];
    }
}

void dfs(int u) {
    if (u == -1) return;
    if (tur[u] == tot) {
        getCir(u, id + 1 - dfn[u]);
        return;
    }
    if (dfn[u]) return;
    dfn[u] = ++id;
    tur[u] = tot;
    dfs(nxt[u]);
}

void dfsBad(int u, int lst) {
    if (lst <= 0) return;
    for (auto v : G[u]) {
        if (cir[v]) continue;
        bad[v] = 1;
        dfsBad(v, lst - 1);
    }
}

void run() {
    scanf("%d %d", &n, &k);
    if (k == 1) {
        puts("0");
        return;
    }
    memset(son, -1, sizeof(son));
    for (int i = 1; i < n; ++i) {
        nxt[i] += i;
        for (int j = i; j < n; j += i) {
            nxt[j]++;
        }
        nxt[i] %= n;
        G[ nxt[i] ].emplace_back(i);
    }
    
    for (int i = 0; i < n; ++i) {
        if (!tur[i]) {
            tot++;
            dfs(i);
        }
    }
    for (int i = 0; i < n; ++i) {
        if (cir[i]) {
            if (cir[i] < k) {
                dfsBad(i, k - cir[i] - 1);
                bad[i] = 1;
            }
        }
    }
    for (int i = 0; i < n; ++i) {
        sum1[i][0] = i;
        son[i][0] = nxt[i];
    }
    for (int j = 1; j < 20; ++j) {
        for (int i = 1; i < n; ++i) {
            if (son[i][j - 1] != -1) {
                son[i][j] = son[ son[i][j - 1] ][j - 1];
                if (j <= fen) {
                    sum1[i][j] = sum1[i][j - 1] + sum1[ son[i][j - 1] ][j - 1];
                } else if (j == fen + 1) {
                    sum2[i][j] = 1ll * sum1[i][j - 1] + sum1[ son[i][j - 1] ][j - 1];
                } else {
                    sum2[i][j] = sum2[i][j - 1] + sum2[ son[i][j - 1] ][j - 1];
                }
            }
        }
    }
    int gg = -1;
    ll pre = 0x3f3f3f3f3f3f3f3f;
    for (int i = 1; i < n; ++i) {
        if (bad[i]) continue;
        int now = i;
        ll ss = 0;
        for (int j = 19; j >= 0; --j) {
            if ((1 << j) & k) {
                if (j > fen) {
                    ss += sum2[now][j];
                } else {
                    ss += sum1[now][j];
                }
                now = son[now][j];
                if (now == -1) break;
            }
        }
        if (now != -1 && ss < pre) {
            pre = ss;
            gg = i;
        }
    }
    if (gg == -1) {
        puts("-1");
        return;
    }
    for (int i = 1; i <= k; ++i) {
        printf("%d ", gg);
        gg = nxt[gg];
    }
}

int main() {
    int t = 1;
    while (t--) run();
    return 0;
} 
posted @ 2021-09-13 20:07  stff577  阅读(63)  评论(0编辑  收藏  举报