Typesetting math: 100%

省选考试总结(2)

3-27 (出题人 h10 )

得分情况

20+50+50=12020+50+50=120

题目

T1 : 组合数 , 斯特林数

给定 mm 元不等式组 1in,xit1in,xit mi=1xiSmi=1xiS

给定 S,t,m,nS,t,m,n 求不等式正整数解的个数对 109+7109+7 取模后的结果.

(ntS1018,nm109,t109,mn1000)(ntS1018,nm109,t109,mn1000)

这道题正解很难 , 还没看懂(这是道集训队作业题...)

还是只讲一下 3030 部分分的容斥吧...

我们有个很显然的结论 (打表规律)

mm 个正整数 , 总和不超过 SS 的方案数(不等式解)为 (Sm)(Sm) .

这个考试打表容易得到 , 还是讲一下为啥吧...

考虑隔板法 , 就是把 m1m1 块隔板放进去.

就有 S1i=0(im1)=(Sm)S1i=0(im1)=(Sm)

前面那个式子的组合意义就是 , 枚举这些数的和为 i+1i+1 然后分成 mm 个数.

就是在 ii 个间隔中插入 m1m1 个隔板 所以从 0S10S1

然后这个式子推到后面的式子就可以用杨辉三角去理解...qwq

然后原题解是这样说的容斥

考虑容斥原理。
注意到如果没有 in,xitin,xit 这个限制, 那我们就只需要用一个组合数就能直接算出
答案。
所以我们就可以把这个限制条件给容斥掉。
我们枚举至少有 ii 个数的权值比 tt 要大, 那么就强制把这 ii 个数先取 tt, 然后就去掉了
这个限制条件, 就可以直接用一个组合数来算贡献了

就是我们把恰好 00 个 变成至少 kk 个 然后去容斥一下....

最后的答案就很简单了.

ans=ni=0(Stim)(1)i(ni)ans=ni=0(Stim)(1)i(ni)

T2 : 三维偏序 , 分类讨论

给你三个 11nn 的排列 ai,bi,ciai,bi,ci
称三元组 (x,y,z)(x,y,z) 是合法的,当且仅当存在一个下标集合 S[n]S[n] 满足

(x,y,z)=(maxiSai,maxiSbi,maxiSci)(x,y,z)=(maxiSai,maxiSbi,maxiSci)

询问合法三元组的数量

(n105)(n105)

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

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

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

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

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

然后用总的减去不可行的

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

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

前者记作 XX 后者记作 AA

然后前者分三种情况讨论 (a,c)(a,c) (b,c)(b,c) (a,c)(a,c) 然后求一个二维偏序就行了.

后者用之前三维偏序的答案 , 再用组合数算下贡献就行了.

但这会算重复 所以答案就是 X3×AX3×A

T3 : 启发式合并 , 线段树合并

给你一颗有 nn 个点的树,其中 11 号点为根节点,每个点都有一个权值 valivali
你可以从树中选择一些点,注意如果 iijj 都被选中且 jjii 的子树内,那么必须满足 vali>valjvali>valj
请你求出最多能同时选出多少个点

(n105,0vali109)(n105,0vali109)

BZOJ4919 这题有两种解法.

第一种是容易理解 , 代码易写 , 但适用面狭窄的 setset 启发式合并.

我们用这个 multisetmultiset 暴力维护 LISLIS

每次合并的时候 把小的 multisetmultiset 合并到大的 mutilsetmutilset 上 每次就是 Θ(minsizelog)Θ(minsizelog)

然后查找比当前 val[u]val[u] 大的第一个节点 , 然后拆掉 , 把自己加进去 (就是模拟那个 dpdp 过程)

最后就直接输出根节点的 sizesize 就行了 然后时间复杂度就是Θ(nlog2n)Θ(nlog2n).

但常数不是很大 跑的飞起

第二种解法是用 线段树 维护一个 差分序列

我们先考虑一个比较水的 dpdp 我们令 dp[u][k]dp[u][k] 表示 uu 子树中最大值为 kk 的能选的最多点数.

我们首先将权值离散化掉...

转移很显然了. dp[u][k]=max(vG[u]dp[v][k],((vG[u]dp[v][val[u]1])+1)×[kval[u]])dp[u][k]=max(vG[u]dp[v][k],((vG[u]dp[v][val[u]1])+1)×[kval[u]])

如果强行维护这个 dpdp 数组是可行的 , 但是很麻烦 , 代码很难写(据一位大佬透露是用左子树维护右子树贡献)

但这个转移我们发现是 对于 kk 来说 dpdp 值是单调的

然后我们尝试用差分表示出这个序列 例如

1,1,2,2,2,4,5,71,1,2,2,2,4,5,7

那么它的差分序列就是

1,0,1,0,0,2,1,21,0,1,0,0,2,1,2

那么我们发现 这样的话 每个点的答案就是它差分序列的前缀和.

然后如何维护那个 +1+1 和取 maxmax

我们观察在差分序列上的变化

仍然是刚才的序列 我们假设当前权值所在的位置为 44

那么我们会对 2+12+1 然后对后面所有数取 maxmax

然后差分序列就变成了

1,1,2,3,3,4,5,71,1,2,3,3,4,5,7

那么我们再看看它的差分序列 就变成了

1,0,1,1,0,1,2,21,0,1,1,0,1,2,2

我们发现它就是把离这个右边最近有数字的点的拉过来 11 就行了

理解一下的话就是对当前这个数往右走 如果差分为 00 那么我们就绝对可以更新

直至走到一个不能更新的就行了.

这个操作容易用线段树来维护qwq

复杂度 Θ(nlogn)Θ(nlogn) 但常数特别大 根本没有前面那个 log2log2 快!!!

启发式合并

#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=9060+0+30=90

题目

T1 : 组合数学

有一个 m×nm×n 的数表 , 行与列的编号都从 11 开始 . 令 fi,jfi,j 表示表格第 ii 行第 jj 列内的数 ,

那么对于表格的第 i(i>1)i(i>1) 行有

{fi,1=afi1,1fi,j=afi1,j+bfi1,j1 (j>1){fi,1=afi1,1fi,j=afi1,j+bfi1,j1 (j>1)

它给你第 pp 行的数 询问 qqfx,yfx,y 的数值.

(n105,m107)(n105,m107) 部分分有 (p=1,p=m)(p=1,p=m)

我们先考虑 p=1p=1 的情况 这个就是顺推了 类似于杨辉三角的形式 我们只需要看一下它的组合意义就行了.

我们发现这个只有两种走的方式 就是向下和向右下 我们考虑第一行列坐标为 1y1y 的数字的贡献 .

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

这里的答案易算 :

ans=yi=1(x1yi)×byi×a(x1)(yi)×f1,ians=yi=1(x1yi)×byi×a(x1)(yi)×f1,i

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

不难推出一个这样的式子qwq 我们令 i,fi,0=0i,fi,0=0

fi,j=(fi+1,jbfi,j1)×1afi,j=(fi+1,jbfi,j1)×1a

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

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

ans=yi=1((mx1)+(yi)yi)×(b)yi×(1a)(mx)+(yi)×fm,ians=yi=1((mx1)+(yi)yi)×(b)yi×(1a)(mx)+(yi)×fm,i

这个次幂一定要先预处理!!! 不然32位老人机轻松卡T!!! TAT

这题我最后想出来了.... 但没预处理 成功多个 loglog TLETLE 2333

结合上面两种情况 其实所有情况就出来了2333

然后时间复杂度就是 Θ(qn+m)Θ(qn+m)

T2 : 前缀和+链表

有一种编程语言,这种语言的每个程序都是一个由字符 <<>> 和数字
0909 构成的非空序列。程序运行时会有一个指针,一开始指向序列最左端的字
符,并且移动方向为向右。接下来指针会按照如下规则移动:

  • 如果指针当前指向的位置是一个数字,那么输出这个数字,并且将该数字

减一,然后把指针按照当前移动方向移到下一个位置。若原来的数字为 00
则直接将其删掉。

  • 如果指针当前指向的位置是 << 或者 >> ,那么把指针的移动方向对应

地进行修改( << 改成向左, >> 改成向右),然后按照新的移动方向移
动到下一个位置。如果新的位置也是 << 或者 >> ,则删掉之前的 <<
>> 字符。

  • 当指针移动到序列外时程序结束运行。

给出一段长度为 nn 的程序。有 qq 次询问,每次询问给出 l,rl,r ,询问如果把
区间 [l,r][l,r] 当做一段独立的程序运行的话,会把每个数字输出多少次。

(1n,q105,1lrn)(1n,q105,1lrn)

题目有点长 不难发现是个大模拟题

注意到指针的移动是连续的,如果我们在程序开头加入足够多的 >> ,那
么任意一个区间的运行过程一定都是整个程序运行过程的一个子段。因此只要
能求出这个子段的开始和结束时间,再直接用前缀和减一下就能得到答案了。
fifi 表示指针第一次从 i1i1 移到 ii 的时间 , gigi 表示指针第一次从 ii 移到
i1i1 的时间。先用链表模拟一遍整个程序,顺带记录下 fifi , gigi 。那么 [l,r][l,r] 对应
的子段的开始时间就是 flfl , 结束时间就是 min(fr+1,gl)min(fr+1,gl)

题解说的很清楚了 qwq

就是我再解释一下为啥结束时间是那样的

总共有两种离开这段序列的方式 一种是向右直接出去 一种是向右后再摆头向左出去

然后只要离开这段区间 答案就已经确定了 .

最后我们记一下前缀和 就可以直接出解了

我们令 kk 为最大的数字 那么时间复杂度就是 Θ(nk+q)Θ(nk+q)

代码似乎有点恶心 std写的动态指针 不太想写了...

T3 : 对偶图+最小割

给出一个包含 n+1n+1 个结点的有向图,结点的编号为 00nn 。图中有 mm
有向边,第 ii 条有向边起点为 uiui ,终点为 vivi ,且长度为 wiwi 。并且这些边还满
足如下的性质:

  • 对任意一条边,满足 ui<viui<vi
  • 不存在两条边 i,ji,j 使得 ui<uj<vi<vjui<uj<vi<vj

除了结点 00 和结点 nn 以外,其余的每个结点都有颜色。现在需要你找出一
条从结点 00 走到结点 nn 的最短路径。对于任意一种颜色,这条路径要么经过了
这种颜色的所有结点,要么就不经过这种颜色的任意一个结点。如果不存在这
样的路径,请输出 11 ,否则输出最短路径的长度。

cici 代表 ii 号点的颜色

1n,m,ci1000,0uivin,1wi1061n,m,ci1000,0uivin,1wi106

这道题首先很(不)显然是对偶图 然后你对偶图的割对应原图的一个环

然后瞎jb搞 瞎搞 就可以啦

此坑待填qwq

总结

这几天身体有些不舒服(垃圾天气) 每天只能瞎搞了 大模拟也不想写 😦

但分数还是看的过去的qwq 希望省选的时候状态要好一些 ++rp

3-31 (出题人 dy0607 )

得分情况

48+45+28=12148+45+28=121

题目

T1 : 状压dp

一个长为 nn 的序列 AA , 从 11 开始标号 , 一开始全为 00 ,现在小 CC 想对它进行 mm 次操作.

对第i次操作,他会选定恰好一个二元组 (j,k)(j,k) , j[1,n],k[0,c]j[1,n],k[0,c] , 并令Aj=Aj+kAj=Aj+k ,
其中选中二元组 (j,k)(j,k) 的概率为 Pi,j,kPi,j,k .
CC 本来是想问你区间最大值的历史版本和的历史最大值的期望的,但鉴于这
是一道签到题,现在他只想知道 mm 次操作后整个序列最大值的期望, 对 109+7109+7 取模.
(1n40,1m10,1c3)(1n40,1m10,1c3)

数据范围很小 考虑一个 状压dp

我们先计算一下 fi,S,jfi,S,j 表示 AiAiSS 集合操作之后 , 值为 jj 的概率

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

fi,S,j=k=max{AiS}min(j,c)trans=0fi,kS,jtrans×Pk,i,transfi,S,j=k=max{AiS}min(j,c)trans=0fi,kS,jtrans×Pk,i,trans

然后我们可以用这个去做另一个 dpdp 了qwq

dpi,j,Sdpi,j,S 表示前 ii 个元素的最大值为jj , 用掉了S集合的操作的概率 . 转移时枚举当前元
素用掉了哪些操作 , 以及进行这些操作后的值 .

dpi1,j,S1×fi,S2,kdpi,max(j,k),S1+S2  (S1S2=)dpi1,j,S1×fi,S2,kdpi,max(j,k),S1+S2  (S1S2=)

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

复杂度 Θ(n(cm)23m)Θ(n(cm)23m)

代码有点细节

#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 : 矩阵快速幂 + 线段树 + 树链剖分

CC 有一棵 nn 个点的树 , 0号点为根 , 每个点有 LL 个权值,表示为 w[u][i]w[u][i] , u[1,n]u[1,n] , i[1,L]i[1,L] .
现在他想对这棵树进行树链剖分,于是 fateskyfatesky 教给他一种自创的剖分方法.
具体地,一棵树的剖分可以表示为若干条链 S1,S2,...,SkS1,S2,...,Sk 满足 :

  • 每个点属于且仅属于一条链 .
  • 一条链在树上是一个连通块,即对 i,u,vSii,u,vSi ,从 uuvv 的简单路径不经过
    任何不在 SiSi 中的节点 .
  • i,Sii,Si 的长度不超过 LL .
  • 链中所有节点深度不同 .

设一条链按深度 从大到小 排序后为 u1,u2,...,umu1,u2,...,um , fateskyfatesky 定义一条链的权值
mi=1w[u][i]mi=1w[u][i] , 一种剖分的权值为所有链的权值和.现在他想最大化剖分的权值.

他会给出 qq 个修改操作,每个修改操作给出一个点 uu 以及 LL 个值,表示修改之后的 w[u][i]w[u][i]
每个修改操作之后,你需要回答最大的剖分权值.

(1n105,2L4,1q105)(1n105,2L4,1q105)

这题似乎就是冬令营上讲过的动态动态规划了 每次调整一些参数 然后问你全局的 dpdp 答案

对于这道题我们可以有一个套路 矩阵乘法来动态 dpdp 转移

具体是这样实现的 我们把矩乘里面的 乘法 变为 加法 , 加法 变为 取 maxmax 然后依然满足结合律

(考虑 类似 flyodflyod 或者 展开暴力证 反正我也不会证 )

那么我们考虑链 且 L=2L=2 的点 那么有个 dpdp

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]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然后我们就有一个转移矩阵了

(dp(i+1,1)dp(i+1,2))×(w[i][1]w[i][2]w[i][1])=(dp(i,1)dp(i,2))(dp(i+1,1)dp(i+1,2))×(w[i][1]w[i][2]w[i][1])=(dp(i,1)dp(i,2))

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

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

总结

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

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

4-2 (出题人 罗进 )

得分情况

60+20+10=9060+20+10=90

题目

T1 : 线段树

有一个长为 NN 的数列 AA , 有 QQ 个操作:

  • 1 L R X1 L R X 对于 LiRLiR , 把 AiAi 变成 AiXAiX .
  • 1 L R X1 L R X 对于 LiRLiR , 把 AiAi 变成 AiXAiX .
  • 3 L R3 L RAL,AL+1,...,ARAL,AL+1,...,AR 中的最大值.

为按位与 , 为按位或.

N,Q2×105,0A<220N,Q2×105,0A<220

这个题是第三遍出现了.... 原来只知道大概思路 并不知道具体实现 主要是 push_down 的部分

和吉司机一样 如果不用操作就返回 如果可以操作 分两种情况

一种这些位置改变的位一样 直接打标记返回 另一种是 改变的位不一样 暴力向下推

按位与的操作会把一些位变成 00 , 然后如果这段区间的所有数这些位都相等 ,

就可以直接打标记 , 否则就要递归下去访问额外节点 , 按位或同理 .

push_down 的时候 有神奇的分配律 所以先 &| 就行了qwq

然后经过神奇的势能分析 整个复杂度就是 Θ(20NlogN)Θ(20NlogN)

T2 : 莫比乌斯反演

有一个长宽均为 NN 的网格 , 每个格子的长宽均为 11 . 除了最左下角的网
格外 , 其他格子中均有一个半径为 RR 的圆 , 圆心在格子的正中心 . 现在你站
在最左下角的格子的正中心 , 求你能够看到多少个圆 , 视线不能够穿过圆 .

pic

(N109,1R5×105)(N109,1R5×105)

这题目很鬼畜....

直接粘题解算了...

先建一个座标系, 人的位置为 (0,0)(0,0) . 如果到某个圆心 (x,y)(x,y) 的视线
被另一个在 (a,b)(a,b) 的圆挡住, 那么在 (x,y)(x,y) 的那个圆被挡住了一半, 显然
(xa,yb)(xa,yb) 会挡住它的另一半. 所以只有能够看到圆心才能够看到这个圆.

可以考虑枚举圆 (x,y)(x,y), 再枚举圆 (a,b)(a,b) , 看 (a,b)(a,b) 是否挡住了 (x,y)(x,y) . 这

样是 O(N4)O(N4) , 但是发现固定了 x,y,ax,y,a 之后, 只要选 b=xyab=xya 就行了, 这样是 O(N3)O(N3) .
怎么判断是否挡住了, 由点到直线的距离公式可以推出 (aybx)2(x2+y2)R2(aybx)2(x2+y2)R2 就算被挡住.

如果 (x,y)(x,y) 不互质, 那么肯定是会被挡住的, 如果 (x,y)(x,y) 互质, 那么肯定
存在 a,b,a<x,b<y,|aybx|=1a,b,a<x,b<y,|aybx|=1 , 这个时候肯定是最近的, 那么就只要
(x2+y2)<1R2(x2+y2)<1R2 就不会被挡住.
现在就是要求一个有多少个互质的点在一个圆和正方形内.1R<1061R<106 ,
所以可以直接枚举约数 dd , 算出有多少对 x,yx,y 都能被 dd 整除, 然后容斥.

这个题 最重要的就是上面的结论了qwq 就是看到一个圆必能看到它的圆心 反之则反

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

nx=1ny=1[xy][(x2+y2<1R2)]nx=1ny=1[xy][(x2+y2<1R2)]

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

我们令 1R=r1R=r .

min(n,r)d=1μ(d)rdx=1y[x2+y2<rd2]min(n,r)d=1μ(d)rdx=1y[x2+y2<rd2]

那么这个第一位暴力枚举 第二位也可以暴枚 第三位的话 我们先用 sqrt 大概算出 yy 的范围

再放缩一下 保证正确性

代码我是抄 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 : 树上博弈

AliceAliceBobBob 在玩游戏.
有一棵 NN 个节点的树, AliceAliceBobBob 轮流操作, AliceAlice 先手. 一开始树
上所有节点都没有颜色, AliceAlice 每次会选一个没有被染色的节点并把这个节
点染成红色 (不能不选), BobBob 每次会选一个没有被染色的节点并把这个节点
染成蓝色 (不能不选). 当有人操作不了时, 游戏就终止了.
AliceAlice 的最终得分为红色连通块的个数, BobBob 的最终的分为蓝色连通块
的个数. 设 AliceAlice 的得分为 KAKA , BobBob 的得分为 KBKB , AliceAlice 想让 KAKBKAKB
尽可能大, BobBob 则想让 KAKBKAKB 尽可能小, 假设两人都采取最优策略操作, 那么
KAKBKAKB 会是多少.
这里指的连通块为一个点集 SS , 满足集合内点的颜色相同, 且每个点都能只经过 SS 内的点走到 SS 内的其他点, 而且如果将任意 u(uS)u(uS) 加入 SS ,那么上述性质将不能被满足.

又是个结论题qwq 直接挂结论和证明吧 很巧妙

这是一棵树, 红色连通块数量等于红色的点数减去两端都为红色的边的
条数, 蓝色连通块数量同理, 设答案为 (SAEA)(SBEB)(SAEA)(SBEB) .

红色的点数和蓝色的点数是固定的, 所以就是求两边均为红色的边数和
两边均为蓝色的边数之差, 求 EBEAEBEA .

对于一条边, 我们在它的两个端点处加 11, AliceAlice 选了一个点就减去这个点的点权,
BobBob 选了一个点就减去这个点的点权.

不难发现如果一条边的两个端点同色就会贡献 2222 的代价,
否则就没有代价, 那么我们算出来的就是 2(EBEA)2(EBEA).
AliceAliceBobBob 肯定会去选点权最小的点, 只要排序就行了.

总结

今天怎么三道类似结论题的东西啊TAT

以后要大力猜结论了 尤其是博弈题

首先暴力 然后没有什么思路 就开始乱搞吧qwq

4-3 (出题人 罗进)

得分情况

10+0+0=1010+0+0=10

题目

T1 : 点分治

有一棵 NN 个节点的树, 令 d(i,j)d(i,j)iijj 经过的边的条数.
MM 个炸弹, 第 ii 个炸弹在节点 posiposi 上, 威力为 poweripoweri ,
它会对所有节点 jj 造成 max(0,powerid(posi,j))max(0,powerid(posi,j)) 的伤害.
求出每个节点最终受到的伤害.

(1N2105)(1N2105)

考试时候想了两小时差分 心态爆炸...

正解是点分治qwq (原来没写过....)

这种与树上距离有关的题 而且要统计树上所有对数的距离 一般都是用点分治

这题也可以用那个去做 那个取 max0max0 有点麻烦

我们把那个形式在点分树中转化一下 就成了

我们令 didiii 在当前点分树中的深度

resj=(poweridi)djresj=(poweridi)dj

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

我们令 sumksumkpoweridikpoweridik 的贡献前缀和

skskpoweridikpoweridik 的个数和

然后每个点 lisilisi 得到的贡献就是

(sum[maxdep]sum[dep[lis[i]]])(s[maxdep]s[dep[lis[i]]])×dep[lis[i]](sum[maxdep]sum[dep[lis[i]]])(s[maxdep]s[dep[lis[i]]])×dep[lis[i]]

然后每次这样算就行了 maxdepmaxdep+1+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 : 动态规划 组合数学

求有多少 NN 个的竞赛图包含至少一个长度为 KK 的简单环, 输出答案模
109+7109+7 的结果.
竞赛图 : 任意两个点之间都有一条有向边的图.
简单环 : 不经过重复节点的回路.

(3KN5000)(3KN5000)

这个题据说又是原题.... ( dy0607 出过的原题)

有一些结论qwq

竞赛图的中如果一个强连通分量的大小大于 KK , 那么这个强连通分量
内存在长度为 [3,K][3,K] 的简单环, 然后这题就是要至少有一个强连通分量的大
小大于等于 KK.

用这个结论的话 我们就能得到一些 dpdp 方程

pipi 为有 ii 个点的竞赛图的数量 那么显然有

pi=2i×(i1)2pi=2i×(i1)2

fifi 为有 ii 个点的竞赛图 全部强连通的方案数 那么就有

fi=pii1j=1fj×pij(ij)fi=pii1j=1fj×pij(ij)

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

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

然后 令 gigi 为有 ii 个点的竞赛图 最大强连通分量的大小 小于 kk 的方案数 那么就有

gi=min(i,k1)j=1fj×gij(ij)gi=min(i,k1)j=1fj×gij(ij)

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

然后答案就是

ans=pngnans=pngn

这就做完了qwq

T3 : 树形dp

给出一棵 NN 个点的有根树, 这棵树以 11 号节点为根. 现在你需要对于
每个非叶子节点 YY 选择它的一个儿子 XX , 并把连接 X,YX,Y 的边标记为重边,
其它的边为轻边.
对于这棵树的每个叶子节点, 把它到根节点经过的边依次写下来, 一条
轻边的代价为 11 , 一段连续的重边代价为 log2L+1log2L+1 ,
LL 为这段重边的数量,这个叶子的代价等于这些代价之和.
求出在最优情况下, 所有叶子的代价中的最大值最小是多少.

( 数据组数 T10T10 , 2N1052N105 )

这题有人用 7 种剖分 水了 7070 分 orz

正解比较麻烦 就只介绍 O(n2)O(n2) 的吧

每次转移的时候考虑从儿子最大答案的地方进行剖分 但是这样转移的话贡献很难算

那么我们可以用个 dpdp 去计算

可以设一个 f[i][k]f[i][k] 表示 ii 有一条向上长为 kk 的重链时的最优答案

然后转移的时候记一个前后缀的最大值 这样去转移就行了

正解的话就是取值比较少 用那个转移就行了

总结

今天考的太难了... 刚 T1 2h 心态直接爆炸

其实 T3 那个 dpdp 冷静下来可以想出来的

以后碰到难题 不要死磕 先写个暴力先溜 然后考虑后面骗分

记住 千万要冷静!!!!

集训最后一次了 HNOI2018 加油!!!!


__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/8663275.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(793)  评论(4编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示