2025-1-1 / 2025-1-2 做题笔记
持续更新中……
- 2025-1-1 / 2025-1-2 做题笔记
- CF1534H - Lost Nodes
- CF1510B - Button Lock
- CF1336E1 - Chiori and Doll Picking (easy version)
- UOJ R28B - 环环相扣
- AT NOMURA2020F - Sorting Game
- AT JOISC2017E - 壊れた機器 (Broken Device)
- SYSUCPC 2024 Final M - Make SYSU Great Again 3
CF1534H - Lost Nodes
首先设 \(ans_i\) 为 \(f=i\) 中的最坏情况下的最小询问数,第一问就是 \(\max ans_i\),接下来考虑求解 \(ans_i\) 也就是第二问。
不难发现第一步应该是确定在根为 \(f\) 下,\(a\) 和 \(b\) 在哪颗子树里面,问出子树后继续递归是什么子树,所以直接设初步 dp,\(dp_u\) 为在根为 \(f\) 下,确定点在 \(u\) 里中的哪颗子树里,或者在 \(u\) 上的代价。
对于一个叶子 \(u\),需要检查一遍有没有点,代价为 \(1\)。
对于普通节点 \(u\),对于一个儿子 \(v\),需要检查点是否在 \(v\) 子树内,这需要 \(1\) 的代价,但是不管在 \(v\) 里询问什么,都可以得出点是否在 \(v\) 子树内,所以直接去根据 \(v\) 的最优策略问,如果没有就直接退出,所以对于第一个子树代价即为 \(dp_v\),若在子树 \(v\) 里没有点,这是浪费了一个代价,并且要去问下一个子树,以此类推,那么转移即为:
要最小化这个东西,即为按照从大到小排序 \(dp_{v_i}\)。
对于 \(ans_u\),最多需要找到两个子树,那么最坏的代价为 \(\max_{0\le i<j<sz_u}dp_i+dp_j+j-1\),最小可以做到 \(dp_{v_0}+\max_{i=1}^{sz_u-1}dp_{v_i}+i-1\),其中 \(v_i\) 是通过将 \(dp_{v_i}\) 从大到小排完序后的结果。
上述都是对于根固定的情况,换根 dp 即可得出所有的 \(ans_i\)。
对于第二问,直接根据转移的方式,直接模拟即可。
时间复杂度 \(O(n \log n)\)。
ケロシの代码
const int N = 1e5 + 5;
int n, dp[N], F[N];
int a[N], b[N], f[N], g[N], t[N], len;
int fi[N], ne[N << 1], to[N << 1], ecnt;
vector<int> e[N];
void add(int u, int v) {ne[++ ecnt] = fi[u];to[ecnt] = v;fi[u] = ecnt;
}
void dfs1(int u, int fa) {for(int i = fi[u]; i; i = ne[i]) {int v = to[i];if(v == fa) continue;dfs1(v, u);}len = 0;for(int i = fi[u]; i; i = ne[i]) {int v = to[i];if(v == fa) continue;a[++ len] = dp[v];}sort(a + 1, a + len + 1);reverse(a + 1, a + len + 1);dp[u] = 1;FOR(i, 1, len) chmax(dp[u], a[i] + i - 1);
}
void dfs2(int u, int fa, int f0) {len = 0;for(int i = fi[u]; i; i = ne[i]) {int v = to[i];if(v == fa) continue;a[++ len] = dp[v];}a[++ len] = f0;sort(a + 1, a + len + 1);reverse(a + 1, a + len + 1);F[u] = 1;FOR(i, 2, len) chmax(F[u], a[1] + a[i] + i - 2);FOR(i, 1, len) f[i] = max(f[i - 1], a[i] + i - 1);g[len + 1] = 0;ROF(i, len, 1) g[i] = max(g[i + 1], a[i] + i - 1);FOR(i, 1, len) b[a[i]] = i;for(int i = fi[u]; i; i = ne[i]) {int v = to[i];if(v == fa) continue;int pos = b[dp[v]];t[v] = max({1, f[pos - 1], g[pos + 1] - 1});}for(int i = fi[u]; i; i = ne[i]) {int v = to[i];if(v == fa) continue;dfs2(v, u, t[v]);}
}
void dfs0(int u, int fa) {for(int i = fi[u]; i; i = ne[i]) {int v = to[i];if(v == fa) continue;dfs0(v, u);}for(int i = fi[u]; i; i = ne[i]) {int v = to[i];if(v == fa) continue;e[u].push_back(v);}sort(e[u].begin(), e[u].end(), [&] (int x, int y) {return dp[x] > dp[y];});
}
int query(int u) {cout << "? " << u << endl << flush;int v; cin >> v;return v;
}
int dfs(int u) {if(e[u].empty()) return query(u);for(int v : e[u]) {int val = dfs(v);if(val != u) return val;}return u;
}
void solve() {cin >> n;if(n == 1) {cout << 0 << endl << flush;int x; cin >> x;cout << "! " << 1 << " " << 1 << endl << flush;return;}REP(_, n - 1) {int u, v;cin >> u >> v;add(u, v), add(v, u);}dfs1(1, 0);dfs2(1, 0, 0);int ans = 0;FOR(i, 1, n) chmax(ans, F[i]);cout << ans << endl << flush;int rt; cin >> rt;int A = rt, B = rt;dfs1(rt, 0);dfs0(rt, 0);for(int v : e[rt]) {int val = dfs(v);if(val != rt) {if(A == rt) {A = val;}else {B = val;break;}}}cout << "! " << A << " " << B << endl << flush;
}
CF1510B - Button Lock
首先考虑建出单向图,对于点 \(S\) 和 点 \(T\),若 \(S \subset T\),则连一条 \(S\) 到 \(T\) 的边。因为有重置操作,所以再搞一个根连向所有点。
不难发现接下来就变为了一个最小路径覆盖问题,需要用从根开始的链覆盖所有的点,一条链的代价为根到链底所需的代价。
对于链覆盖问题,考虑网路流,因为有不同的代价权,考虑建费用流,类似普通最小链覆盖,把原图拆成二分图,每个点有入点 \(in(u)\) 和出点 \(out(u)\)。
若原图上 \(u\) 到 \(v\) 有边,则在费用流上建 \(in(u) \to out(v)\),流量为 \(1\),费用为 \(-w(u)\),意义是通过走 \(u\) 到 \(v\),省去了点 \(u\) 的代价。
因为一个点不可能省或被省去两次,且原图本来就是一个闭包,所以直接再建出原点 \(S\) 和汇点 \(T\),连 \(S \to in(u)\) 和 \(out(u) \to T\),流量为 \(1\),费用为 \(0\)。
但是直接跑费用流可能跑不过,考虑到费用的范围很小,根据费用流每次增广最短路,所以直接从小到大枚举费用加边,每次跑一遍最大流即可。
时间复杂度 \(O(d2^d\sqrt{3^d})\)。
ケロシの代码
namespace Dinic {const int N = 3e3 + 5;const int M = 1e6 + 5;const int INF = 0x3f3f3f3f;struct Edge {int ne, to, ew;} e[M];int S, T; int fi[N], c[N], ecnt;int d[N];void init() {memset(fi, 0, sizeof fi);ecnt = 1;}void add(int u, int v, int w) {e[++ ecnt] = {fi[u], v, w};fi[u] = ecnt;e[++ ecnt] = {fi[v], u, 0};fi[v] = ecnt;}bool bfs() {memset(d, 0x3f, sizeof d);queue<int> q;d[S] = 0; q.push(S);while(! q.empty()) {int u = q.front();q.pop();for(int i = fi[u]; i; i = e[i].ne) if(e[i].ew) {int v = e[i].to;if(d[v] == INF) {d[v] = d[u] + 1;q.push(v);}}}return d[T] != INF;}int dfs(int u, int w) {if(u == T || ! w) return w;int res = 0;for(int & i = c[u]; i; i = e[i].ne) {int v = e[i].to;if(d[v] != d[u] + 1) continue;int val = dfs(v, min(e[i].ew, w));e[i].ew -= val;e[i ^ 1].ew += val;w -= val;res += val;if(! w) return res;}return res;}int dinic(int _S, int _T) {S = _S, T = _T;int res = 0;while(bfs()) {memcpy(c, fi, sizeof c);res += dfs(S, INF);}return res;}
}
const int N = 1.1e3 + 5;
int d, n, a[N], vis[N];
vector<PII> e[N];
void add(int u, int v, int w) {e[w].push_back({u, v});
}
void print(int x, int y) {REP(i, d) if((x >> i & 1) != (y >> i & 1))cout << i << " ";
}
void dfs(int u) {vis[u] = 1;for(int i = Dinic :: fi[u]; i; i = Dinic :: e[i].ne) {int v = Dinic :: e[i].to - n, w = Dinic :: e[i].ew;if(1 <= v && v <= n && ! w) {print(a[u], a[v]);dfs(v);} }
}
void solve() {cin >> d >> n;FOR(i, 1, n) {string s; cin >> s;ROF(j, d - 1, 0) a[i] = a[i] << 1 | (s[j] == '1');}n ++;sort(a + 1, a + n + 1);int S = 0, T = n * 2 + 1;Dinic :: init();FOR(i, 2, n) add(1, i + n, 1);FOR(i, 2, n) FOR(j, i + 1, n) if((a[i] & a[j]) == a[i])add(i, j + n, popcount(a[i]) + 1);FOR(i, 1, n) Dinic :: add(S, i, 1);FOR(i, 1, n) Dinic :: add(i + n, T, 1);int ans = - 1;FOR(i, 1, n) ans += popcount(a[i]) + 1;ROF(i, d + 1, 1) {for(auto h : e[i])Dinic :: add(FI(h), SE(h), 1);ans -= Dinic :: dinic(S, T) * i;}cout << ans << endl;FOR(i, 1, n) if(! vis[i]) {if(i > 1) cout << "R ";print(0, a[i]);dfs(i);}
}
CF1336E1 - Chiori and Doll Picking (easy version)
首先将所有 \(n\) 个数插入线性基,有经典结论,线性基 \(A\) 里能异或出来的数的方案数都是 \(2^{n-|A|}\),因为无论外边 \(n-|A|\) 个数怎么异或,线性基里总能得出一种异或方法异或到某个数。
接下来就只需在线性基里计算即可,但是线性基大小比较大,比较难计算。
考虑拆成高低位,不难发现低位是影响不到高位的,所以将高位异或出的数的高位部分独立出来,设 \(g_S\) 为低位中异或出来为 \(S\) 的方案数,设 \(f_{i,S}\) 为高位中异或出来的数,在高位的 popcount 为 \(i\),低位部分为 \(S\) 的方案数。
接下来对于每个高位 popcount 取值,都做使用 FWT 进行一遍异或卷积来计算低位异或部分:
就能得出不同低位取值和高位 popcount 的方案数,然后就能直接统计出答案了。
时间复杂度 \(O(m^22^\frac{m}{2})\)。
ケロシの代码
const int N = 40;
const int P = 998244353;
const int P2 = (P + 1) / 2;
inline int add(int x, int y) { return (x + y < P ? x + y : x + y - P); }
inline void Add(int & x, int y) { x = (x + y < P ? x + y : x + y - P); }
inline int sub(int x, int y) { return (x < y ? x - y + P : x - y); }
inline void Sub(int & x, int y) { x = (x < y ? x - y + P : x - y); }
inline int mul(int x, int y) { return (1ll * x * y) % P; }
inline void Mul(int & x, int y) { x = (1ll * x * y) % P; }
int fp(int x, int y) {int res = 1;for(; y; y >>= 1) {if(y & 1) Mul(res, x);Mul(x, x);}return res;
}
int n, m;
ll b[N];
int p[N], len, a[20][1 << 18];
int f[1 << 18], g[1 << 18];
int ans[N];
void insert(ll x) {ROF(i, m - 1, 0) if(x >> i & 1) {if(! b[i]) {b[i] = x;return;}x ^= b[i];}
}
void XOR(int * a, int lim) {for(int i = 1; i < lim; i <<= 1) for(int j = 0; j < lim; j += (i << 1))REP(k, i) {int x = a[j + k];int y = a[j + k + i];a[j + k] = add(x, y);a[j + k + i] = sub(x, y);}
}
void IXOR(int * a, int lim) {for(int i = 1; i < lim; i <<= 1) for(int j = 0; j < lim; j += (i << 1))REP(k, i) {int x = mul(a[j + k], P2);int y = mul(a[j + k + i], P2);a[j + k] = add(x, y);a[j + k + i] = sub(x, y);}
}
void solve() {cin >> n >> m;REP(_, n) {ll x; cin >> x;insert(x);}int mid = m / 2;FOR(i, mid, m - 1) if(b[i]) p[len ++] = i;REP(S, 1 << len) {ll val = 0;REP(i, len) if(S >> i & 1) val ^= b[p[i]];a[popcount(val >> mid)][val & ((1 << mid) - 1)] ++;}len = 0;REP(i, mid) if(b[i]) p[len ++] = i;REP(S, 1 << len) {ll val = 0;REP(i, len) if(S >> i & 1) val ^= b[p[i]];g[val] ++;}XOR(g, 1 << mid);FOR(i, 0, m - mid) {REP(S, 1 << mid) f[S] = a[i][S];XOR(f, 1 << mid);REP(S, 1 << mid) Mul(f[S], g[S]);IXOR(f, 1 << mid);REP(S, 1 << mid) Add(ans[i + popcount(S)], f[S]);}int sz = 0;REP(i, m) if(b[i]) sz ++;FOR(i, 0, m) Mul(ans[i], fp(2, n - sz));FOR(i, 0, m) cout << ans[i] << " "; cout << endl;
}
UOJ R28B - 环环相扣
不妨设 \(a_i>a_j>a_k\),那么最优的排法只有两种情况:\((a_i \bmod a_j) + (a_j \bmod a_k) + a_k\) 和 \((a_i \bmod a_k) + a_j + a_k\)。
考虑重要性质:若 \(x>y\),那么 \((x \bmod y)+y \le (x - y)+y = x\),也就是 \((x \bmod y)+ y\le x\)。
接下来通过手玩,发现对于区间的最优解中,区间的最大值和次大值是必选的。
证明 ………………
接下来考虑继续优化,…………
…………
…………
时间复杂度 \(O(n \log V + q \log n)\)。
ケロシの代码
namespace IO {char buf[1024], * p1 = buf, * p2 = buf;inline char gc() {if(p1 == p2) {p2 = buf + fread(buf, 1, sizeof buf, stdin);p1 = buf;}return p1 == p2 ? EOF : * p1 ++;}ll read() {ll res = 0; int v = 0;char c = gc();while(c < '0' || c > '9') {if(c == '-') v = 1;c = gc();}while('0' <= c && c <= '9') {res = (res << 3) + (res << 1) + (c ^ 48);c = gc();}return v ? - res : res;}
}
using IO :: read;
const int N = 2e6 + 5;
const ll LNF = 1e18;
int n, m, op;
ll a[N];
vector<pair<int, ll>> f[N], g[N];
struct SgT {int le[N << 2], ri[N << 2];int D[N << 2];void pushup(int u) {D[u] = a[D[u << 1]] >= a[D[u << 1 | 1]] ? D[u << 1] : D[u << 1 | 1];}void build(int u, int l, int r) {le[u] = l, ri[u] = r;if(l == r) {D[u] = l;return;}int mid = l + r >> 1;build(u << 1, l, mid);build(u << 1 | 1, mid + 1, r);pushup(u);}int query(int u, int l, int r) {if(l > r) return 0;if(l <= le[u] && ri[u] <= r) {return D[u];}int mid = le[u] + ri[u] >> 1;if(r <= mid) return query(u << 1, l, r);if(mid < l) return query(u << 1 | 1, l, r);int L = query(u << 1, l, r);int R = query(u << 1 | 1, l, r);return a[L] >= a[R] ? L : R;}
} t;
ll F(int l, int r, int i) {ll res = - LNF;int L = 0, R = SZ(g[i]) - 1;while(L <= R) {int mid = L + R >> 1;if(FI(g[i][mid]) <= r) {chmax(res, SE(g[i][mid]));L = mid + 1;}else {R = mid - 1;}}L = 0, R = SZ(f[i]) - 1;while(L <= R) {int mid = L + R >> 1;if(FI(f[i][mid]) >= l) {chmax(res, SE(f[i][mid]));L = mid + 1;}else {R = mid - 1;}}return res;
}
void solve() {n = read(); m = read(); op = read();FOR(i, 1, n) a[i] = read();FOR(l, 1, n) {ll res = - LNF; int pos = 0, cnt = 0;FOR(i, l + 1, n) {ll x = 0;if(a[i] > a[pos]) x = a[pos], pos = i;else x = a[i];if(x && chmax(res, a[l] % x + x))g[l].push_back({i, res});if(a[i] > a[l] / 2) cnt ++;if(cnt == 2) break;}res = - LNF; pos = cnt = 0;ROF(i, l - 1, 1) {ll x = 0;if(a[i] > a[pos]) x = a[pos], pos = i;else x = a[i];if(x && chmax(res, a[l] % x + x))f[l].push_back({i, res});if(a[i] > a[l] / 2) cnt ++;if(cnt == 2) break;}}t.build(1, 1, n);ll lst = 0;REP(_, m) {int l, r;l = read(); r = read();if(op) {l = (lst + l) % n; if(! l) l = n;r = (lst + r) % n; if(! r) r = n;}int p1 = t.query(1, l, r);int pl = t.query(1, l, p1 - 1);int pr = t.query(1, p1 + 1, r);int p2 = a[pl] > a[pr] ? pl : pr;ll res = - LNF;chmax(res, F(l, r, p1) + a[p2]);chmax(res, F(l, r, p2) + a[p1] % a[p2]);if(p1 < p2) {if(l < p1) {int p3 = t.query(1, l, p1 - 1);chmax(res, a[p1] % a[p3] + a[p3] + a[p2]);}if(p2 < r) {int p3 = t.query(1, p2 + 1, r);chmax(res, a[p2] % a[p3] + a[p3] + a[p1] % a[p2]);}}if(p2 < p1) {if(p1 < r) {int p3 = t.query(1, p1 + 1, r);chmax(res, a[p1] % a[p3] + a[p3] + a[p2]);}if(l < p2) {int p3 = t.query(1, l, p2 - 1);chmax(res, a[p2] % a[p3] + a[p3] + a[p1] % a[p2]);}}cout << (lst = res) << endl;}
}
AT NOMURA2020F - Sorting Game
先考虑固定的 \(a\) 数组,对于 \(i<j\) 且 \(a_i>a_j\) 的对,\(i\) 和 \(j\) 必须相邻交换一次,所以对于所有的 \(i<j\) 且 \(a_i>a_j\),必须满足 \(a_i\) 与 \(a_j\) 数位上只差一位。
更深入的,这就相当于 \(a_i\) 和 \(a_j\) 的第一个不同高位,\(a_i\) 的是 \(1\) 且 \(a_j\) 是 \(0\),且下面的位全部相同。
再考虑如果要修改这个 \(a\) 数组,一次修改只会推平一个位,两个数的不同位数并不会增大,只会操作最高不同位,使得 \(a_i<a_j\) 变为 \(a_i>a_j\)。
不难发现上述的交换和改数组都是高位到低位,所以考虑 dp。
社 \(f_{n,m}\) 为数组长为 \(m\),值域为 \([0,2^n)\) 的答案,那么考虑最高位是什么情况。
第一种是不存在 \(10\) 逆序对,形如 \(00\cdots 0011\cdots 11\),这时因为有左边 \(0\) 部分小于右边 \(1\) 部分,所以最佳改数组方案就是推平这一位,这样这一位的偏序就没有用了,相当于去掉这一位,递归进子问题。\(01\) 分割点有 \(m+1\) 种选法,方案即为 \((m+1)f_{n-1,m}\)。
第二种是存在 \(10\) 逆序对,考虑提出最左的 \(1\) 和最右的 \(0\),形如 \(0001\cdots 0111\),那么根据上述的性质,中间的 \(1\cdots 0\) 段下面的位都是相同的,直接随最左的 \(1\) 固定掉。接下来仍然可以推平这一位,使得最左的 \(0\) 段和最右的 \(1\) 段失去偏序关系,所以继续递归进子问题,枚举保留的有 \(i\) 位,那么方案为 \(\sum_{i=1}^{m-1}i \cdot 2^{m-i+1}f_{n-1,i}\)。
用前缀和优化这个转移即可,时间复杂度 \(O(n^2)\)。
ケロシの代码
const int N = 5e3 + 5;
const int P = 1e9 + 7;
inline int add(int x, int y) { return (x + y < P ? x + y : x + y - P); }
inline void Add(int & x, int y) { x = (x + y < P ? x + y : x + y - P); }
inline int mul(int x, int y) { return (1ll * x * y) % P; }
inline void Mul(int & x, int y) { x = (1ll * x * y) % P; }
int n, m;
int f[N][N], g[N][N];
void solve() {cin >> n >> m;FOR(j, 1, m) f[0][j] = 1;FOR(j, 1, m) g[0][j] = add(mul(g[0][j - 1], 2), mul(j, f[0][j]));FOR(i, 1, n) FOR(j, 1, m) {f[i][j] = mul(f[i - 1][j], j + 1);Add(f[i][j], g[i - 1][j - 1]);g[i][j] = add(mul(g[i][j - 1], 2), mul(j, f[i][j]));}cout << f[n][m] << endl;
}
AT JOISC2017E - 壊れた機器 (Broken Device)
考虑坏掉的位是零,所以需要用含有 \(1\) 的信息来表达进制中的 \(0\)。
这样的话一个位就表达不了信息,考虑用两个位存一个进制。
两个位可以存三种状态:\(01,10,11\),发现这可以表示三进制,分别对应 \(0,1,2\)。
但是最坏情况下这样不坏掉的两个位最少只有 \(\frac{150-40\times 2}{2}=35\) 个,只能表达 \(3^{35}\) 级别的数,而 \(10^{18}\) 需要 \(38\) 个位来表示。
考虑将位随机乱排,这样坏掉的位有几率排到一起,有效位就能增加,但不足以通过。
考虑有两个位,前一个位坏了但后一个是好的,那么这个两个位依然可以表示 \(01\),将这类情况下的位也充分利用,即可通过 \(N=150\)。
ケロシの代码
#include "Broken_device_lib.h"
const int N = 205;
int t[N], b[N], id[N];
void Anna(int n, ll x, int k, int * p) {mt19937 rnd(114);REP(i, n) id[i] = i;shuffle(id, id + n, rnd);REP(i, n) t[i] = b[i] = 0;REP(i, k) t[p[i]] = 1;int pos = 0;while(x) {int val = x % 3;if(val == 0) {while(t[id[pos]]) pos += 2;b[id[pos]] = 1;}if(val == 1) {while(t[id[pos + 1]]) pos += 2;b[id[pos + 1]] = 1;}if(val == 2) {while(t[id[pos]] || t[id[pos + 1]]) pos += 2;b[id[pos]] = b[id[pos + 1]] = 1;}pos += 2;x /= 3;}REP(i, n) Set(i, b[i]);
}
ll Bruno(int n, int * a) {mt19937 rnd(114);REP(i, n) id[i] = i;shuffle(id, id + n, rnd);ll x = 0;for(int pos = n - 2; pos >= 0; pos -= 2) {if(! a[id[pos]] && ! a[id[pos + 1]]) continue;x *= 3;if(a[id[pos]] && a[id[pos + 1]]) x += 2;if(! a[id[pos]] && a[id[pos + 1]]) x += 1;}return x;
}
考虑足以通过 \(N=120\) 甚至更小的更优做法。
考虑随机 \(n\) 个数 \(a_i\),如果构造出来的序列中第 \(i\) 位是 \(1\) 就表示答案就异或上 \(a_i\)。
这样只需选定一些数异或出目标数,使用线性基维护即可。
ケロシの代码
#include "Broken_device_lib.h"
const int N = 1000;
ll a[N], b[N], w[N];
int t[N], id[N], ans[N];
void insert(ll x, int u) {ll res = 0;ROF(i, 60, 0) if(x >> i & 1) {if(! b[i]) {b[i] = x;id[i] = u;w[i] = res ^ (1ll << i);return;}x ^= b[i];res ^= w[i];}
}
void Anna(int n, ll x, int k, int * p) {mt19937 rnd(114);REP(i, n) a[i] = 1ull * rnd() * rnd() % (1ll << 60);REP(i, 61) w[i] = b[i] = 0;REP(i, n) ans[i] = t[i] = 0;REP(i, k) t[p[i]] = 1;REP(i, n) if(! t[i]) insert(a[i], i);ll res = 0;ROF(i, 60, 0) if(x >> i & 1) {x ^= b[i];res ^= w[i];}ROF(i, 60, 0) if(res >> i & 1) ans[id[i]] = 1; REP(i, n) Set(i, ans[i]);
}
ll Bruno(int n, int * p) {mt19937 rnd(114);REP(i, n) a[i] = 1ull * rnd() * rnd() % (1ll << 60);ll res = 0;REP(i, n) if(p[i]) res ^= a[i];return res;
}
SYSUCPC 2024 Final M - Make SYSU Great Again 3
首先考虑是链怎么做到答案为 \(\left \lceil \frac{n}{2} \right \rceil -1\),考虑将排列分为奇数位和偶数位,对于偶数位,都填 \(\left [1,\left \lfloor \frac{n}{2} \right \rfloor \right ]\) 中的数,奇数位填其余的数。
接下来就不难想到一个构造,偶数位直接从 \(1\) 开始填,不断填到大的,然后偶数位在满足条件时类似折返一样填即可,例如:
然后考虑是环怎么做,不难发现可以交换前两项,这样不影响最左边三个数的匹配,同时开头的 \(1\) 给最后两个数也产生了贡献:
ケロシの代码
const int N = 2e5 + 5;
int n, a[N];
void solve() {cin >> n;FOR(i, 1, n) a[i] = 0;if(n % 2) {for(int i = 1, p = 2; p <= n; p += 2, i ++)a[p] = i;for(int i = n / 2 + 1, p = n; p >= 1; p -= 4, i ++)a[p] = i;for(int i = n, p = n - 2; p >= 1; p -= 4, i --)a[p] = i;swap(a[1], a[2]);}else {for(int i = 1, p = 2; p <= n; p += 2, i ++)a[p] = i;for(int i = n / 2 + 1, p = n - 1; p >= 1; p -= 4, i ++)a[p] = i;for(int i = n, p = n - 3; p >= 1; p -= 4, i --)a[p] = i;swap(a[1], a[2]);}cout << "Yes" << endl;FOR(i, 1, n) cout << a[i] << " "; cout << endl;
}