AtCoder Grand Contest 060 E Number of Cycles
讲个笑话,一年前做过,今天模拟赛出了,但是完全不记得,然后想了一种完全不同的方法,我真抽象。
首先考虑什么时候有解。显然 的时候有解,令 即可。然后考虑任意交换一对 ,此时 都会被交换。所以 的变化量均为 。所以 的奇偶性确定,若 和 的奇偶性不同就无解。
然后发现 时才可能有解。考虑 的情形,显然交换任意一对后 都会 ,所以交换后一定不优。
然后发现这两个条件是充要条件,下面考虑构造性证明。
先构造出 的方案,也就是 。考虑先从 入手,以任意顺序扫 的连通块,并且与上一个连通块合并,也就是任意交换这个连通块和上一个连通块的一对元素即可。显然它们在 中属于的连通块也不同。所以 经过这样的一次操作后都会 ,所以 经过这样一次操作后会 。
扫完之后 ,但是 可能 。我们希望当 时让 减小 ,同时保持 。
不妨考虑找到一个 使得 在 中不在同一个连通块(容易发现一定能找到,因为不能找到就说明 ,可以直接退出)。此时可以直接交换 ,这样 会 ,但是 会 。
我们发现一个非常好的事情,就是 在 中变成了一个自环,同时所有其他的点组成了一个环。也就是说我们让 和任意一个其他点交换, 都会变回 。那么就让 和任意一个在 中和它不在同一个连通块的点 交换,这样 会 。
所以经过上面的过程, 会 。
考虑快速维护这个构造的过程。可以使用一个类似栈的东西维护全部 可能在 中在同一个连通块的点 。找的时候直接取栈顶,在一个连通块就弹出。交换一对 就把 都加入栈即可。
然后对一个点 找在 中和它不在一个连通块的任意一个点,可以维护任意两个连通块的祖先,其中必然有一个不是 的祖先。
所以时间复杂度就是 , 是并查集复杂度,可以粗略地看成线性。实际跑得很快。
code
#include <bits/stdc++.h> #define pb emplace_back #define fst first #define scd second #define mkp make_pair #define mems(a, x) memset((a), (x), sizeof(a)) using namespace std; typedef long long ll; typedef unsigned long long ull; typedef double db; typedef long double ldb; typedef pair<int, int> pii; const int maxn = 200100; int n, m, a[maxn], b[maxn], stk[maxn * 5], top, X, Y, fa[maxn]; bool vis[maxn]; int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); } inline void merge(int x, int y) { x = find(x); y = find(y); if (x != y) { fa[x] = y; vis[x] = 1; while (vis[X]) { ++X; } while (vis[Y] || X == Y) { ++Y; } } } void solve() { scanf("%d%d", &n, &m); X = Y = 0; for (int i = 1; i <= n; ++i) { scanf("%d", &a[i]); b[i] = fa[i] = i; vis[i] = 0; } for (int i = 1; i <= n; ++i) { merge(i, a[i]); } int t = n; for (int i = 1; i <= n; ++i) { t += (fa[i] == i); } if (m > t || ((m + t) & 1)) { puts("No"); return; } puts("Yes"); if (m == t) { for (int i = 1; i <= n; ++i) { printf("%d ", i); } putchar('\n'); return; } int lst = -1; for (int i = 1; i <= n; ++i) { if (fa[i] == i) { if (lst != -1) { swap(a[lst], a[i]); swap(b[lst], b[i]); t -= 2; } lst = i; } if (m == t) { for (int i = 1; i <= n; ++i) { printf("%d ", b[i]); } putchar('\n'); return; } } for (int i = 1; i <= n; ++i) { fa[i] = i; vis[i] = 0; stk[++top] = i; } X = 1; Y = 2; for (int i = 1; i <= n; ++i) { merge(i, b[i]); } while (1) { while (1) { int i = stk[top]; int j = a[i]; if (find(i) == find(j)) { --top; continue; } merge(i, j); stk[++top] = j; swap(a[i], a[j]); swap(b[i], b[j]); int k = (find(i) == X ? Y : X); stk[++top] = k; merge(j, k); swap(a[j], a[k]); swap(b[j], b[k]); break; } t -= 2; if (m == t) { for (int i = 1; i <= n; ++i) { printf("%d ", b[i]); } putchar('\n'); return; } } } int main() { int T = 1; scanf("%d", &T); while (T--) { solve(); } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通