[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
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 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;
}