省选考试总结(2)

3-27 (出题人 h10 )

得分情况

\[20+50+50=120 \]

题目

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\) 个 然后去容斥一下....

最后的答案就很简单了.

\[\displaystyle \mathrm{ans} = \sum_{i=0}^n \binom {S-ti} m * (-1)^i * \binom n i \]

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)\)

一个三元组是否合法 , 我们只要讨论三列的情况就行了... (更多列的话绝对包括在前面的情况中)

  1. 只有一列 , 这个情况所有三元组都行 ;

  2. 有两列 , 我们讨论不可行的方案 .

三个最大值集中在一列就不可行了.

我们只要用 cdq分治 + BIT 搞个三维偏序就行了

然后用总的减去不可行的

  1. 有三列 , 我们同样讨论不可行的方案 .

就是有至少两个最大值集中在一列 或者 三个最大值集中在一列 都不行

前者记作 \(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 )

得分情况

\[60+0+30=90 \]

题目

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\) 的数字的贡献 .

因为只有右下能走横的 所以两个的步数是固定的 然后只要我们用组合数去选取就行了.

这里的答案易算 :

\[\displaystyle ans = \sum_{i=1}^{y} \binom {x-1}{y-i} \times b^{y-i} \times a^{(x-1)-(y-i)} \times f_{1,i} \]

然后我们再考虑 \(p=m\) 的情况 表面上是个逆推 其实可以用顺推的式子表示出来

不难推出一个这样的式子qwq 我们令 \(\forall i, f_{i,0} = 0\)

\[\displaystyle f_{i,j}=(f_{i+1,j}-bf_{i,j-1}) \times \frac{1}{a} \]

你发现也还是只有两种走的方式 向右和向上 同前面的考虑 我们也能用贡献来表达出来

但注意第一步只能强行向上走!!!

\[\displaystyle ans = \sum_{i=1}^{y} \binom {(m-x-1)+(y-i)}{y-i} \times (-b)^{y-i} \times (\frac{1}{a})^{(m-x)+(y-i)} \times f_{m,i} \]

这个次幂一定要先预处理!!! 不然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 )

得分情况

\[48+45+28=121 \]

题目

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\) 的概率

注意这个转移必须保证有序

\[\displaystyle f_{i,S,j} = \sum _{k = \max \{A_i \in S\}} \sum _{trans=0}^{\min(j,c)} f_{i,\complement_kS,j-trans} \times P_{k,i,trans} \]

然后我们可以用这个去做另一个 \(dp\) 了qwq

\(dp_{i,j,S}\) 表示前 \(i\) 个元素的最大值为\(j\) , 用掉了S集合的操作的概率 . 转移时枚举当前元
素用掉了哪些操作 , 以及进行这些操作后的值 .

\[dp_{i-1,j,S1} \times f_{i,S2,k} \to dp_{i,\max(j,k),S_1+S_2} \ \ (S_1 \cup S_2 = \emptyset) \]

我们这个用枚举子集就行了

复杂度 \(\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\)

\[dp(i,2)= dp(i + 1, 1) + w[i][2] \\ dp(i, 1) = \max\{dp(i + 1, 1), dp(i + 1, 2)\} + w[i][1] \]

qwq然后我们就有一个转移矩阵了

\[\begin{pmatrix} dp(i + 1, 1) & dp(i + 1, 2) \end{pmatrix} \times \begin{pmatrix} w[i][1] & w[i][2] \\ w[i][1] & - \infty \end{pmatrix} = \begin{pmatrix} dp(i,1) & dp(i,2) \end{pmatrix} \]

然后我们用线段树维护一段区间矩阵乘积就行了 qwq

树上的比较麻烦 要分轻重链去考虑 然后转移矩阵有些不同 也是可以做的qwq

总结

今天又是暴力.... 但拿满了还是比较开心

套路知道的太少 \(dp\) 也太过于巧妙 以后也要这样稳啊!!

4-2 (出题人 罗进 )

得分情况

\[60+20+10=90 \]

题目

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\) 的圆 , 圆心在格子的正中心 . 现在你站
在最左下角的格子的正中心 , 求你能够看到多少个圆 , 视线不能够穿过圆 .

pic

\((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 就是看到一个圆必能看到它的圆心 反之则反

然后根据后面另外一个结论 我们可以直接推出一个算的式子

\[\displaystyle \sum _{x=1}^n \sum_{y=1}^n [x \bot y] \cdot [(x^2+y^2< \frac{1}{R^2})] \]

那么这个直接反演一套就是

我们令 \(\frac{1}{R} = r\) .

\[\displaystyle \sum_{d=1}^{\min(n, r)} \mu(d) \sum_{x=1}^{\lfloor\frac{r}{d}\rfloor} \sum_{y} [x^2+y^2<\frac{r}{d^2}] \]

那么这个第一位暴力枚举 第二位也可以暴枚 第三位的话 我们先用 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 (出题人 罗进)

得分情况

\[10+0+0=10 \]

题目

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\) 在当前点分树中的深度

\[res_j=(power_i-d_i)-d_j \]

我们对于 \(i\) 计算一下那个差值 然后记在两个数组中

我们令 \(sum_k\)\(power_i - d_i\le k\) 的贡献前缀和

\(s_k\)\(power_i - d_i \le k\) 的个数和

然后每个点 \(lis_i\) 得到的贡献就是

\[(sum[maxdep]-sum[dep[lis[i]]])-(s[maxdep]-s[dep[lis[i]]]) \times dep[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\) 个点的竞赛图的数量 那么显然有

\[p_i = 2^{\frac{i \times(i-1)}{2}} \]

\(f_i\) 为有 \(i\) 个点的竞赛图 全部强连通的方案数 那么就有

\[\displaystyle f_i=p_i - \sum_{j=1}^{i-1} f_j \times p_{i-j} \binom i j \]

这个意思就是 将全部方案 减去不行的方案就是最后的答案 把之前的强连通分量缩点考虑

不行的方案 我们考虑用组合数去计算

然后 令 \(g_i\) 为有 \(i\) 个点的竞赛图 最大强连通分量的大小 小于 \(k\) 的方案数 那么就有

\[\displaystyle g_i = \sum_{j=1} ^{\min(i, k - 1)} f_j \times g_{i-j} \binom i j \]

这个你考虑将之前的强连通进行缩点 然后变成链 考虑前后连边的方案数

然后答案就是

\[\mathrm{ans} =p_n - g_n \]

这就做完了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 加油!!!!

posted @ 2018-03-28 13:09  zjp_shadow  阅读(764)  评论(4编辑  收藏  举报