2025-08-18 NOIP && NOI 模拟赛题解
T1
拆贡献,然后拆位。异或记个后缀和边扫边算;与和或考虑极长的连续 0/1 段。
注意可能爆 long long 范围以及精度问题。
T2
正解直接建圆方树,然后把路径沿 lca 拆成两条竖着的路径,这两条路径进入的环最后一定从方点的父亲出来,于是给每个除方点父亲的圆点的父边赋值为到方点父亲的最短路,lca 处如果是方点就拆开。
但是这个做法还是太没意思了,注意到范围很小,所以出题人肯定不想让我们用正经的做法。我们考虑直接对询问的某一个点跑 spfa,然后考虑怎么优化常数。
首先队列手写。然后把询问丢进桶里,然后尽可能抽出覆盖询问多的点跑,这样就能跑尽可能少次 spfa。
接下来是正经的内容。
那么最多会跑多少次呢?首先转化成一个无向图, \(n\) 点 \(n\) 边,求最小点覆盖。结论是当 \(n\) 为 3 的倍数时,最多跑 \(\frac{2n}{3}\) 次。
首先有个结论,就是一个连通图的最小点覆盖不会超过 \(\lceil \frac{边数}{2} \rceil\)。证明考虑跑出 dfs 树,然后依次删掉两条共顶点的边,然后随便归纳一下就好了。
回到原问题上,那也就是说,既然我们想让点覆盖尽可能大,那尽量让每个连通块都有奇数条边,这样点覆盖就有可能超过边数除以 2。
也就是说要找到一个东西满足以下条件:
- 连通
- 最小点覆盖严格大于边数一半
而性价比最高的就是三元环了。因为用三个边换了两个点覆盖。而比如五元环就是三个点覆盖,没有它值。
所以证明了当 \(n\) 为 3 的倍数时,最小点覆盖最大为 \(\frac{2n}{3}\)。构造一堆独立的三元环即可。
这样最坏我把我自己卡到了 6664 次 spfa,由于仙人掌本身就松弛次数少,所以没什么优化空间,这个乱搞应该也是被卡掉了。(好的评测机应该也能撑)
然而随机数据下期望跑 4000 次左右,还是能通过的。(不过比较奇怪的是树跑的比仙人掌要慢近 200ms,按理来讲不会再次松弛 spfa,是要比仙人掌快的,,,当然询问数据强弱差异也不是没有可能)
T3
转化一下问题,你有 n 个数,范围 \([0,2^m)\),然后你可以给所有数加 \([0,T]\) 次,问最后异或和为 \(S\) 的情况数。
\(n\le 10^5, m\le 50, T\le 10^{18}\)
非常套路的 dp 题啊,由于涉及进位所以从低位 dp,设 \(f_{k,x}\) 表示考虑完了前 \(k\) 位(与 \(S\) 相同),有 \(x\) 个数向 \(k+1\) 位进位。
考虑转移。现在有个问题是怎么知道哪 \(x\) 个数进位了。但是我们仔细一想啊,产生进位等价于 \(a_i\ge 2^{k}\),既然加的是同一个值,那产生进位的一定是值域上(\([0,2^{m})\))的一个后缀啊!于是我们将 \(a_i\) 按照模 \(2^m\) 的值降序排序,取前几大的即可(同一个值得取完)。然后就做完了。另外代码中用了基数排序实现,边做边排很方便。
// Author: Aquizahv
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int N = 1e5 + 5, M = 55;
int n, m, f[M][N], s[M][2], fix[M];
ll a[N], S, T;
bool eq(ll x, ll y)
{
return (x & 1) == (y & 1);
}
ll solve()
{
memset(f, 0, sizeof(f));
vector<ll> b[2];
if (fix[0] != 1 && eq(s[0][1], S))
f[0][0] = 1;
if (fix[0] != 0 && eq(s[0][0], S))
f[0][s[0][1]] = 1;
for (int k = 1; k < m; k++)
{
b[0].clear(), b[1].clear();
for (int i = 1; i <= n; i++)
b[(a[i] >> (k - 1)) & 1].push_back(a[i]);
int pos = 0;
for (auto x : b[0])
a[++pos] = x;
for (auto x : b[1])
a[++pos] = x;
int c[2] = {0, 0};
int all = (1ll << k) - 1;
for (int i = n + 1; i >= 1; i--)
{
if (i <= n)
c[(a[i] >> k) & 1]++;
if (i == n + 1 || i == 1 || (a[i] & all) != (a[i - 1] & all))
{
// 0
if (fix[k] != 1 && eq(c[0] + s[k][1] - c[1], S >> k))
f[k][c[1]] += f[k - 1][n - i + 1];
// 1
if (fix[k] != 0 && eq(c[1] + s[k][0] - c[0], S >> k))
f[k][s[k][1] + c[0]] += f[k - 1][n - i + 1];
}
}
}
ll res = 0;
for (int i = 0; i <= n; i++)
res += f[m - 1][i];
return res;
}
int main()
{
cin >> n >> m >> S >> T;
for (int i = 1; i <= n; i++)
{
scanf("%lld", a + i);
for (int k = 0; k < m; k++)
s[k][(a[i] >> k) & 1]++;
}
for (int k = 0; k < m; k++)
fix[k] = -1;
ll ans = T / (1ll << m) * solve();
T %= 1ll << m;
for (int k = 0; k < m; k++)
fix[k] = (T >> k) & 1;
ans += solve();
for (int k = 0; k < m; fix[k++] = -1)
if (T & (1ll << k))
{
fix[k] = 0;
ans += solve();
}
memset(fix, 0, sizeof(fix));
cout << ans - solve() << endl;
return 0;
}
T4
[Tree]
给定一棵有 \(n\) 个节点的带权无根树。现在,你需要选择一些互不相交(包括端点)的路径。如果你选择了 \(k\) 条路径,且覆盖的点权和为 \(S\),你的得分为 \(\frac{S}{k+1}\)。
另外,在选取路径之前,你必须执行一次如下操作(操作分为三个步骤):
- 选定一个参数 \(C\),满足 \(C\in [0,T]\)
- 将所有点权 \(+C\)
- 将所有点权对 \(lim\) 取模
其中,\(T\)、\(lim\) 的值已经被给出。你的任务就是求出可能的最高得分。
看到分数,考虑二分答案 mid,那么合法性就是 \(\frac{S}{k+1} \ge mid\),即 \(S-kmid\ge mid\)。于是就不用记覆盖链数的状态了,覆盖一个减掉一个 mid 即可。
考虑 dp,设 \(f_{u,0/1/2}\) 表示考虑到 \(u\) 这一位,其中 \(u\) 不选/选了且父边不选/选了且父边选 的方案数。
考虑在一个终止的地方减掉 mid,也就是说如果 \(u\) 选了且父边不选则减掉。不难发现这样是对的。
其中 \(f_{u,1}\) 最多可以有两个儿子连上,\(f_{u,2}\) 最多可以有一个儿子。
维护 \(f_{v,2}-\max(f_{v,0}, f_{v,1})\) 的最大次大值即可转移。
但是如果我们不能枚举每一个 \(C\in [0,T]\),注意到如果当前所有数都能 \(+1\),那么这一组数是没用的。所以有用的 \(C\) 只有 \(\min(lim-1-a_i,T)\) 这些,个数 \(O(n)\)。
于是时间复杂度 \(O(n^2\log V)\)。
但是这个题卡的很恶心啊,所以进行一些优化:二分左端点设为 ans;如果当前 \(check(ans)\) 的值为假则直接 continue;;将可能的 \(C\) 去重、shuffle。
// Author: Aquizahv
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int N = 5005;
const double eps = 1e-9;
const double Eps = 1e-6;
int n;
ll MOD, T, a[N], b[N], c[N];
double f[N][3];
vector<int> g[N];
void dfs(int u, int fa, double mid)
{
f[u][0] = 0;
f[u][1] = b[u] - mid;
f[u][2] = b[u];
double mx[2] = {-1, -1};
for (auto v : g[u])
if (v != fa)
{
dfs(v, u, mid);
double x = max(f[v][0], f[v][1]);
double y = f[v][2] - x;
if (y > mx[0])
mx[1] = mx[0], mx[0] = y;
else if (y > mx[1])
mx[1] = y;
f[u][0] += x;
f[u][1] += x;
f[u][2] += x;
}
if (mx[0] > 0)
{
f[u][1] += mx[0], f[u][2] += mx[0];
if (mx[1] > 0)
f[u][1] += mx[1];
}
}
bool check(double mid)
{
dfs(1, 0, mid);
return max(f[1][0], f[1][1]) > mid - eps;
}
int main()
{
cin >> n >> MOD;
for (int i = 1; i <= n; i++)
scanf("%lld", a + i);
int u, v;
for (int i = 1; i < n; i++)
scanf("%d%d", &u, &v), g[u].push_back(v), g[v].push_back(u);
cin >> T;
double ans = 0;
for (int i = 1; i <= n; i++)
{
c[i] = MOD - 1 - a[i];
c[i] = min(c[i], T);
}
sort(c + 1, c + n + 1);
int nn = unique(c + 1, c + n + 1) - c - 1;
random_shuffle(c + 1, c + nn + 1);
for (int i = 1; i <= nn; i++)
{
for (int j = 1; j <= n; j++)
b[j] = (a[j] + c[i]) % MOD;
if (!check(ans))
continue;
double l = ans, r = n * MOD;
while (r - l > Eps)
{
double mid = (l + r) / 2;
if (check(mid))
l = mid;
else
r = mid;
}
ans = max(ans, l);
}
printf("%.9lf\n", ans);
return 0;
}

浙公网安备 33010602011771号