【省选模拟】1 月

1.11

T1 的暴力 DP 写了很久,心里也觉得不太稳,但过了大样例就没管,结果爆 0 了。。。还是不能有侥幸心理
T2 想到了正解结论但写了个暴力维护没过样例,以为假了,而且时间也不太够
T3 想到了正解结论的弱化版,还是缺少观察

首先是时间分配不太合理,在感知到 T2 可做的情况下还花了大量时间做 T1,不太划算
其次是有些浮躁,能想出一些东西但不够深入

「没啥了,没啥,就挺自卑的,挺难受的,感觉啥也不是——pyt」

序列(ARC100F)

序列 \(A\) 出现多次算多次,那么可以枚举出现位置算有多少个彩色序列,直接算很难,考虑算补集(有多少个非彩色序列包含 \(A\))。

直接 DP 需要状压(判断 \(A\) 是否出现),减小状态的关键是观察 \(A\) 在不同情况下的性质,分类讨论采用不同 DP 方式

具体做法官方题解很详细了

有向图(CF1361E)

「至少 \(20\%\) 结点有趣」显然是用来随机化找到一个有趣点的

建 dfs 树,如果树中存在前向边/横叉边那么根一定不合法,否则树上都是返祖边,考虑其他点是否合法

结论:一点合法的充要条件是其子树中的返祖边仅有一条指向子树外(该点祖先),且该点祖先合法
正确性显然

实现方法貌似有很多

图形(CF1290F)

设每个向量选了 \(k_i\) 次,那么合法的 \(\{k_{1},\cdots,k_{n}\}\) 一定唯一对应一个凸包,因此本质是数序列。同时可以发现,凸包横坐标极差为 \(\sum_{x_{i}>0}k_{i}x_{i}\)

朴素做法是枚举每个 \(k_i\) 或状压当前 \(\sum k_{i}x_{i}\)
类似「NOIP2021 T2」,考虑按位 DP 来凑出 \(k\)。枚举 \(k_i\) 在当前位的取值,结合上一位的进位可以得到 \(\sum_{x_{i}>0}k_{i}x_{i}\),合法的条件是 \(\sum_{x_{i}>0}k_{i}x_{i},\sum_{x_{i}<0}k_{i}x_{i}\) 当前位相同。
为了保证 \(\sum_{x_{i}>0}k_{i}x_{i}\le m\),还需要记当前及以后位是否 \(\le m\)

这个 trick 大概对于 \(\sum^{n}k_{i}x_{i}=m\)\(n,x\) 很小而 \(m\) 很大的题都适用

1.13

没啥大问题,唯一的遗憾就是最后 30min 没有玩 T1 的 \(k\le3\) 而是去打 T3 的表

守序划分问题

结论:一个划分是守序的,等价于 \(\forall k\in(1,n],\exists i,\min A_{i}<k\le\max A_{i}\)
必要性:若不满足,则可将 \(A\) 根据 \(\max\)\(k\) 的大小划分为两个集合 \(S,T\),则有 \(\forall i\in S,j\in T,\max A_{i}<k\le\min A_{j}\),一定不能相邻,无法成环
充分性:考虑归纳构造。先将 \(A\)\(\min\) 排序,设前 \(i\)\(A\) 已排列成环,根据结论一定存在 \(j\le i,\min A_{j}<\min A_{i+1}<\max A_{j}\),又有 \(\min A_{j-1}<\min A_{i+1}\),可以将 \(A_{i+1}\) 放在 \(A_j\) 之前

发现我们只关心每个 \(A\)\(\min,\max\),且不关心其具体大小,可以以此 DP。从小到大枚举每个数 \(i\),考虑其放到那个集合中。

\(f[i,j,k]\) 为前 \(i\) 个数,分成 \(j\) 个集合,其中 \(k\) 个的 \(\max\ge i\),转移有四种:

  • \(i\) 放到原来集合中:\(f[i+1,j,k]+=k\times f[i,j,k]\)
  • \(i\) 放到原来集合中并作为最大值:\(f[i+1,j,k-1]+=k\times f[i,j,k]\)
  • \(i\) 新开一个集合(作为最小值):\(f[i+1,j+1,k+1]+=f[i,j,k]\)
  • \(i\) 新开一个集合并作为最大值(即该集合中只有一个元素 \(i\)):\(f[i+1,j+1,k]+=f[i,j,k]\)

欧拉函数

做法很多:分块,莫队,线段树+bitset。标算是 \(\text{poly}\log\)

显然难点在于单点修改、区间乘积的 \(\varphi\),即 \(\prod(1-\frac{1}{p})\)。(每个质因子只贡献一次,所以本质是数颜色)

修改本质是删除和插入,而删除可以看作插入的逆元,分别考虑每个质数的插入。使用 set 维护集合 \(\{i|p\mid a_{i}\}\)(即质因子包含 \(p\) 的位置集合)

这类问题的经典想法是容斥。记 \(w=1-\frac{1}{p}\),在 \(i\) 位置插入时,求出其前驱 \(pre\),后继 \(suf\),然后在 \((pre,suf),(i,i)\) 上乘 \(w\)\((pre,i),(i,suf)\) 上乘 \(\frac{1}{w}\),这样查询 \([l,r]\) 时只需要查正方形 \((l,l)\sim(r,r)\) 的权值积,这是一个朴素的三维偏序

时间复杂度为 \(O(n\log^{2})\)。但 \(40000\) 内的数最多有 \(6\) 个不同质因子,每次修改需要在平面上加入 \(6\) 个点,因此常数很大

点整的上圆

1.15

超脑星球

\(mn(i)=\min(a_{i-1},a_{i})\)\(mx\) 同理

先考虑 60pts 的费用流做法:先统计所有的 \(|a_{i}-a_{i-1}|\),将 \(b_i\) 插到 \(a_i\) 前的贡献为:\(\max(2b_{i}-2mn(i),2mx(i)-2b_{i},0)\),可以将 \(a,b\) 的值分开计算。建两个点代表 \(\max\{2b\},\min\{-2b\}\),这样只需要将 \(a,b\) 分别与这两点连边,边数降到 \(O(n)\) 级别

进一步发现这张图性质很好,可以模拟费用流

另一种做法是反悔贪心。将每对 \(a_{i-1},a_{i}\) 看作线段,\(b_{i}\) 看作点,那么就是要给线段匹配点,若点在线段内则无贡献,否则贡献为点到线段两端点距离 \(\min\) 的两倍。
朴素贪心是将点排序(显然一定是最值先匹配,以下以匹配 \(\min\{b\}\) 为例),用堆维护线段,每次取左端点最大的未匹配线段,这样很容易 hack,所以考虑加上反悔。

结论:若要反悔,一定是将一条原来与最大值匹配的线段改为匹配当前最小值,并给被夺取匹配的最大值另找一条线段
证明:显然具体的匹配不重要,只需要考虑线段是与最大值还是最小值匹配。因此反悔时一定是将原来匹配最大值的改为匹配最小值,且被夺取匹配的最大值不会继续反悔,否则在它加入时就已经反悔了
另一种考虑角度是观察网络流的图,只能从另一个代表最值的点退 \(1\) 单位流量

整理以下,对于最小值而言,决策有:

  • 匹配:\(2mn(j)-2b_{i}\),其中 \(j\) 未匹配
  • 反悔:\(-(2b_{i'}-2mx(j'))+2mn(j')-2b_{i}+2b_{i'}-2mx(j)=2a_{j'-1}+2a_{j'}-2mx(j)-2b_{i}\),其中 \(i',j'\) 时原来的一组匹配

开四个堆保存最值,数组记录匹配情况即可

暗星人

1.17

暂咕

1.18

感觉这两天签到完了就没啥干的,想不动也写不动。以后如果发现签到题就先写完拍上,然后扔掉去做原计划。还有就是学艺不精,看到点仙人掌没想到边双树

点点的计算

考场上打表出了 \(ans(n,k)=\text{lcm}(n,ans(n-1,k-1))\),但没意识到答案就是 \(\text{lcm}(1\cdots n)\)。。。 证明

那么只需要维护后缀的 \(\text{lcm}\),可以使用主席树+栈来维护质因子

点点的最大流

有树剖+线段树和 LCT 两种做法,不难想,但确实难调。考场上遇到这种题一定要慎重抉择是否写

注意封装,可以借鉴一下 ycx:“我都不知道每个 namespace 里写了点啥,能跑就行”

1.19

写完一题就交一下,别最后发现什么问题导致手忙脚乱的

中心城镇问题

这题的 \(k\le2\)\(w_{i}=1\) 都是原题,做的时候也没想如何扩展,于是 gg 了

k++,这样距离为 \(k\) 的点对可以同时选

正解考虑 DP,设 \(f[u,d]\) 为子树 \(u\) 中所选的点距 \(u\) 至少 \(d\) 的最大价值,转移时枚举距 \(u\)\(d\) 的点出自那个子树:

这是长剖优化的经典形式,写的时候注意定义中蕴含了后缀 \(\max\),需要仔细维护

DP 部分
LL dp(int u,int d) { return 0<=d&&d<h[u] ? f[u][d] : 0; }
void dfs2(int u) {
	f[u][0] = a[u];
	if( son[u] ) dfs2(son[u]), f[u][0] = max(f[u][0]+dp(son[u],k-1), f[son[u]][0]);
	for(int v : to[u]) if( v != fa[u] && v != son[u] ) {
		dfs2(v);
		memcpy(g,f[u],sizeof(LL)*(h[v]+1));
		for(int i = 0; i <= k && i <= h[v]; ++i) {
			if( i*2 >= k ) g[i] += f[v][i-1];
			else g[i] = max(dp(u,i)+dp(v,k-i-1), dp(u,k-i)+dp(v,i-1));
			ckmax(g[i],dp(v,i-1));
		}
		rFor(i,h[v],0) f[u][i] = max(g[i], dp(u,i+1));
	}
}

心理阴影

外校的老哥确实『讲的好』

最初的想法时枚举点对算贡献,但这样需要考虑两点到其 LCA 路径上所有点,复杂度无法接受。不过据此可以得到正解的想法:在 LCA 处统计贡献

\(f[u,v]\) 为子树 \(u,v\) 中点归并成的拓扑序中逆序对数的期望,\(g[u,v]\) 为点 \(u\) 比子树 \(v\) 中点大的个数,有

\[ans=\sum_{(w,u),(w,v)\in E}f(u,v) \\ f(u,v)=\frac{siz[u]}{siz[u]+siz[v]}(g[u,v]+\sum_{x\in u}f(x,v))\frac{siz[v]}{siz[u]+siz[v]}(g[v,u]+\sum_{x\in v}f(x,u)) \]

统计答案为枚举 LCA(\(w\)),则其两子树 \(u,v\) 中的点都是 \(w\),一起计算
\(f\) 的转移为先枚举 \(u,v\) 哪个在前,然后合并其子树(子树合并顺序无关,因此不需要考虑相互的影响)

记得预处理逆元

1.21

考了好几次拉格朗日插值了,却都没能看出来,当初学习的时候大概只是记住了公式,但对其使用情况并不熟悉,需要引起注意

高中数列题

\(p_{i}=\lfloor\frac{i+b}{c}\rfloor,q_{i}=ic-b\)\(q_i\) 即为满足 \(p_{x}=i\) 的最小 \(x\)

\[a_{n}=a+\sum_{i=1}^{n}f(i)a_{p_{i}} \]

考虑算后面的 \(\sum\)

\[\sum_{i=1}^{n}f(i)\sum_{j=1}^{p_{i}}f(j)a_{p_{j}} \\ =\sum_{j=1}^{p_{n}}f(j)a_{p_{j}}\sum_{i=q_{j}}^{n}f(i) \\ =\sum_{j=1}^{p_{n}}f(j)(sf(n)-sf(q_{j}-1))a_{p_{j}} \]

如果设 \(g(i)=f(i)(sf(n)-sf(q_{i}-1))\),那么就是一个递归的形式

每次 \(g\) 的次数会加 \(m+1\),递归次数为 \(\log\),使用 \(O(n)\) 拉格朗日插值复杂度为 \(O(m^{2}\log^{3}n)\)
实现上可以递归,每次传递 \(g\) 的点值即可

使用多项式科技可以做到 \(O(m\log^{2}n\log^{2}(m\log n))\)

城市绿化

显然通过 \(n-1\) 次询问可以得到每个深度上有哪些点,然后考虑逐层计算(为当前深度的点在上一层找父亲)

从上一层的点中随机一个点作为基准,询问这两层的点到基准的距离,不难发现父亲与儿子的距离差一定为 \(1\),可以据此分治(建议画图,本质是从基准开始跳父亲,分离出其兄弟子树中的点并递归该子树)

询问复杂度为 \(T(2^{n})=\sum_{i=1}^{n-1}T(2^{i})+2^{n}\),据 chy 证明为 \(O(n\log n)\),实际表现约为 \(10n\)

code
const int N = 1e5+5;
int dep[N],dis[N],fa[N][17];
VI ver[N];

void init(int u,int v) {
	fa[u][0] = v;
	For(i,1,16) fa[u][i] = fa[fa[u][i-1]][i-1];
}
int lca(int u,int v) {
	if( dep[u] < dep[v] ) swap(u,v);
	rFor(i,16,0) if( dep[fa[u][i]] >= dep[v] ) u = fa[u][i];
	if( u == v ) return u;
	rFor(i,16,0) if( fa[u][i] != fa[v][i] ) u = fa[u][i], v = fa[v][i];
	return fa[u][0];
}
int dist(int x,int y) { return dep[x]+dep[y]-2*dep[lca(x,y)]; }

int visit(int, int);
int V(int x,int y) { return x==y ? 0 : fa[x][0]&&fa[y][0] ? dist(x,y) : visit(x,y); }

void mysort(int x,VI::iterator l,VI::iterator r) {
	for(auto i = l; i != r; ++i) dis[*i] = V(x,*i);
	sort(l,r,[](const int &x,const int &y){return dis[x]<dis[y];});
}
void solve(int d,int l,int r,int ll,int rr) {
	if( ll == rr ) {
		For(i,l,r) init(ver[d][i],ver[d-1][ll]);
		return;
	}
	if( l == r )
		For(i,ll,rr) if( V(ver[d][l],ver[d-1][i]) == 1 )
			return init(ver[d][l],ver[d-1][i]);
	mysort(ver[d-1][ll],ver[d-1].begin()+ll,ver[d-1].begin()+rr+1),
	mysort(ver[d-1][ll],ver[d].begin()+l,ver[d].begin()+r+1);
	int p = l, pp = ll+1;
	for(; p <= r && dis[ver[d][p]] == 1; ++p) init(ver[d][p],ver[d-1][ll]);
	while( p <= r ) {
		while( dis[ver[d-1][pp]]+1 != dis[ver[d][p]] ) ++pp;
		int i = p, ii = pp;
		while( ii < rr && dis[ver[d-1][ii+1]] == dis[ver[d-1][pp]] ) ++ii;
		while( i < r && dis[ver[d][i+1]] == dis[ver[d][p]] ) ++i;
		solve(d,p,i,pp,ii), p = i+1, pp = ii+1;
	}
}

void findtree(int n,int m,int* p) {
	ver[0].pb(1); For(i,2,n) dep[i] = V(1,i), ver[dep[i]].pb(i);
	for(int d = 1; sz(ver[d]); ++d) solve(d,0,sz(ver[d])-1,0,sz(ver[d-1])-1);
	For(i,2,n) p[i] = fa[i][0];
}

1.23

这套题区分度不错,部分分对正解也有一定启发性,但考场上 T2 想假做法+ T3 看假题后心态完全爆炸,没有推出任何性质。大概是太想切题了,遇到这种拼部分分的总是在确认自己都不会做之后暴力 5min,罚坐 2h,确实得调整心态

合成小丹

从位运算的角度考虑,\(\lfloor\frac{x|y}{2}\rfloor=\lfloor\frac{x}{2}\rfloor|\lfloor\frac{y}{2}\rfloor\),每个数每次右移视作一次合并。设每个数合并了 \(d_i\) 次,那么当且仅当 \(\sum\frac{1}{2^{d_{i}}}\ge1\) 时合法(\(>1\) 可以通过一些删除操作使其 \(=1\)

贪心地考虑答案的每一位,如果要填 \(0\),那么每个数的这一位都是 \(0\)。记每个数在之前位右移了 \(x_i\) 位,不难 \(O(w)\) 得到还需要移 \(y_i\) 位,那么通过 \(\displaystyle \sum\frac{1}{2^{x_{i}+y_{i}}}\)\(1\) 的大小不难判断是否合法,时间复杂度 \(O(nw^{2})\),可以通过

考虑用位运算技巧来优化掉检验的 \(w\)。每个 \(a_i\) 维护一个 \(b_i\)\(b_i\) 的第 \(j\) 位表示 \(a_i\) 右移 \(j\) 位与之前的答案是否冲突(答案的某一位是 \(0\),但 \(a_i\) 右移 \(j\) 位是 \(1\)),不难得到如果答案当前位 \(t\)\(0\),那么 \(b_i\) 应该或上 \(a_i\) 右移 \(t\),最少右移次数即为 \(b_i\) 的最低 \(0\)

路过中丹

通过手模样例 \(2\) 可以得到若区间内存在奇回文串,那么一定可以构造出合法方案。考虑对偶回文串进行类似的构造,发现并不可行,因此若要合法必须要若干个偶回文串覆盖(画一下图易证若折返形成偶回文串,该区间一定可以拆成两个小偶回文串覆盖)

然后有两种充要条件:

  1. \(P_i\)\(i\) 所在回文串的最大左端点:\(\forall i\in[l,r],l\le P_{i}\)
  2. \(L_i\) 为以 \(i\) 为右端点的回文串的最大左端点,\(R_i\) 同理:\(\forall i\in[l,r],l\le L_{i}\lor R_{i}\le r\)

我写的是第二种。求 \(L_{i},R_{i}\) 可以用 manacher+set 维护回文中心,扫描线求解
具体做法是在 \(i\) 处覆盖 \([L_{i}+1,i]\)\(R_{i}\) 处删除,用 \(r\)\(l\) 点被覆盖次数减 \(l-1\) 处的,若不为 \(0\) 则不合法(意味着 \([l,r]\) 中出现了一个 \(i\),其 \(L_{i}<l\)\(r\) 处未被删除,即 \(R_{i}>r\)

膜拜大丹

任意一个环中一定存在两点可以相互到达,因此最优解中一定都是二元环,转化为匹配问题。

两点 \(i,j\) 能匹配的条件是 \(a_{i}\ge j,b_{j}\ge i\)。倒序处理 \(i\),把 \(b_{j}\ge i\)\(j\) 加入 set 里,选能匹配的 \(j\) 中最大的即可

1.25

T1 一看就是 DP,直接跳了,然后亏了一万分
T2 原题+多点求值板子,被卡科技了/kk。时限 2s 本机 1.2s T 了什么鬼。暴力 30pts 点分治+NTT 40pts 不知道出题人什么心态

体育测试

部分分很多,可以看官方题解。正解对我而言还是很新颖的

\(f[i,j]\) 为前 \(i\) 个数,填了 \(j\)\(a<0\) ,转移时只考虑在当前位置填 \(a<0\) 的数,\(a>0\) 的数结束时再分配位置

\[f[i,j]=(f[i-1,j]+f[i-1,j-1]\times((\sum[-a_{k}=i])-j+1))\times A_{i-j-\sum[0<a_{k}<i]}^{\sum[a_{k}=i]} \]

贸易

点分治+NTT+多点求值模板

链的情况需要求值的多项式是等差乘等比,可以直接计算

密码

部分分

  • \(nk\le m\)encode\(a\)\(b\) 上连续写多次(跳过给定的位),decode 根据每位上 \(0,1\) 的个数判断答案的这位,怕被卡可以将原文循环移位
  • 给定的都是 \(0\)\(2(n+k)\le m\):每 \(2\) 位分成一组,第一位是 \(1\) 代表第 \(2\) 位属于答案,把给定的位写成 \(00\) 形式即可

正解(如何想到?)

用相同种子随机 \(m\)\(n\) 位二进制数,令字符串 \(b\) 的第 \(i\) 位为 \(1\) 代表需要异或第 \(i\) 个数,encode 需要用自由的 \(m-k\) 个串异或出原文和给定 \(1\) 代表的数的异或值,decode 只需要异或起来即可

发现 encode 的工作可以用线性基完成,由于 \(m-k\ge n+50\),那么满秩的概率很高(不满秩相当于要使 \(50+1\) 个串某一位上都是 \(0\))。本题无法改变原数,那么再记录一下每个线性基中的数由那些数异或得到即可,使用 bitset 复杂度 \(O(\frac{n^{3}}{\omega})\)

线性基
typedef bitset<N> BS;
struct Basis {
	BS b[N],fr[N];
	void insert(int k,BS x) {
		BS y; y[k] = 1;
		for(int i = x._Find_first(); i < sz(x); i = x._Find_next(i)) {
			if( !b[i][i] ) return b[i] = x, fr[i] = y, void();
			x ^= b[i], y ^= fr[i];
		}
	}
	BS query(BS x) {
		BS y;
		for(int i = x._Find_first(); i < sz(x); i = x._Find_next(i))
			x ^= b[i], y ^= fr[i];
		return y;
	}
} ;

总结

整月都在垫底,偶尔遇到顺手的题才能上去。对部分分的利用很差,写不出高分暴力和无法借助特殊性质思考是关键,导致有效得分时间很少,年后需要调整。挂分集中在数学题和随手写的 subtask,长久以来确实过于依赖对拍来保证正确性,也许需要降低码速来减少错误和 debug 时间

posted @ 2022-01-11 18:31  401rk8  阅读(75)  评论(0编辑  收藏  举报