专栏文章
NOI 2025
个人记录参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @minrmcu4
- 此快照首次捕获于
- 2025/12/02 07:13 3 个月前
- 此快照最后确认于
- 2025/12/02 07:13 3 个月前
均不转述题目.
D1T2
考虑一个经典 trick: 相邻相同位相抵 (例如, 反转, 缩去形如 的子串, 以及本题的同时减去 ) 可以考虑奇数/偶数位反转
同时, 另一个经典的思考模式: 考虑如果我们一条路走到黑会发生什么. 因此我们考虑处理函数 : 考虑一直让后面抵消前面, 即递推
这样对于最小的 , 令 . 同理定义 为
对应的最小的 . 那么一个可达的区域 可以定义为:
则 说明我们不能把 的贡献推到一个 上, 反之亦然. 如果我们遇到 说明我们应该把这个区间拆分, 因此我们着重研究 . 我们来看一下 -不变量.
- : 这说明我们把 推进到 时使得所有数在 处相消. 显然这是一个充要条件, 因此 能清空 .
- 否则, 任取 , 不难看出 因此 这提示我们应当考虑位置的奇偶性.
分段双射于所有可能操作. 具体地, , 并附带 满足 , 且 时 且 时 满足相应奇偶性要求, 这样的结构 双射于 . 显然根据上文的铺垫我们有 的一个自然映射. 现在我们证明它是一个双射.
- 满射: 对于 , 存在对应于其的一个操作序列 , 则我们可以通过对 中每操作 则连边, 则连通分量为所求. (易证每个连通分量至多一个非 );
- 单射: 不难发现不同的操作序列给出不同的 . 而不同的 给出不同的操作序列. 结论得证.
下面对于一个对加法成幺半群, 对乘法 (除了 以外) 成群的结构讨论.
现在考虑分段. 对于某一段 , 只需考虑其对答案的贡献. 进而考虑对 做分类讨论. 如果 , 那么这一段的贡献显然是
否则, 显然是:
因此可以预处理后 去做.
这题有点魔怔的点就在于入股哦你不想对同一件事写两份代码 (虽然写两份代码就是提截取的各位所做的), 那么你应该就要面临卡常. 先放一个卡常前的比较简洁的代码
CPPenum block { split = -2, emptify = -1, parity_0 = 0, parity_1 = 1 };
auto blocking(u32 n, vector<i64>& a) {
vector<i64> pre(n + 1), L(n + 1, n), R(n + 1);
for (u32 i = 0; i < n; i++) pre[i + 1] = pre[i] + a[i] * (i % 2 == 1 ? -1 : 1);
for (u32 i = 0; i < n; i++) {
i64 x = a[i];
for (u32 j = i + 1; j < n && x > 0; j++) {
x = a[j] - x;
if (x < 0) L[i] = j;
if (x == 0) L[i] = j + 1;
}
}
for (u32 i = 0; i < n; i++) {
i64 x = a[i];
for (int j = i - 1; j >= 0 && x > 0; j--) {
x = a[j] - x;
if (x < 0) R[i + 1] = j + 1;
if (x == 0) R[i + 1] = j;
}
}
auto corridor = [L = move(L), R = move(R)](u32 l, u32 r) {
return make_pair(max<u32>(l, R[r]), min<u32>(r, L[l]));
};
auto classify = [pre = move(pre), corridor](u32 l, u32 r) {
auto [ll, rr] = corridor(l, r);
if (ll >= rr) return split;
if (pre[r] - pre[l] == 0) return emptify;
if (pre[r] - pre[l] > 0) return parity_0;
return parity_1;
};
return make_pair(corridor, classify);
}
template <class T>
T solve(u32 n, vector<i64>& a, vector<T>& weight) {
auto [corridor, classify] = blocking(n, a);
vector prod(n, vector<T>(n + 1, T::one()));
for (u32 i = 0; i < n; i++) {
prod[i][i + 1] = weight[i];
for (u32 j = i + 1; j < n; j++) prod[i][j + 1] = prod[i][j] * weight[j];
}
vector<T> pweight = weight;
for (auto& x : pweight) x = x.inv();
vector parity(n, vector<array<T, 2>>(n + 1, {T::zero(), T::zero()}));
for (u32 i = 0; i < n; i++) {
for (u32 p : {0, 1})
if (i % 2 == p) parity[i][i + 1][p] = pweight[i];
for (u32 j = i + 1; j < n; j++) {
for (u32 p : {0, 1}) parity[i][j + 1][p] = parity[i][j][p];
parity[i][j + 1][j % 2] = parity[i][j][j % 2] + pweight[j];
}
}
auto calc = [&](u32 l, u32 r) {
auto typ = classify(l, r);
if (typ == split) return T::zero();
if (typ == emptify) return prod[l][r];
auto [ll, rr] = corridor(l, r);
return prod[l][r] * parity[ll][rr][typ];
};
vector<T> dp(n + 1, T::zero());
dp[0] = T::one();
for (u32 i = 1; i <= n; i++)
for (u32 j = 0; j < i; j++) dp[i] = dp[i] + dp[j] * calc(j, i);
return dp[n];
}
卡常技巧就是把
prod 和 parity 当做数组指针传进去, 然后注意到 大概是 , 所以你可以给 上 int 类型然后转 i64. 这很魔怔吧!D1T3
发现答案是 , 考虑线性代数. 假设所有点是 , 所有颜色是 . 可以看出所有 DFS 序对应一个反转方案, 而一个反转方案是 的向量. 我们定义 表示 . 首先考虑刻画它. 考虑一个颜色 的分歧位置 , 满足 的左边含一个 , 右边含一个 . 如果把 和 的可消部分消掉, 那么剩下的部分一定形如
. 现在考虑按照某种方式消颜色, 那么考虑
发现 相等的点应当匹配, 它们的反转 应当有偶数个翻转位置. 当然, 如果 是平凡的 (这里的平凡指 Hamming 权重 ), 那么反不反转是一致的, 不构成约束.
考虑证明. 不妨设 就是根 (我们可以取一个适当的子树满足这一点).
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...