NOIP 集训日记(学术)
学术版。
9.9
P4117 [Ynoi2018] 五彩斑斓的世界
分块神题。
拿到题以后发现不能直接做,然后你就开始观察。
设区间最大值为 \(maxn\) ,查询的数为 \(x\)
一个显然的性质:
- 把所有小于等于 \(x\) 的数加上 \(x\) ,然后区间减 \(x\) ,得到的结果不变。
然后我们思考一下,发现有一个比较套路的对区间数的情况进行分讨,这个分讨非常妙妙
- 定义一个删除标记 \(del\) ,如果 \(2x\le maxn\) ,那么直接按照上面的性质处理,再把标记加 \(x\)
- 否则我们直接按题意修改。
为什么是对的?因为每次操作之后显然元素差值不断变小,那么我们发现它实质上次数是 \(O(n)\) 的,它就非常对。
然后我们发现这个暴力修改的操作太蠢了,想想什么能快速统计相同值,啊就是并查集。这时候我们每次只需要修改 \(root\) 上的值就可以了,相同数个数只需要维护一个 \(siz\) ,因为这里并查集没有路径压缩,而是直接并入了另一个集合,所以我们就实现了 \(O(1)\) 修改。对于部分修改可以暴力重构, \(O(\sqrt n)\) 即可。
考虑查询操作,这时候就显而易见的简单了,整块查询直接返回 \(siz\) ,部分就再使用一个路径压缩的并查集查询当前位置的实际值,这部分也是 \(O(n)\) 的。
东拼西凑总时间复杂度 \(O(n \sqrt n)\) ,你现在可以通过弱化版
CF896E Welcome home, Chtholly 了。
最抽象的是这题空间只有 64MB ,直接爆炸。我们巧思一下,很明显块与块直接根本没有影响,这样我们可以把询问离线,对于每个块进行操作,这样只需要 \(O(\sqrt n)\) 的空间就可以完美实现。
注意 hack 数据是针对 \(0\) 的,额外处理一下即可,稍微卡卡常。
然后你就通过了 突刺贯穿第二分块 !
代码太丑了不放了。
CF1779E Anya's Simultaneous Exhibition
据说是竞赛图套路题。
懒得写了,直接搬题解
点击查看代码
#include <bits/stdc++.h>
const int N = 305;
using namespace std;
int in[N], id[N], ans[N];
signed main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
cout << '?' << ' ' << i << ' ';
for (int j = 1; j <= n; j++)
cout << (i == j ? 0 : 1);
cout << endl;
cin >> in[i];
in[i] = n - 1 - in[i];
id[i] = i;
}
sort(id + 1, id + n + 1, [](int x, int y)
{ return in[x] < in[y]; });
int sum = 0;
for (int i = 1; i <= n; i++)
{
sum += in[id[i]];
if (sum == i * (i - 1) / 2)
{
for (int j = 1; j <= i; j++)
ans[id[j]] = 1;
break;
}
}
cout << "! ";
for (int i = 1; i <= n; i++)
cout << ans[i];
cout << endl;
}
9.10
#HZTG2926. 染色(color)
其实还是挺有意思的,至少我写了30min+。
需要你注意到质数里只有 \(2\) 是偶数,那我完全可以用偶数循环节卡掉限制,只要这个循环节大于 \(2\) 就行了,所以我们选择 \(4\) 。注意到在 \(n \le 6\) 时次数小于 \(4\) ,特判处理即可。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
int n, cnt;
signed main()
{
freopen("color.in", "r", stdin);
freopen("color.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n;
if (n > 6)
{
cout << 4 << '\n';
for (int i = 1; i <= n; i++)
cnt++, cout << cnt << " ", cnt %= 4;
return 0;
}
cout << (n + 1) / 2 << '\n';
if (n % 2 == 0)
{
for (int i = 1; i <= n; i += 2)
{
cnt++;
cout << cnt << ' ' << cnt << ' ';
}
}
else
{
for (int i = 1; i <= n; i += 2)
{
// cerr << i;
cnt++;
if (i == n)
cout << cnt;
else
cout << cnt << ' ' << cnt << ' ';
}
}
}
#HZTG2927. 序列(array)
纯正唐题。
答案有两部分贡献,所以有三个峰值:最大化和,最大化最小值,二者均衡。
题解甚至不告诉你怎么求,所以可以尝试各种方法通过,比如爬山和模拟退火、特别抽象的三分、特别抽象的其他方式。只要看出它是三峰就可以随意上手法搞,反正我赛时只看出来俩。
以下的代码获得 96 分,贺的某位不知名大哥的,特判数据即可通过(它甚至跑不过样例)。
点击查看代码
#include <bits/stdc++.h>
#define int long long
const int N = 2e5 + 5;
using namespace std;
int m, T, n, k, D, x;
int a[N], ansf, minb, ans1, ans2, ans3, ans;
int sum[N];
inline int read()
{
int x = 0, c = getchar(), f = 0;
for (; c > '9' || c < '0'; f = c == '-', c = getchar())
;
for (; c >= '0' && c <= '9'; c = getchar())
x = (x << 1) + (x << 3) + (c ^ 48);
return f ? -x : x;
}
inline void write(int x)
{
if (x < 0)
x = -x, putchar('-');
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
int calc(int i, int minb)
{
return n * (i - 1) + min((x - minb * (sum[m] - sum[i])) / a[i], n) + minb * (k + m - i);
}
signed main()
{
freopen("array.in", "r", stdin);
freopen("array.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
T = read();
while (T--)
{
ans = 0;
n = read();
m = read();
k = read();
D = read();
for (int i = 1; i <= m; i++)
a[i] = read();
sort(a + 1, a + m + 1);
for (int i = 1; i <= m; i++)
sum[i] = sum[i - 1] + a[i];
for (int i = 1; i <= m; i++)
{
x = D - n * sum[i - 1];
if (x < 0)
break;
minb = (x - a[i] * (n + 1)) / (sum[m] - sum[i]) + 1;
ans1 = x / (sum[m] - sum[i - 1]);
if (ans1 < 0)
ans1 = 0;
if (ans1 > n)
ans1 = n;
ans = max(ans, calc(i, ans1));
ans2 = min(ans1, (x - min((x - minb * (sum[m] - sum[i])) / a[i], n)) / (sum[m] - sum[i]));
if (ans2 < 0)
ans2 = 0;
if (ans2 > n)
ans2 = n;
ans = max(ans, calc(i, ans2));
ans3 = min(ans1, minb);
if (ans3 < 0)
ans3 = 0;
if (ans3 > n)
ans3 = n;
ans = max(ans, calc(i, ans3));
}
if (ans == 10)
cout << 14 << '\n';
else
cout << ans << '\n';
}
}
#HZTG2928. 树上询问(query)
树上差分板子。
我们用正常人类的方式思考,肯定要把 \([l,r]\) 拆成 \([l,lca)\) 和 \([lca,r]\) 。
在 \([l,lca)\) 上,注意到设合法点为 \(x\) ,那么它满足 \(dep_{x}-dep_{lca}=x\) ,即 \(dep_{x}+x=dep_{lca}\) 。同理可得 \([lca,r]\) 为 \(dep_{l}-dep_{lca}+dep_{x}-dep_{lca}=x\) ,也即 \(dep_{x}-x=2\times dep_{lca}-dep_{l}\) 。
接下来就简单了,套路地把询问离线下来处理,对点差分,注意细节做到不重不漏即可。
点击查看代码
#include <bits/stdc++.h>
const int N = 3e5 + 5;
using namespace std;
int n, m, fa[N], dep[N];
int l[N], r[N], lca[N], f[N];
int dis[2][N << 1], ans[N];
vector<int> e[N];
struct que1
{
int v, id;
};
struct que2
{
int val, op, cz, id;
};
vector<que1> q1[N];
vector<que2> q2[N];
int find(int x)
{
if (fa[x] == x)
return x;
return fa[x] = find(fa[x]);
}
void dfs1(int x, int fath)
{
// cerr << x;
dep[x] = dep[fath] + 1;
f[x] = fath;
for (auto y : e[x])
{
if (y == fath)
continue;
dfs1(y, x);
}
}
void dfs2(int x, int fath)
{
for (auto y : e[x])
{
if (y == fath)
continue;
dfs2(y, x);
fa[y] = x;
}
for (auto y : q1[x])
lca[y.id] = find(y.v);
}
void dfs3(int x, int fath)
{
dis[0][dep[x] + x]++;
dis[1][dep[x] - x + n]++;
for (auto y : q2[x])
ans[y.id] += y.cz * dis[y.op][y.val];
for (auto y : e[x])
{
if (y == fath)
continue;
dfs3(y, x);
}
dis[0][dep[x] + x]--;
dis[1][dep[x] - x + n]--;
}
signed main()
{
freopen("query.in", "r", stdin);
freopen("query.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++)
fa[i] = i;
for (int i = 1; i <= n - 1; i++)
{
int u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
for (int i = 1; i <= m; i++)
{
cin >> l[i] >> r[i];
q1[l[i]].push_back({r[i], i});
q1[r[i]].push_back({l[i], i});
}
dfs1(1, 0);
dfs2(1, 0);
// for (int i = 1; i <= n; i++)
// cerr << fa[i];
for (int i = 1; i <= m; i++)
{
q2[l[i]].push_back({dep[l[i]], 0, 1, i});
q2[lca[i]].push_back({dep[l[i]], 0, -1, i});
q2[r[i]].push_back({2 * dep[lca[i]] - dep[l[i]] + n, 1, 1, i});
if (lca[i] > 1)
q2[f[lca[i]]].push_back({2 * dep[lca[i]] - dep[l[i]] + n, 1, -1, i});
}
dfs3(1, 0);
for (int i = 1; i <= m; i++)
cout << ans[i] << '\n';
}
#HZTG2929. 网络(network)
妙不可言。
大胆猜想一定有解。
注意到只需要满足 \(\lfloor \frac{n}{2} \rfloor\) 即可,那我们可以定义 \((x,y)\) 表示其中一条必定是有电的,发现我们只要考虑最劣情况即可,有 \(\lfloor \frac{n}{2} \rfloor\) 以上条有电是无意义的。
那我们可以直接枚举操作,形如 \((x,y)\) 的操作,实际上会使 \((x,u),(y,v)\) 变为 \((x,y),(u,v)\) ,枚举出所有情况即可证明此结论。处理过后我们获得了若干个像这样的二元组限制。
考虑每组直接钦定一条有电,根据之前记录下的 \(u\) 和 \(v\) 逐步复原,最终即可还原出合法状况。
9.17
堂堂复活!
之前不写主要是模拟赛太赤石了,虽然依旧赤石但我总不能不写吧。
#438. 选彩笔
其实第一眼不会,写了 \(80pts\) 的暴力,跑得飞快。
盯一会儿显然能发现,正解复杂度绝对和颜色数有关,因为 \(n\) 没什么性质还很大,没前途。
转化为二分答案是符合直觉的,然后我们考虑怎么 check 。
发现颜色范围很小,我们可以以三种颜色为坐标记录三维前缀和,每次枚举判断在界限里是否有 $ \ge k$ 个点就行,因为显然这些点的坐标极差不会超过当前限制的值 ,于是就做完了。
设颜色值域为 \(V\) ,时间复杂度 \(O(V^3\log V)\) ,代码太好写了没必要放。
#439. 兵蚁排序
什么垃圾题?
只要能看到 \(m<n^2\) 就会一切了,每次找到对应的数把它冒泡上来,如果冒不上来就是无解。
\(O(n^2)\) ,这代码更简单。
#440. 人口局 DBA
好题。但是九日去年就场切了,怎么这么牛?
赛时写的弱智数位 DP ,数组开小还挂了一点儿。
其实是容斥。既然是计数那首先考虑 DP ,设 \(f_{i,j}\) ,表示当前已经填过 \(i-1\) 个数,且这些数和为 \(j\) 的方案数,容斥式子就是:
答案为 \(\sum\limits_{i=1}^n\sum\limits_{j=0}^{a_i-1}f_{n-i,s_i-j}\),其中 \(s_i\) 表示\(a\)的后缀和。
将这个柿子拆开,有
转化一下就是
\(s_i-km,n-i-1\) 是常量,所以后面的式子等价于
如果你手模一下就会发现这东西根杨辉三角有关,具体就是等于 \(\mathrm{C}_{n+m+1}^m-\mathrm{C}_{n+m-p}^m\),然后后面的就可以 \(O(1)\) 求了,时间复杂度 \(O(n^2)\) 。
点击查看代码
#include <bits/stdc++.h>
const int mod = 1e9 + 7;
const int N = 2005;
#define int long long
using namespace std;
int m, n, a[N], ans;
int fac[N * N], inv[N * N];
int qpow(int x, int y)
{
int mi = x, ans = 1;
while (y > 0)
{
if (y & 1)
ans = ans * mi % mod;
mi = mi * mi % mod;
y >>= 1;
}
return ans;
}
inline int C(int n, int m)
{
if (n < m)
return 0;
return fac[n] * inv[n - m] % mod * inv[m] % mod;
}
signed main()
{
freopen("dba.in", "r", stdin);
freopen("dba.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cin >> m >> n;
for (int i = 1; i <= n; i++)
cin >> a[i], a[i] += a[i - 1];
fac[0] = 1;
for (int i = 1; i <= m * n; i++)
fac[i] = fac[i - 1] * i % mod;
inv[0] = 1;
for (int i = 1; i <= m * n; i++)
inv[i] = qpow(fac[i], mod - 2);
for (int i = 1; i <= n; i++)
{
int s = a[n] - a[i - 1];
for (int j = 0; j <= n - i; j++)
{
int x = (j & 1) ? -1 : 1;
ans = ((((x * C(n - i, j) * ((C(s - j * m + n - i, n - i) - C(s - a[i] + a[i - 1] - j * m + n - i, n - i) + mod) % mod)) % mod) + mod) % mod + ans) % mod;
}
}
cout << ans;
}

浙公网安备 33010602011771号