省选考试总结(2)
3-27 (出题人 h10 )
得分情况
题目
T1 : 组合数 , 斯特林数
给定 \(m\) 元不等式组 $$1 ≤ i ≤ n, x_i ≤ t$$ $$\displaystyle\sum _{i=1}^{m} x_i \le S$$
给定 \(S,t,m,n\) 求不等式正整数解的个数对 \(10^9+7\) 取模后的结果.
\((nt ≤ S ≤ 10^{18} , n ≤ m ≤ 10^9 , t ≤ 10^9 , m - n ≤ 1000)\)
这道题正解很难 , 还没看懂(这是道集训队作业题...)
还是只讲一下 \(30\) 部分分的容斥吧...
我们有个很显然的结论 (打表规律)
有 \(m\) 个正整数 , 总和不超过 \(S\) 的方案数(不等式解)为 \(\displaystyle \binom S m\) .
这个考试打表容易得到 , 还是讲一下为啥吧...
考虑隔板法 , 就是把 \(m-1\) 块隔板放进去.
就有 \(\displaystyle \sum _{i=0} ^ {S-1} \binom i {m-1} = \binom S m\)
前面那个式子的组合意义就是 , 枚举这些数的和为 \(i +1\) 然后分成 \(m\) 个数.
就是在 \(i\) 个间隔中插入 \(m-1\) 个隔板 所以从 \(0 \to S-1\)
然后这个式子推到后面的式子就可以用杨辉三角去理解...qwq
然后原题解是这样说的容斥
考虑容斥原理。
注意到如果没有 \(i ≤ n, x_i ≤ t\) 这个限制, 那我们就只需要用一个组合数就能直接算出
答案。
所以我们就可以把这个限制条件给容斥掉。
我们枚举至少有 \(i\) 个数的权值比 \(t\) 要大, 那么就强制把这 \(i\) 个数先取 \(t\), 然后就去掉了
这个限制条件, 就可以直接用一个组合数来算贡献了
就是我们把恰好 \(0\) 个 变成至少 \(k\) 个 然后去容斥一下....
最后的答案就很简单了.
T2 : 三维偏序 , 分类讨论
给你三个 \(1\) 到 \(n\) 的排列 \(a_i , b_i , c_i\)
称三元组 \((x, y, z)\) 是合法的,当且仅当存在一个下标集合 \(S [n]\) 满足\[\displaystyle (x, y, z) = (\max_{i∈S} a_i , \max_{i∈S} b_i , \max_{i∈S} c_i ) \]询问合法三元组的数量
\((n \le 10^5)\)
一个三元组是否合法 , 我们只要讨论三列的情况就行了... (更多列的话绝对包括在前面的情况中)
-
只有一列 , 这个情况所有三元组都行 ;
-
有两列 , 我们讨论不可行的方案 .
三个最大值集中在一列就不可行了.
我们只要用 cdq分治 + BIT 搞个三维偏序就行了
然后用总的减去不可行的
- 有三列 , 我们同样讨论不可行的方案 .
就是有至少两个最大值集中在一列 或者 三个最大值集中在一列 都不行
前者记作 \(X\) 后者记作 \(A\)
然后前者分三种情况讨论 \((a,c)\) \((b,c)\) \((a,c)\) 然后求一个二维偏序就行了.
后者用之前三维偏序的答案 , 再用组合数算下贡献就行了.
但这会算重复 所以答案就是 \(X - 3 \times A\)
T3 : 启发式合并 , 线段树合并
给你一颗有 \(n\) 个点的树,其中 \(1\) 号点为根节点,每个点都有一个权值 \(val_i\)
你可以从树中选择一些点,注意如果 \(i\) 与 \(j\) 都被选中且 \(j\) 在 \(i\) 的子树内,那么必须满足 \(val_i > val_j\)
请你求出最多能同时选出多少个点\((n \le 10^5, 0 \le val_i \le 10^9)\)
BZOJ4919 这题有两种解法.
第一种是容易理解 , 代码易写 , 但适用面狭窄的 \(set\) 启发式合并.
我们用这个 \(multiset\) 暴力维护 \(\mathrm{LIS}\)
每次合并的时候 把小的 \(multiset\) 合并到大的 \(mutilset\) 上 每次就是 \(\Theta(\min size * \log)\)
然后查找比当前 \(val[u]\) 大的第一个节点 , 然后拆掉 , 把自己加进去 (就是模拟那个 \(dp\) 过程)
最后就直接输出根节点的 \(size\) 就行了 然后时间复杂度就是\(\Theta(n \log ^ 2 n)\).
但常数不是很大 跑的飞起
第二种解法是用 线段树 维护一个 差分序列
我们先考虑一个比较水的 \(dp\) 我们令 \(dp[u][k]\) 表示 \(u\) 子树中最大值为 \(k\) 的能选的最多点数.
我们首先将权值离散化掉...
转移很显然了. $$\displaystyle dp[u][k] = \max (\sum_{v \in G[u]} dp[v][k], ((\sum_{v \in G[u]} dp[v][val[u]-1])+1)\times [k \ge val[u]])$$
如果强行维护这个 \(dp\) 数组是可行的 , 但是很麻烦 , 代码很难写(据一位大佬透露是用左子树维护右子树贡献)
但这个转移我们发现是 对于 \(k\) 来说 \(dp\) 值是单调的
然后我们尝试用差分表示出这个序列 例如
\[1,1,2,2,2,4,5,7 \]那么它的差分序列就是
\[1,0,1,0,0,2,1,2 \]
那么我们发现 这样的话 每个点的答案就是它差分序列的前缀和.
然后如何维护那个 \(+1\) 和取 \(\max\) 呢
我们观察在差分序列上的变化
仍然是刚才的序列 我们假设当前权值所在的位置为 \(4\)
那么我们会对 \(2+1\) 然后对后面所有数取 \(\max\)
然后差分序列就变成了
\[1,1,2,3,3,4,5,7 \]那么我们再看看它的差分序列 就变成了
\[1,0,1,1,0,1,2,2 \]我们发现它就是把离这个右边最近有数字的点的拉过来 \(1\) 就行了
理解一下的话就是对当前这个数往右走 如果差分为 \(0\) 那么我们就绝对可以更新
直至走到一个不能更新的就行了.
这个操作容易用线段树来维护qwq
复杂度 \(\Theta(n \log n)\) 但常数特别大 根本没有前面那个 \(\log ^2\) 快!!!
启发式合并
#include <bits/stdc++.h>
#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
using namespace std;
inline int read() { int x; scanf("%d", &x); return x; }
const int N = 1e5 + 1e3, maxn = 2010;
int val[N], fa[N], n;
vector<int> G[N];
multiset<int> S[N];
void merge(int x, int y) {
if (S[x].size() > S[y].size()) swap(S[x], S[y]);
for (auto it : S[x]) S[y].insert(it); S[x].clear();
}
void Dfs(int u) {
for (auto v : G[u]) { if (v == fa[u]) continue ; Dfs(v); merge(v, u); }
auto it = S[u].lower_bound(val[u]); if (it != S[u].end()) S[u].erase(it);
S[u].insert(val[u]);
}
int main () {
n = read(); For (i, 1, n) { val[i] = read(); fa[i] = read(); G[fa[i]].push_back(i); }
Dfs(1); printf ("%d\n", (int)S[1].size());
return 0;
}
线段树合并
#include <bits/stdc++.h>
#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
using namespace std;
inline bool chkmin(int &a, int b) {return b < a a = b, 1 : 0;}
inline bool chkmax(int &a, int b) {return b > a a = b, 1 : 0;}
inline int read() {
int x = 0, fh = 1; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
return x * fh;
}
#define lson lc[o], l, mid
#define rson rc[o], mid + 1, r
const int maxn = 1e7 + 1e3;
struct Segment_Tree {
int lc[maxn], rc[maxn], sz[maxn], Size;
int Merge(int x, int y) {
if (!x || !y) return x + y;
sz[x] += sz[y];
lc[x] = Merge(lc[x], lc[y]);
rc[x] = Merge(rc[x], rc[y]);
return x;
}
bool Delete(int o, int l, int r, int up) {
if (!sz[o]) return false;
if (l == r) { -- sz[o]; return true; }
int mid = (l + r) >> 1;
if (up <= mid && Delete(lson, up)) { -- sz[o]; return true; }
if (Delete(rson, up)) { -- sz[o]; return true; }
return false;
}
void Insert(int &o, int l, int r, int up) {
if (!o) o = ++ Size; ++ sz[o];
if (l == r) return ;
int mid = (l + r) >> 1;
if (up <= mid) Insert(lson, up);
else Insert(rson, up);
}
} T;
const int N = 2e6 + 1e3;
int root[N];
int val[N], fa[N], n, Hash[N], m;
vector<int> G[N];
void Dfs(int u) {
For(i, 0, G[u].size() - 1) {
int v = G[u][i];
Dfs(v);
root[u] = T.Merge(root[u], root[v]);
}
T.Delete(root[u], 1, m, val[u]);
T.Insert(root[u], 1, m, val[u]);
}
int main () {
n = read();
For (i, 1, n)
Hash[i] = val[i] = read(), fa[i] = read(), G[fa[i]].push_back(i);
sort(Hash + 1, Hash + 1 + n);
m = unique(Hash + 1, Hash + 1 + n) - Hash - 1;
For (i, 1, n)
val[i] = lower_bound(Hash + 1, Hash + 1 + m, val[i]) - Hash;
Dfs(1);
printf ("%d\n", T.sz[root[1]]);
return 0;
}
总结
今天的题目虽然不会做 , 但正解还是能大概知道是什么东西 只是原来没有认真学过而已
暴力分拿的很满 这样也是一条出路吧
然后写完暴力 尝试下正解 反正没事做了...
3-30 (出题人 dwjshift )
得分情况
题目
T1 : 组合数学
有一个 \(m × n\) 的数表 , 行与列的编号都从 \(1\) 开始 . 令 \(f_{i,j}\) 表示表格第 \(i\) 行第 \(j\) 列内的数 ,
那么对于表格的第 \(i(i > 1)\) 行有
\[\displaystyle \begin{cases} f_{i,1} = af_{i1,1} \\ f_{i,j}=af_{i-1,j}+bf_{i-1,j-1} \ (j>1) \end{cases} \]它给你第 \(p\) 行的数 询问 \(q\) 次 \(f_{x,y}\) 的数值.
\((n \le 10^5 , m \le 10^7)\) 部分分有 \((p=1,p=m)\)
我们先考虑 \(p=1\) 的情况 这个就是顺推了 类似于杨辉三角的形式 我们只需要看一下它的组合意义就行了.
我们发现这个只有两种走的方式 就是向下和向右下 我们考虑第一行列坐标为 \(1 \to y\) 的数字的贡献 .
因为只有右下能走横的 所以两个的步数是固定的 然后只要我们用组合数去选取就行了.
这里的答案易算 :
然后我们再考虑 \(p=m\) 的情况 表面上是个逆推 其实可以用顺推的式子表示出来
不难推出一个这样的式子qwq 我们令 \(\forall i, f_{i,0} = 0\)
你发现也还是只有两种走的方式 向右和向上 同前面的考虑 我们也能用贡献来表达出来
但注意第一步只能强行向上走!!!
这个次幂一定要先预处理!!! 不然32位老人机轻松卡T!!! TAT
这题我最后想出来了.... 但没预处理 成功多个 \(\log\) \(\mathrm{TLE}\) 2333
结合上面两种情况 其实所有情况就出来了2333
然后时间复杂度就是 \(\Theta(qn + m)\)
T2 : 前缀和+链表
有一种编程语言,这种语言的每个程序都是一个由字符 \(”<”\) 、\(”>”\) 和数字
\(0-9\) 构成的非空序列。程序运行时会有一个指针,一开始指向序列最左端的字
符,并且移动方向为向右。接下来指针会按照如下规则移动:
- 如果指针当前指向的位置是一个数字,那么输出这个数字,并且将该数字
减一,然后把指针按照当前移动方向移到下一个位置。若原来的数字为 \(0\)
则直接将其删掉。
- 如果指针当前指向的位置是 \(”<”\) 或者 \(”>”\) ,那么把指针的移动方向对应
地进行修改( \(”<”\) 改成向左, \(”>”\) 改成向右),然后按照新的移动方向移
动到下一个位置。如果新的位置也是 \(”<”\) 或者 \(”>”\) ,则删掉之前的 \(”<”\) 或
者 \(”>”\) 字符。
- 当指针移动到序列外时程序结束运行。
给出一段长度为 \(n\) 的程序。有 \(q\) 次询问,每次询问给出 \(l, r\) ,询问如果把
区间 \([l, r]\) 当做一段独立的程序运行的话,会把每个数字输出多少次。\((1 ≤ n, q ≤ 10^5 , 1 ≤ l ≤ r ≤ n)\)
题目有点长 不难发现是个大模拟题
注意到指针的移动是连续的,如果我们在程序开头加入足够多的 \(”>”\) ,那
么任意一个区间的运行过程一定都是整个程序运行过程的一个子段。因此只要
能求出这个子段的开始和结束时间,再直接用前缀和减一下就能得到答案了。
设 \(f_i\) 表示指针第一次从 \(i 1\) 移到 \(i\) 的时间 , \(g_i\) 表示指针第一次从 \(i\) 移到
\(i 1\) 的时间。先用链表模拟一遍整个程序,顺带记录下 \(f_i\) , \(g_i\) 。那么 \([l, r]\) 对应
的子段的开始时间就是 \(f_l\) , 结束时间就是 \(min(f_r+1 , g_l )\) 。
题解说的很清楚了 qwq
就是我再解释一下为啥结束时间是那样的
总共有两种离开这段序列的方式 一种是向右直接出去 一种是向右后再摆头向左出去
然后只要离开这段区间 答案就已经确定了 .
最后我们记一下前缀和 就可以直接出解了
我们令 \(k\) 为最大的数字 那么时间复杂度就是 \(\Theta(nk + q)\)
代码似乎有点恶心 std写的动态指针 不太想写了...
T3 : 对偶图+最小割
给出一个包含 \(n + 1\) 个结点的有向图,结点的编号为 \(0\) 到 \(n\) 。图中有 \(m\) 条
有向边,第 \(i\) 条有向边起点为 \(u_i\) ,终点为 \(v_i\) ,且长度为 \(w_i\) 。并且这些边还满
足如下的性质:
- 对任意一条边,满足 \(u_i < v_i\) 。
- 不存在两条边 \(i, j\) 使得 \(u_i < u_j < v_i < v_j\) 。
除了结点 \(0\) 和结点 \(n\) 以外,其余的每个结点都有颜色。现在需要你找出一
条从结点 \(0\) 走到结点 \(n\) 的最短路径。对于任意一种颜色,这条路径要么经过了
这种颜色的所有结点,要么就不经过这种颜色的任意一个结点。如果不存在这
样的路径,请输出 \(-1\) ,否则输出最短路径的长度。令 \(c_i\) 代表 \(i\) 号点的颜色
\(1 ≤ n, m, c_i ≤ 1000,0 ≤ u_i ≤ v_i ≤ n,1 ≤ w_i ≤ 10^6\)
这道题首先很(不)显然是对偶图 然后你对偶图的割对应原图的一个环
然后瞎jb搞 瞎搞 就可以啦
此坑待填qwq
总结
这几天身体有些不舒服(垃圾天气) 每天只能瞎搞了 大模拟也不想写 😦
但分数还是看的过去的qwq 希望省选的时候状态要好一些 ++rp
3-31 (出题人 dy0607 )
得分情况
题目
T1 : 状压dp
一个长为 \(n\) 的序列 \(A\) , 从 \(1\) 开始标号 , 一开始全为 \(0\) ,现在小 \(\mathrm{C}\) 想对它进行 \(m\) 次操作.
对第i次操作,他会选定恰好一个二元组 \((j, k)\) , \(j ∈ [1,n] , k ∈ [0, c]\) , 并令\(A_j = A_j + k\) ,
其中选中二元组 \((j, k)\) 的概率为 \(P_{i,j,k}\) .
小 \(\mathrm{C}\) 本来是想问你区间最大值的历史版本和的历史最大值的期望的,但鉴于这
是一道签到题,现在他只想知道 \(m\) 次操作后整个序列最大值的期望, 对 \(10^9+7\) 取模.
\((1 \le n \le 40, 1 \le m \le 10, 1 \le c \le 3)\)
数据范围很小 考虑一个 状压dp
我们先计算一下 \(f_{i,S,j}\) 表示 \(A_i\) 在 \(S\) 集合操作之后 , 值为 \(j\) 的概率
注意这个转移必须保证有序
然后我们可以用这个去做另一个 \(dp\) 了qwq
令 \(dp_{i,j,S}\) 表示前 \(i\) 个元素的最大值为\(j\) , 用掉了S集合的操作的概率 . 转移时枚举当前元
素用掉了哪些操作 , 以及进行这些操作后的值 .
我们这个用枚举子集就行了
复杂度 \(\Theta(n(cm)^23^m)\)
代码有点细节
#include <bits/stdc++.h>
#define For(i, l, r) for(int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Time() cerr << (double)clock() / CLOCKS_PER_SEC << endl
using namespace std;
inline bool chkmin(int &a, int b) {return b < a a = b, 1 : 0;}
inline bool chkmax(int &a, int b) {return b > a a = b, 1 : 0;}
inline int read() {
int x = 0, fh = 1; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
return x * fh;
}
void File() {
freopen ("max.in", "r", stdin);
freopen ("max.out", "w", stdout);
}
int n, m ,C;
typedef long long ll;
const ll Mod = 1e9 + 7;
const int N = 45;
int Mat[N][N][4];
int maxsta;
const int NN = (1 << 11);
int f[N][NN][N];
void Calcf() {
For (i, 1, n) {
f[i][0][0] = 1;
For (j, 1, maxsta) {
int val = __builtin_ctz(j), len = __builtin_popcount(j);
For (q, 0, C * len)
For (trans, 0, min(q, C))
(f[i][j][q] += 1ll * f[i][j ^ (1 << val)][q - trans] * Mat[val + 1][i][trans] % Mod) %= Mod;
}
}
}
int dp[N][NN][N];
int bitcount[NN];
int main () {
File() ;
n = read(); m = read(); C = read();
maxsta = (1 << m) - 1;
For (i, 1, m) {
For (j, 1, n)
For (k, 0, C)
Mat[i][j][k] = read();
}
For (i, 1, maxsta)
bitcount[i] = bitcount[i >> 1] + (i & 1);
Calcf();
dp[0][0][0] = 1;
For (i, 1, n) {
For (S12, 0, maxsta) {
for (register int S1 = S12; S1; S1 = (S1 - 1) & S12) {
register int S2 = S12 ^ S1, len1 = bitcount[S1], len2 = bitcount[S2];
For (j, 0, C * len1)
For (k, 0, C * len2)
(dp[i][S12][max(j, k)] += 1ll * dp[i - 1][S1][j] * f[i][S2][k] % Mod) %= Mod;
}
int len = bitcount[S12];
For (k, 0, C * len)
(dp[i][S12][k] += f[i][S12][k]) %= Mod;
}
}
int ans = 0;
For (i, 0, m * C)
(ans += 1ll * dp[n][maxsta][i] * i % Mod) %= Mod;
printf ("%d\n", ans);
return 0;
}
T2 : 线段树 + 单调栈
见我另一篇题解吧...
T3 : 矩阵快速幂 + 线段树 + 树链剖分
小 \(\mathrm{C}\) 有一棵 \(n\) 个点的树 , 0号点为根 , 每个点有 \(L\) 个权值,表示为 \(w[u][i]\) , \(u ∈[1, n]\) , \(i ∈ [1, L]\) .
现在他想对这棵树进行树链剖分,于是 \(\mathrm{fatesky}\) 教给他一种自创的剖分方法.
具体地,一棵树的剖分可以表示为若干条链 \(S_1 , S_2 , ..., S_k\) 满足 :
- 每个点属于且仅属于一条链 .
- 一条链在树上是一个连通块,即对 \(i, u, v ∈ S_i\) ,从 \(u\) 到 \(v\) 的简单路径不经过
任何不在 \(S_i\) 中的节点 .- \(i, S_i\) 的长度不超过 \(L\) .
- 链中所有节点深度不同 .
设一条链按深度 从大到小 排序后为 \(u_1 , u_2 , ..., u_m\) , \(\mathrm{fatesky}\) 定义一条链的权值
为 \(\sum_{i=1}^{m} w[u][i]\) , 一种剖分的权值为所有链的权值和.现在他想最大化剖分的权值.他会给出 \(q\) 个修改操作,每个修改操作给出一个点 \(u\) 以及 \(L\) 个值,表示修改之后的 \(w[u][i]\)
每个修改操作之后,你需要回答最大的剖分权值.\((1 \le n \le 10^5 , 2 \le L \le 4, 1 \le q \le 10^5)\)
这题似乎就是冬令营上讲过的动态动态规划了 每次调整一些参数 然后问你全局的 \(dp\) 答案
对于这道题我们可以有一个套路 矩阵乘法来动态 \(dp\) 转移
具体是这样实现的 我们把矩乘里面的 乘法 变为 加法 , 加法 变为 取 \(\max\) 然后依然满足结合律
(考虑 类似 \(\mathrm{flyod}\) 或者 展开暴力证 反正我也不会证 )
那么我们考虑链 且 \(L=2\) 的点 那么有个 \(dp\)
qwq然后我们就有一个转移矩阵了
然后我们用线段树维护一段区间矩阵乘积就行了 qwq
树上的比较麻烦 要分轻重链去考虑 然后转移矩阵有些不同 也是可以做的qwq
总结
今天又是暴力.... 但拿满了还是比较开心
套路知道的太少 \(dp\) 也太过于巧妙 以后也要这样稳啊!!
4-2 (出题人 罗进 )
得分情况
题目
T1 : 线段树
有一个长为 \(N\) 的数列 \(A\) , 有 \(Q\) 个操作:
- \(1\ L\ R\ X\) 对于 \(L ≤ i ≤ R\) , 把 \(A_i\) 变成 \(A_i ∧ X\) .
- \(1\ L\ R\ X\) 对于 \(L ≤ i ≤ R\) , 把 \(A_i\) 变成 \(A_i ∨ X\) .
- \(3\ L\ R\) 求 \(A_L , A_{L+1} , . . . , A_R\) 中的最大值.
\(∧\) 为按位与 , \(∨\) 为按位或.
\(N, Q ≤ 2 × 10^5 , 0 ≤ A < 2^{20}\)
这个题是第三遍出现了.... 原来只知道大概思路 并不知道具体实现 主要是 push_down 的部分
和吉司机一样 如果不用操作就返回 如果可以操作 分两种情况
一种这些位置改变的位一样 直接打标记返回 另一种是 改变的位不一样 暴力向下推
按位与的操作会把一些位变成 \(0\) , 然后如果这段区间的所有数这些位都相等 ,
就可以直接打标记 , 否则就要递归下去访问额外节点 , 按位或同理 .
push_down 的时候 有神奇的分配律 所以先 & 后 | 就行了qwq
然后经过神奇的势能分析 整个复杂度就是 \(\Theta(20N \log N)\) 的
T2 : 莫比乌斯反演
有一个长宽均为 \(N\) 的网格 , 每个格子的长宽均为 \(1\) . 除了最左下角的网
格外 , 其他格子中均有一个半径为 \(R\) 的圆 , 圆心在格子的正中心 . 现在你站
在最左下角的格子的正中心 , 求你能够看到多少个圆 , 视线不能够穿过圆 .\((N \le 10^9, 1 \le R \le 5 \times 10^5)\)
这题目很鬼畜....
直接粘题解算了...
先建一个座标系, 人的位置为 \((0, 0)\) . 如果到某个圆心 \((x, y)\) 的视线
被另一个在 \((a, b)\) 的圆挡住, 那么在 \((x, y)\) 的那个圆被挡住了一半, 显然
\((x a, y b)\) 会挡住它的另一半. 所以只有能够看到圆心才能够看到这个圆.可以考虑枚举圆 \((x, y)\), 再枚举圆 \((a, b)\) , 看 \((a, b)\) 是否挡住了 \((x, y)\) . 这
样是 \(O(N^4)\) , 但是发现固定了 \(x, y, a\) 之后, 只要选 \(b = \frac{x}{y} a\) 就行了, 这样是 \(O(N^3 )\) .
怎么判断是否挡住了, 由点到直线的距离公式可以推出 \(\frac{(aybx)^2} {(x^2 +y^2 )} ≤ R^2\) 就算被挡住.如果 \((x, y)\) 不互质, 那么肯定是会被挡住的, 如果 \((x, y)\) 互质, 那么肯定
存在 \(a, b, a < x, b < y, |ay bx| = 1\) , 这个时候肯定是最近的, 那么就只要
\((x^2 + y^2)<\frac{1}{R^2}\) 就不会被挡住.
现在就是要求一个有多少个互质的点在一个圆和正方形内.\(\frac{1}{R}< 10^6\) ,
所以可以直接枚举约数 \(d\) , 算出有多少对 \(x, y\) 都能被 \(d\) 整除, 然后容斥.
这个题 最重要的就是上面的结论了qwq 就是看到一个圆必能看到它的圆心 反之则反
然后根据后面另外一个结论 我们可以直接推出一个算的式子
那么这个直接反演一套就是
我们令 \(\frac{1}{R} = r\) .
那么这个第一位暴力枚举 第二位也可以暴枚 第三位的话 我们先用 sqrt 大概算出 \(y\) 的范围
再放缩一下 保证正确性
代码我是抄 Wearry 的.... (自认为不能写的再好了qwq) ...
#include <bits/stdc++.h>
#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
using namespace std;
inline bool chkmin(int &a, int b) {return b < a a = b, 1 : 0;}
inline bool chkmax(int &a, int b) {return b > a a = b, 1 : 0;}
inline int read() {
int x = 0, fh = 1; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
return x * fh;
}
void File() {
freopen ("b.in", "r", stdin);
freopen ("b.out", "w", stdout);
}
const int N = 1e6 + 1e3;
int mu[N], cnt = 0;
bitset<N> is_prime;
typedef long long ll;
int prime[N];
void Init(int maxn) {
mu[1] = 1; is_prime.set();
is_prime[1] = is_prime[0] = false;
For (i, 2, maxn) {
if (is_prime[i]) {
prime[++ cnt] = i;
mu[i] = -1;
}
For (j, 1, cnt) {
int res = prime[j] * i;
if (res > maxn) break ;
is_prime[res] = false;
if (i % prime[j]) mu[res] = -mu[i];
else { mu[res] = 0; break ; }
}
}
}
const ll Limit = 1e6;
ll n, r, back;
ll res = 0;
inline ll F(ll n, ll lim) {
ll res = 0;
for (ll x = 1; x <= n && x * x <= lim; ++ x) {
ll y = min((ll) sqrt(max(lim - x * x - 5, 0ll)), n);
while (y < n && (y + 1) * (y + 1) + x * x <= lim) ++ y;
res += y;
}
return res;
}
int main() {
File();
Init((int)1e6);
n = read(); r = read();
r = (Limit * Limit - 1) / (r * r);
-- n;
for (ll d = 1; d * d <= r; ++ d) if (mu[d])
res += mu[d] * F(n / d, r / d / d);
printf ("%lld\n", res + 2);
}
T3 : 树上博弈
\(\mathrm{Alice}\) 和 \(\mathrm{Bob}\) 在玩游戏.
有一棵 \(N\) 个节点的树, \(\mathrm{Alice}\) 和 \(\mathrm{Bob}\) 轮流操作, \(\mathrm{Alice}\) 先手. 一开始树
上所有节点都没有颜色, \(\mathrm{Alice}\) 每次会选一个没有被染色的节点并把这个节
点染成红色 (不能不选), \(\mathrm{Bob}\) 每次会选一个没有被染色的节点并把这个节点
染成蓝色 (不能不选). 当有人操作不了时, 游戏就终止了.
\(\mathrm{Alice}\) 的最终得分为红色连通块的个数, \(\mathrm{Bob}\) 的最终的分为蓝色连通块
的个数. 设 \(\mathrm{Alice}\) 的得分为 \(K_A\) , \(\mathrm{Bob}\) 的得分为 \(K_B\) , \(\mathrm{Alice}\) 想让 \(K_A K_B\)
尽可能大, \(\mathrm{Bob}\) 则想让 \(K_A K_B\) 尽可能小, 假设两人都采取最优策略操作, 那么
\(K_A K_B\) 会是多少.
这里指的连通块为一个点集 \(S\) , 满足集合内点的颜色相同, 且每个点都能只经过 \(S\) 内的点走到 \(S\) 内的其他点, 而且如果将任意 \(u(u \notin S)\) 加入 \(S\) ,那么上述性质将不能被满足.
又是个结论题qwq 直接挂结论和证明吧 很巧妙
这是一棵树, 红色连通块数量等于红色的点数减去两端都为红色的边的
条数, 蓝色连通块数量同理, 设答案为 \((S_A E_A) (S_B E_B)\) .红色的点数和蓝色的点数是固定的, 所以就是求两边均为红色的边数和
两边均为蓝色的边数之差, 求 \(E_B E_A\) .对于一条边, 我们在它的两个端点处加 \(1\), \(\mathrm{Alice}\) 选了一个点就减去这个点的点权,
\(\mathrm{Bob}\) 选了一个点就减去这个点的点权.不难发现如果一条边的两个端点同色就会贡献 \(-2\) 或 \(2\) 的代价,
否则就没有代价, 那么我们算出来的就是 \(2(E_B E_A)\).
\(\mathrm{Alice}\) 和 \(\mathrm{Bob}\) 肯定会去选点权最小的点, 只要排序就行了.
总结
今天怎么三道类似结论题的东西啊TAT
以后要大力猜结论了 尤其是博弈题
首先暴力 然后没有什么思路 就开始乱搞吧qwq
4-3 (出题人 罗进)
得分情况
题目
T1 : 点分治
有一棵 \(N\) 个节点的树, 令 \(d(i, j)\) 为 \(i\) 到 \(j\) 经过的边的条数.
有 \(M\) 个炸弹, 第 \(i\) 个炸弹在节点 \(pos_i\) 上, 威力为 \(power_i\) ,
它会对所有节点 \(j\) 造成 \(\max(0, power_i d(pos_i , j))\) 的伤害.
求出每个节点最终受到的伤害.\((1 \le N \le 2*10^5)\)
考试时候想了两小时差分 心态爆炸...
正解是点分治qwq (原来没写过....)
这种与树上距离有关的题 而且要统计树上所有对数的距离 一般都是用点分治
这题也可以用那个去做 那个取 \(\max 0\) 有点麻烦
我们把那个形式在点分树中转化一下 就成了
我们令 \(d_i\) 为 \(i\) 在当前点分树中的深度
我们对于 \(i\) 计算一下那个差值 然后记在两个数组中
我们令 \(sum_k\) 为 \(power_i - d_i\le k\) 的贡献前缀和
令 \(s_k\) 为 \(power_i - d_i \le k\) 的个数和
然后每个点 \(lis_i\) 得到的贡献就是
然后每次这样算就行了 \(maxdep\) 要 \(+1\) 避免最深的那个贡献算错
#include <bits/stdc++.h>
#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
using namespace std;
inline bool chkmin(int &a, int b) {return b < a a = b, 1 : 0;}
inline bool chkmax(int &a, int b) {return b > a a = b, 1 : 0;}
inline int read() {
int x = 0, fh = 1; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
return x * fh;
}
void File() {
freopen ("a.in", "r", stdin);
freopen ("a.out", "w", stdout);
}
const int inf = 0x7f7f7f7f;
const int N = 200010, M = N << 1;
int n, m;
int Head[N], Next[M], to[M], e = 0;
bool vis[N];
vector<int> G[N], V[N];
int sz[N], maxsz[N], sumnode, rt;
void Get_Root(int u, int fa) {
sz[u] = maxsz[u] = 1;
for (int v : G[u]) if (!vis[v] && v != fa) {
Get_Root(v, u);
sz[u] += sz[v];
chkmax(maxsz[u], sz[v]);
}
chkmax(maxsz[u], sumnode - sz[u]);
if (maxsz[u] < maxsz[rt]) rt = u;
}
int maxdep, lis[N], cnt, dep[N];
void Get_Dep(int u, int fa) {
chkmax(maxdep, dep[u]); lis[++ cnt] = u;
for (int v : G[u]) if (!vis[v] && v != fa) {
dep[v] = dep[u] + 1;
Get_Dep(v, u);
}
}
typedef long long ll;
ll s[N], sum[N];
ll ans[N];
void Calc(int u, int init, int opt) {
dep[u] = init; maxdep = 0; cnt = 0;
Get_Dep(u, 0); ++ maxdep;
For (i, 1, cnt)
for (int power : V[lis[i]]) {
int delta = power - dep[lis[i]];
if (delta <= 0) continue ;
++ s[min(maxdep, delta)];
sum[min(maxdep, delta)] += delta;
}
For (i, 1, maxdep) s[i] += s[i - 1], sum[i] += sum[i - 1];
For (i, 1, cnt)
ans[lis[i]] += (sum[maxdep] - sum[dep[lis[i]]] - 1ll * (s[maxdep] - s[dep[lis[i]]]) * dep[lis[i]]) * opt;
For (i, 1, maxdep) s[i] = sum[i] = 0;
}
void Solve(int u) {
vis[u] = true;
Calc(u, 0, 1);
for (int v : G[u]) if (!vis[v]) {
Calc(v, 1, -1); rt = 0; sumnode = sz[v];
Get_Root(v, 0);
Solve(rt);
}
}
int main () {
File();
n = read(); m = read();
For (i, 1, n - 1) { int u = read(), v = i + 1; G[u].push_back(v); G[v].push_back(u); }
For (i, 1, m) {
int pos = read(), power = read();
V[pos].push_back(power);
}
maxsz[0] = inf; rt = 0; sumnode = n; Get_Root(1, 0); Solve(rt);
For (i, 1, n) printf ("%lld\n", ans[i]);
return 0;
}
T2 : 动态规划 组合数学
求有多少 \(N\) 个的竞赛图包含至少一个长度为 \(K\) 的简单环, 输出答案模
\(10^9 + 7\) 的结果.
竞赛图 : 任意两个点之间都有一条有向边的图.
简单环 : 不经过重复节点的回路.\((3 \le K \le N \le 5000)\)
这个题据说又是原题.... ( dy0607 出过的原题)
有一些结论qwq
竞赛图的中如果一个强连通分量的大小大于 \(K\) , 那么这个强连通分量
内存在长度为 \([3, K]\) 的简单环, 然后这题就是要至少有一个强连通分量的大
小大于等于 \(K\).
用这个结论的话 我们就能得到一些 \(dp\) 方程
令 \(p_i\) 为有 \(i\) 个点的竞赛图的数量 那么显然有
令 \(f_i\) 为有 \(i\) 个点的竞赛图 全部强连通的方案数 那么就有
这个意思就是 将全部方案 减去不行的方案就是最后的答案 把之前的强连通分量缩点考虑
不行的方案 我们考虑用组合数去计算
然后 令 \(g_i\) 为有 \(i\) 个点的竞赛图 最大强连通分量的大小 小于 \(k\) 的方案数 那么就有
这个你考虑将之前的强连通进行缩点 然后变成链 考虑前后连边的方案数
然后答案就是
这就做完了qwq
T3 : 树形dp
给出一棵 \(N\) 个点的有根树, 这棵树以 \(1\) 号节点为根. 现在你需要对于
每个非叶子节点 \(Y\) 选择它的一个儿子 \(X\) , 并把连接 \(X, Y\) 的边标记为重边,
其它的边为轻边.
对于这棵树的每个叶子节点, 把它到根节点经过的边依次写下来, 一条
轻边的代价为 \(1\) , 一段连续的重边代价为 \(\log_2 L +1\) ,
\(L\) 为这段重边的数量,这个叶子的代价等于这些代价之和.
求出在最优情况下, 所有叶子的代价中的最大值最小是多少.( 数据组数 \(T ≤ 10\) , \(2 ≤ N ≤ 10^5\) )
这题有人用 7 种剖分 水了 \(70\) 分 orz
正解比较麻烦 就只介绍 \(O(n^2)\) 的吧
每次转移的时候考虑从儿子最大答案的地方进行剖分 但是这样转移的话贡献很难算
那么我们可以用个 \(dp\) 去计算
可以设一个 \(f [i][k]\) 表示 \(i\) 有一条向上长为 \(k\) 的重链时的最优答案
然后转移的时候记一个前后缀的最大值 这样去转移就行了
正解的话就是取值比较少 用那个转移就行了
总结
今天考的太难了... 刚 T1 2h 心态直接爆炸
其实 T3 那个 \(dp\) 冷静下来可以想出来的
以后碰到难题 不要死磕 先写个暴力先溜 然后考虑后面骗分
记住 千万要冷静!!!!
集训最后一次了 HNOI2018 加油!!!!