2022牛客暑期多校训练营训练日志

2022牛客暑期多校训练营1

G - Lexicographical Maximum

签到题。

A - Villages: Landlines

简化题意:给你 n n n 条线段,问你把这些线段铺到数轴上之后,未被覆盖的区域总长是多少?(无需考虑最左端以左和最右端以右)

签到题,依题意模拟即可。

D - Mocha and Railgun

一道简单的平面几何题,推出题目要求的式子即可。

I - Chiitoitsu

一道关于日本麻将的数学期望题。

初始牌堆有 136 136 136 张牌(共 34 34 34 种不同的牌,每种有 4 4 4 张),先发给你 13 13 13 张作为初始手牌(由输入给定),每一轮进行如下 3 3 3 个步骤:

(1)从牌堆中随机抽取一张牌。

(2)检查手中的 14 14 14 张牌是否构成 7 7 7 个对子,如果是,游戏立即结束;否则转入第 3 3 3 步。

(3)从手牌中选择一张牌丢入弃牌堆,即表示这张牌未来再也不会被抽到了。

问你采用最优策略下,期望多少轮后游戏结束。

一件非常显然的事是,如果抽到的是“杂牌”,即与初始手牌均不一样的一张牌,那必然选择丢弃这张牌,因为用它替换手里的牌显然不会更优。

因此我们可以发现,最后的 a n s ans ans 仅与初始拥有的对子数有关,而初始拥有的对子数只可能是 0 − 6 0-6 06,暴力打表即可。

PS:初始对子数为 0 0 0 时,暴力 d f s dfs dfs 需要相当久的时间,而正好题目中的样例就是初始对子数等于 0 0 0 的情况,因此我们直接把样例那个 a n s ans ans 抄过去,再结合 1 1 1 ~ 6 6 6 a n s ans ans,就直接 A C AC AC 了。

J - Serval and Essay

给你一张 n n n 个点 m m m 条边的无重边无自环的有向图,初始时可以选择一个点染黑,其余点均为白点。若某个点所有入边的起点均为黑点,则该点可以被染黑,问你图中最多能有多少个黑点。

朴素做法是枚举每个点 i i i 作为初始黑点,然后遍历一遍求 a n s i ans_i ansi,显然会 T T T

考虑优化。不难发现 a n s ans ans 之间存在包含关系,若 a n s y ans_y ansy 包含 a n s x ans_x ansx,则我们可以将 x x x 看作 y y y 的祖先。因此可以得到一个森林,森林中的最大树即为答案。

最后用启发式合并加速迭代过程即可。

C - Grab the Seat!

二维平面,屏幕是 ( 0 , 1 ) − ( 0 , m ) (0,1)-(0,m) (0,1)(0,m) 的线段。有 n n n m m m 列座位在屏幕前面,是坐标范围 1 ≤ x ≤ n , 1 ≤ y ≤ m 1\leq x\leq n,1\leq y\leq m 1xn,1ym 的整点,其中 k k k 个座位已经有人,求出到屏幕的视线不被任何人挡住的座位数量。

q q q 次询问,每次修改一个人的坐标后求出答案。

整个区域实际上构成一个折线图,这个折线图由 2 k 2k 2k 条线段构成,而这 2 k 2k 2k 条线段又有比较明显的性质,就是延长线都经过 ( 0 , 1 ) (0,1) (0,1) ( 0 , m ) (0,m) (0,m)。因此可以把它们分成两类,对于延长线经过这两个点的线段分别维护一下斜率。

首先对每个 y y y,如果这一列有多个人,那么肯定考虑 x x x 最小的人,因为小的会完全覆盖大的。然后对于 ( 0 , 1 ) (0,1) (0,1) ( 0 , m ) (0,m) (0,m) 分开考虑。

( 0 , 1 ) (0,1) (0,1) 为例,所有线段都是从某个点往右上方走。

如果按 y y y 从小到大加入线段,不难发现斜率大的线段一旦加入,就一直会覆盖斜率小的线段。对于某个 y y y,要找到最小的合法 x x x,那么肯定取的是前面所有线段中斜率最大的线段。所以对 y y y 正着扫一遍,在扫的同时维护一下最大的斜率,即可得出每一列只考虑经过 ( 0 , 1 ) (0,1) (0,1) 的线段的答案。

对于 ( 0 , m ) (0,m) (0,m) 同理,倒着扫一遍维护斜率最小(绝对值最大)的线段即可。

每个 y y y 在两种情况下取 m i n min min 就是答案。总复杂度为 O ( m q ) O(mq) O(mq)

B - Spirit Circle Observation

给你一个长度为 n n n 的数字串 s s s,计算满足下列条件的二元组 ( A , B ) (A,B) (A,B) 个数:

(1) A A A B B B s s s 的子串。

(2) A A A B B B 长度相同。

(3) B = A + 1 B=A+1 B=A+1

可以发现满足条件的子串一定是 A = x p 99...9 ‾ , B = x ( p + 1 ) 00...0 ‾ A=\overline{xp99...9},B=\overline{x(p+1)00...0} A=xp99...9,B=x(p+1)00...0,其中前缀 x x x 是一个相同的数字串(可以为空), p ∈ [ 0 , 8 ] p\in[0,8] p[0,8],且后缀的 99..9 99..9 99..9 00...0 00...0 00...0 长度相同(可以为空)。因此枚举 p p p 的值后,实际只需要考虑对固定长度的 99..9 99..9 99..9 00...0 00...0 00...0,计算所有可能的前缀 x x x 的贡献。

可以用 S A M SAM SAM 求出 f a i l fail fail 树,然后枚举所有可能的 p p p。我们考虑枚举 99..9 99..9 99..9 00...0 00...0 00...0 的长度,并将形如 x p 1 99...9 ‾ ( p 1 ≠ 9 ) \overline{xp_199...9}(p_1\neq9) xp199...9(p1=9) x p 2 000...0 ‾ ( p 2 = p 1 + 1 ) \overline{xp_2000...0}(p_2=p_1+1) xp2000...0(p2=p1+1) p 1 , p 2 p_1,p_2 p1,p2 的位置视为关键点。可以发现对所有长度而言,关键点的总数是 O ( n ) O(n) O(n) 的。

对于一组相同的长度,可以在 f a i l fail fail 树的某个结点统计其底下 p 1 , p 2 p_1,p_2 p1,p2 类关键点的贡献,这两类关键点在该结点能取到的所有前缀 x x x 正是该结点的 S A M SAM SAM 子串等价类,维护该信息是 O ( 1 ) O(1) O(1) 的。

对每种长度,建立虚树跑上述信息即可。

2022牛客暑期多校训练营2

G - Link with Monotonic Subsequence

签到题。

J - Link with Arithmetic Progression

给你一个数列 a a a,将其修改为一个等差数列 b b b,代价为 ∑ i = 1 n ( a i − b i ) 2 \sum\limits_{i=1}^n(a_i-b_i)^2 i=1n(aibi)2,求最小代价。

签到题,本质是一个线性回归,直接套式子即可。

K - Link with Bracket Sequence I

已知括号序列 a a a 是一个长度为 m m m 的合法括号序列 b b b 的子序列,求可能的序列 b b b 的数量。

d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 表示在序列 b b b 的前 i i i 位中,与 a a a L C S LCS LCS j j j,且左括号比右括号多 k k k 个的方案数。转移时枚举下一位填写的是哪种括号即可。

D - Link with Game Glitch

在一个游戏中有 n n n 个物品, m m m 对物品置换关系,每对置换关系形如“你可以用 a a a b b b c c c d d d”。然而不幸的是,由于参数设置出现了问题,玩家可以通过反复置换来获取无限多个物品(例如 1 1 1 x x x 2 2 2 y y y 1 1 1 y y y 2 2 2 x x x)。

作为游戏开发人员,你需要修复这个 bug,而修复的办法非常简单粗暴——将所有的 c c c 改为 w ⋅ c w\cdot c wc 。你的任务是找到 w w w 的最大值(显然 w w w 小于 1 1 1)。

二分答案。check 的方法类似于 S P F A SPFA SPFA 找“负环”,实际上是求最长路,如果一个点被松弛超过 n n n 次,说明图中存在一个乘积大于 1 1 1 的环,即此次的 w w w 是非法的,否则合法。

考虑到乘积可能过大,实现的时候用 l o g log log 将乘法转换为加法即可。

代码如下:

#include<bits/stdc++.h>
const int MAXN=2022;
int read()
{
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9')
	{
		if(c=='-')
			f=-1;
		c=getchar();
	}
	while('0'<=c&&c<='9')
	{
		x=(x<<3)+(x<<1)+(c^'0');
		c=getchar();
	}
	return x*f;
}
struct edge
{
	int to;
	double val;
	edge* next;
	edge(int _to,double _val,edge* _next)
	{
		to=_to;
		val=_val;
		next=_next;
	}
};
std::vector<edge*> head(MAXN);
void add(int x,int y,double z)
{
	head[x]=new edge(y,z,head[x]);
}
double dis[MAXN];
int cnt[MAXN];
bool v[MAXN];
bool check(int n,double w)
{
	w=log(w);
	std::queue<int> q;
	for(int i=1;i<=n;i++)
	{
		dis[i]=cnt[i]=0;
		q.push(i),v[i]=true;
	}
	while(q.size())
	{
		int now=q.front();
		q.pop(),v[now]=false;
		for(edge* i=head[now];i;i=i->next)
			if(dis[i->to]<dis[now]+i->val+w)
			{
				dis[i->to]=dis[now]+i->val+w;
				if(!v[i->to])
				{
					q.push(i->to),v[i->to]=true;
					++cnt[i->to];
					if(cnt[i->to]>n)
						return 0;
				}
			}
	}
	return 1;
}
void solve()
{
	int n=read(),m=read();
	for(int i=1;i<=m;i++)
	{
		int a=read(),b=read(),c=read(),d=read();
		add(b,d,log(1.0*c/a));
	}
	double l=0,r=1;
	while(r-l>1e-6)
		if(check(n,(l+r)/2))
			l=(l+r)/2;
		else
			r=(l+r)/2;
	printf("%.10lf\n",l);
}
int main()
{
	int t=1;
	while(t--)
		solve();
	return 0;
}

E - Falfa with Substring

对于所有的 0 ≤ i ≤ n 0\leq i\leq n 0in,求长度为 n n n 的字符串中恰好出现了 i i i b i t bit bit 子串的字符串数量。

先求至少出现了 k k k b i t bit bit 串的数量 F k F_k Fk,显然有 F k = C n − 2 k k × 2 6 n − 3 k F_k=C_{n-2k}^k\times 26^{n-3k} Fk=Cn2kk×26n3k

套上容斥,得到答案为 G k = ∑ j ≥ k ( − 1 ) j − k × C j k × F j G_k = \sum_{j\ge k}(-1)^{j-k}\times C_j^k\times F_j Gk=jk(1)jk×Cjk×Fj,直接计算显然会 T T T,考虑优化。

首先分离系数得到 k ! × G k = ∑ j ≥ k ( j ! × F j ) ( − 1 ) j − k ( j − k ) ! k!\times G_k=\sum_{j\ge k}(j!\times F_j)\frac{(-1)^{j-k}}{(j-k)!} k!×Gk=jk(j!×Fj)(jk)!(1)jk,然后发现它是卷积的形式,设 P i = i ! × F i , Q i = ( − 1 ) n − i ( n − i ) ! , R n + i = i ! × G i P_i=i!\times F_i,Q_i=\frac{(-1)^{n-i}}{(n-i)!},R_{n+i}=i!\times G_i Pi=i!×Fi,Qi=(ni)!(1)ni,Rn+i=i!×Gi,则有 R = P × Q R=P\times Q R=P×Q,所以我们直接使用 N T T NTT NTT 加速一下式子计算就做完了。

2022牛客暑期多校训练营3

C - Concatenation

签到题。

PS:出题人好像想让选手用线性复杂度做法,但是又由于 judge 不太稳,担心卡常数影响队伍体验,时限开得比较松。最后结果是暴力 sort 也让过了,作为一个签到题。

A - Ancestor

给你两棵带点权的树 A A A B B B,给出 k k k 个关键点的编号,问有多少种方案使得去掉恰好一个关键点使得剩余关键点在树 A A A L C A LCA LCA 的权值大于树 B B B L C A LCA LCA 的权值。

签到题,预处理前缀 L C A LCA LCA 和后缀 L C A LCA LCA,然后枚举去掉的关键结点并使用前后缀 L C A LCA LCA 算出剩余结点的 L C A LCA LCA 比较权值即可。

J - Journey

给定一个城市有若干十字路口,右转无需等红灯,直行、左转和掉头都需要,求起点到终点最少等几次红灯。

把每条路看作点,在十字路口处连边,形成一个边权为 0 / 1 0/1 0/1 的有向图。

一开始写的 d i j k s t r a dijkstra dijkstra 求最短路,结果竟然 T T T 了,然后改成 0 / 1 B F S 0/1BFS 0/1BFS + 手写 d e q u e deque deque 还是 T T T,有点自闭。最后把 m a p map map 改成 u n o r d e r e d _ m a p unordered\_map unordered_map 才过,看来 m a p map map 的常数因子确实相当大。

PS:掌握一些奇技淫巧还是相当有用的,说不定就能多过一道题。

H - Hacker

给你 1 1 1 个长度为 n n n 的小写字符串 A A A k k k 个长度为 m m m 的小写字符串 B i B_i Bi B B B 的每个位置拥有统一的权值,对于每个 B i B_i Bi 求最大和区间满足该区间构成的字符串是 A 的子串(空区间合法)。

实际上是对 B i B_i Bi 的每个位置,求出它作为结束位置在 A A A 中的最长子串长度,然后在该区间求最大子段和,所有位置的最大值即为答案。

对于每个位置的最长子串,可以对 A A A S A M SAM SAM,然后 B i B_i Bi 从左往右在 A A A S A M SAM SAM 上转移,如果当前结点无法转移跳至父亲结点,最后无法转移则长度为 0 0 0,转移成功则为转移前结点的最大长度 + 1 1 1

F - Fief

一道很好玩的图论题。

给你一个无向图,每次询问两点 x x x y y y,求是否存在一个 n n n 的排列,使得第一个元素为 x x x,最后一个元素为 y y y,且排列的任意一个前缀、任意一个后缀都连通。

该题意等价于问你添加一条边 ( x , y ) (x,y) (x,y) 后,图是否是点双连通的。

首先,我们求出这幅图的所有双连通分量,不难发现只有割点会同时存在于多个双连通分量中,而其他点只可能存在于一个双连通分量里。

接下来我们讨论 4 4 4 种特殊情况:

(1)图不连通,直接全部输出 NO 即可。

(2)这副图只有一个双连通分量,即这幅图本身就是点双连通的,直接全部输出 YES 即可。

(3)这幅图存在一个割点,满足其同时存在于两个以上的双连通分量中,那么无论你添加哪条边,图都不可能是点双连通的,直接全部输出 NO 即可。

(4)这幅图存在一个双连通分量,满足其同时包含两个以上割点,那么无论你添加哪条边,图都不可能是点双连通的,直接全部输出 NO 即可。

如果你把一个双连通分量看成一个点,把割点看成一条边,你会惊奇地发现:如果这幅图不属于以上 4 4 4 种情况,那么这幅图一定是一条链。

所以只有当 x x x y y y 分别来自于首尾那俩双连通分量(且不是割点),才能保证添加一条边 ( x , y ) (x,y) (x,y) 后,图是双连通的。

2022牛客暑期多校训练营4

K - NIO’s Sword

在一个游戏中有 n n n 个敌人,你需要依次杀死这 n n n 个敌人,杀死第 i i i 个敌人的充要条件是 A ≡ i ( m o d   n ) A\equiv i(mod\ n) Ai(mod n),其中 A 是你的宝剑的攻击力。

你随时可以升级你的武器,每次升级你需要选择一个不超过 10 10 10 的整数 x x x,然后宝剑的攻击力会从 A A A 变成 10 A + x 10A+x 10A+x

问你最少需要升级多少次才能消灭所有的敌人。

签到题,对于每个敌人,暴力计算杀死他需要升级多少次即可。

N - Particle Arts

n n n 个粒子,每个粒子有个能量 a i a_i ai。两两随机碰撞,每碰撞一次,两粒子的能量分别变为 a & b a\&b a&b a ∣ b a|b ab,求所有粒子能量稳定后的方差(稳定当且仅当任意碰撞无意义)。

签到题,不难发现稳定时 a & b = a a\&b=a a&b=a a & b = b a\&b=b a&b=b,将此时的 a i a_i ai 排序,那么每个二进制位必为前面 0 0 0 后面 1 1 1,否则不满足包含关系。

所以直接统计每个二进制位有几个 0 0 0,有几个 1 1 1 就可以把稳态确定下来了。

D - Jobs (Easy Version)

n n n 个公司,第 i i i 个公司有 m i m_i mi 份工作,每个工作对 I Q IQ IQ E Q EQ EQ A Q AQ AQ 分别有数值要求,必须三个数值都达标才能胜任这份工作。一个人只要能胜任一个公司的任意一份工作就可以去这个公司工作。

q q q 次询问,每次给出一个三元组,代表一个人的 I Q IQ IQ E Q EQ EQ A Q AQ AQ,问这个人最多可以去几个公司工作。

强制在线, n n n 不超过 10 10 10 I Q IQ IQ E Q EQ EQ A Q AQ AQ 均不超过 400 400 400

签到题,考虑到 n n n c c c(三商)很小,所以我们可以直接设 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 表示 I Q IQ IQ i i i E Q EQ EQ j j j A Q AQ AQ k k k 的人能去哪几个公司(状压一下)。然后对于每份工作,暴力维护 d p dp dp 数组即可。

H - Wall Builder II

给你 n n n 1 × 1 1\times 1 1×1 的矩形, n − 1 n-1 n1 1 × 2 1\times 2 1×2 的矩形, n − 2 n-2 n2 1 × 3 1\times 3 1×3 的矩形,……, 1 1 1 1 × n 1\times n 1×n 的矩形,把这些小矩形拼成一个大矩形(拼接时不可旋转小矩形),问你大矩形的最小周长。

签到题,首先可以求出大矩形的面积 S S S,然后枚举长宽并暴力验证即可。验证时可以贪心,把所有小矩形从长到短依次放进去,从下到上一行一行试着放,遇到某一行能放就放。

PS:比赛时根本没暴力验证,感觉直接选取最接近正方形的那个长宽就行,肯定是能构造出来的(手玩了几组数据没发现反例),然后交上去就过了。

A - Task Computing

n n n 个任务中选 m m m 个出来并任意排序,收益是 ∑ i = 1 m w a i ∏ j i − 1 p a j \sum_{i=1}^mw_{a_i}\prod_j^{i-1}p_{a_j} i=1mwaiji1paj,求最大收益。

首先把 n n n 个任务按照 w a x + w a y p a x < w a y + w a x p a y w_{a_x}+w_{a_y}p_{a_x}<w_{a_y}+w_{a_x}p_{a_y} wax+waypax<way+waxpay 重新排序,这样就仅需选择 m m m 个出来,而无需选出来之后再排序了。

接下来显然是个 d p dp dp,由于正着 d p dp dp 时,每次转移会依赖于之前选择的任务的 p p p 之积,而那玩意显然没法跟着 d p dp dp 一起维护,所以我们考虑倒着 d p dp dp

d p [ i ] [ j ] dp[i][j] dp[i][j] 表示从后往前考虑到了第 i i i 个任务,从中选择了 j j j 个任务的最优收益,则有状态转移方程 d p [ i ] [ j ] = m a x ( d p [ i + 1 ] [ j ] , d p [ i + 1 ] [ j − 1 ] ∗ p [ i ] + w [ i ] ) dp[i][j]=max(dp[i+1][j],dp[i+1][j-1]*p[i]+w[i]) dp[i][j]=max(dp[i+1][j],dp[i+1][j1]p[i]+w[i])

可以发现这样就完美避开了前缀积的问题,因为每次的 p p p 就自然而然地乘给后面的所有任务了。

C - Easy Counting Problem

题意十分简单,统计长度为 n n n 且数字 i i i 至少出现 c i c_i ci 次的数字串数量。

由指数生成函数易得 a n s = [ x n n ! ] ∏ k = 0 9 ( e x − ∑ i = 0 c k − 1 x i i ! ) ans=[\frac{x^n}{n!}]\prod_{k=0}^9(e^x-\sum_{i=0}^{c_k-1}\frac{x^i}{i!}) ans=[n!xn]k=09(exi=0ck1i!xi),由于 n n n 很大没法直接卷出来,观察到后面的和式较短,令 y = e x , f k ( x ) = ∑ i = 0 c k − 1 x i i ! y=e^x,f_k(x)=\sum_{i=0}^{c_k-1}\frac{x^i}{i!} y=ex,fk(x)=i=0ck1i!xi,将原式看作一个关于 y y y 的多项式(其系数为关于 x x x 的多项式)。

y y y 暴力卷积,对 x x x N T T NTT NTT 即可求出一个形如 ∑ ( e x ) i x f i ( x ) \sum (e^x)^ixf_i(x) (ex)ixfi(x) 的式子。

对于每个询问扫一遍 y k y^k yk 的系数即可求出答案。

2022牛客暑期多校训练营5

G - KFC Crazy Thursday

给你一个字符串,求以字母 k k k f f f c c c 结尾的回文子串的数量。

签到题,先用 m a n a c h e r manacher manacher 算法求出每个点的回文半径,然后统计一下 k k k f f f c c c 的前缀和,最后枚举每个点作为结尾统计 a n s ans ans 即可。

K - Headphones

n n n 副耳机(共 2 n 2n 2n 只耳机)散乱地放在抽屉里,你的妹妹从中随机取出了 2 k 2k 2k 只耳机,问你要从抽屉中随机取出至少多少只耳机,才能保证匹配出来的正确耳机数,一定比你妹妹匹配出的要多。

签到题,考虑最坏情况,你的妹妹成功匹配出了 k k k 副耳机,所以你必须要匹配出 k + 1 k+1 k+1 副耳机,才能胜过你的妹妹。而根据抽屉原理,你只需要取出 ( n − k ) + ( k + 1 ) = n + 1 (n-k)+(k+1)=n+1 (nk)+(k+1)=n+1 只耳机,就可以保证一定能匹配出 k + 1 k+1 k+1 副耳机。

如果剩余的耳机数不足 n + 1 n+1 n+1,输出 − 1 -1 1 即可。

B - Watches

n n n 块手表,第 i i i 块的价格是 a i a_i ai。但是你生活在一个赋税严重的国家,如果你总共购买了 k k k 块手表,那么第 i i i 块手表的价格就从 a i a_i ai 变成 a i + k × i a_i+k\times i ai+k×i 了。你有 m m m 块钱,问你最多能买多少块表。

签到题,二分答案即可。

H - Cutting Papers

一道简单的平面几何题,推出题目要求的式子即可。

F - A Stack of CDs

模板题,求圆交的周长。

C - Bit Transmission

有一个 01 01 01 字符串,对某些位置询问是否是 1 1 1,共询问 3 n 3n 3n 次。最多只有一次询问会返回错误答案,问你能否通过这些询问结果唯一确定字符串。

如果能,输出那个唯一确定的字符串;如果不能,输出 − 1 -1 1

签到题,依题意模拟即可。

D - Birds in the tree

给你一棵无根树,每个点非黑即白。问你这棵无根树有多少个子树,满足其叶子同色(无根树的叶子即度数为 1 1 1 的点)。

只要是树上问题,哪怕是无根树,也要先转化成有根树来做。

考虑树上 d p dp dp。设 d p [ 0 ] [ x ] dp[0][x] dp[0][x] 表示以 x x x 为根的子树中,根与叶子均为白色的子树(白树)的数量; d p [ 1 ] [ x ] dp[1][x] dp[1][x] 表示以 x x x 为根的子树中,根与叶子均为黑色的子树(黑树)的数量。

根据定义,我们可以很快写出下面的状态转移方程:若 x x x 为白色,则 d p [ 0 ] [ x ] = ∏ ( d p [ 0 ] [ y ] + 1 ) , d p [ 1 ] [ x ] = ∑ d p [ 1 ] [ y ] dp[0][x]=\prod(dp[0][y]+1),dp[1][x] = \sum dp[1][y] dp[0][x]=(dp[0][y]+1),dp[1][x]=dp[1][y];若 x x x 为黑色,则 d p [ 1 ] [ x ] = ∏ ( d p [ 1 ] [ y ] + 1 ) , d p [ 0 ] [ x ] = ∑ d p [ 0 ] [ y ] dp[1][x]=\prod(dp[1][y]+1),dp[0][x] = \sum dp[0][y] dp[1][x]=(dp[1][y]+1),dp[0][x]=dp[0][y],其中 y 是 x 的儿子。

然而这样转移存在一个显著的问题,为了说明这个问题,我们不妨假设 x x x 为白色, y y y 为黑色。

在计算 d p [ 0 ] [ y ] dp[0][y] dp[0][y] 的时候,由于 y y y 为黑色,它不能作为白树的根,所以只能把儿子们的 d p [ 0 ] [ z ] dp[0][z] dp[0][z] 简单地加起来作为 d p [ 0 ] [ y ] dp[0][y] dp[0][y],这一步在当时看来确实没什么问题。但是,在计算 d p [ 0 ] [ x ] dp[0][x] dp[0][x] 的时候,y 就不再需要作为白树的根了,那么这时再用 d p [ 0 ] [ y ] dp[0][y] dp[0][y] 来更新 d p [ 0 ] [ x ] dp[0][x] dp[0][x] 就显得有点不够用了。

事实上,那时我们应该用 ( d p [ 0 ] [ z ] + 1 ) (dp[0][z]+1) (dp[0][z]+1) 之积来更新 d p [ 0 ] [ x ] dp[0][x] dp[0][x],而不是 d p [ 0 ] [ z ] dp[0][z] dp[0][z] 之和。

为此,我们再引入 d p [ 2 ] [ x ] dp[2][x] dp[2][x],表示以 x x x 为根,叶子均为 x x x 异色的子树的数量。显然, d p [ 2 ] [ x ] dp[2][x] dp[2][x] 存在的价值和意义,仅仅是为了服务 x x x 的父亲。

综上所述,我们可以总结出完整的状态转移方程:

A - Don’t Starve

在二维平面上有 n n n 个点,你从原点 ( 0 , 0 ) (0,0) (0,0) 出发,每一轮游戏你可以前往任意一个点,代价是两点之间的距离。你要保证每一轮的代价单调递减,问最多能进行多少轮游戏。

我们可以研究整个游戏的逆过程,则这题变为“任选一个点作为起点,每一轮游戏你可以前往任意一个点,但要保证距离递增,问最多能进行多少轮游戏”。

挺一眼 d p dp dp 的吧,我们可以设 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示考虑到第 i i i 条边,以 j j j 作为起点,最多能进行多少轮游戏,则有 d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ k ] + 1 ) dp[i][j]=max(dp[i-1][j],dp[i-1][k]+1) dp[i][j]=max(dp[i1][j],dp[i1][k]+1),其中第 i i i 条边为 j j j k k k

这样转移相当于把起点从 k k k 延伸到了 j j j,而延伸的前提是第 i i i 条边的长度更长,那么我们把边按长度递增排序即可,值得注意的是长度一样的边要“同步”转移。

最后用滚动数组优化 d p dp dp 即可,代码如下:

#include <bits/stdc++.h>
#define int long long
const int MAXN = 2e3 + 5;
int read()
{
	int x;
	std::cin >> x;
	return x;
}
int x[MAXN], y[MAXN];
struct edge
{
	int a, b;
	int dis;
	edge(int _a = 0, int _b = 0)
	{
		a = _a, b = _b;
		dis = (x[b] - x[a]) * (x[b] - x[a]) + (y[b] - y[a]) * (y[b] - y[a]);
	}
	bool operator<(const edge& x)const
	{
		return dis < x.dis;
	}
}e[MAXN * MAXN];
int dp1[MAXN], dp2[MAXN];
void solve()
{
	int n = read();
	for (int i = 1; i <= n; i++)
		x[i] = read(), y[i] = read();
	int m = 0;
	for (int i = 0; i <= n; i++)
		for (int j = 0; j <= n; j++)
		{
			if (i == j)
				continue;
			e[++m] = edge(i, j);
		}
	std::sort(e + 1, e + m + 1);
	int l = 1, r = 1;
	for (int i = 1; l <= m; i++, l = r + 1)
	{
		while (r < m and e[r + 1].dis == e[l].dis)
			r++;
		for (int j = l; j <= r; j++)//“同步”转移
			if(e[j].b)
				dp2[e[j].a] = std::max(dp2[e[j].a], dp1[e[j].b] + 1);
		for (int j = l; j <= r; j++)
			dp1[e[j].a] = std::max(dp1[e[j].a], dp2[e[j].a]);
	}
	std::cout << dp1[0] << '\n';
}
signed main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	int t = 1;
	while (t--)
		solve();
	return 0;
}

2022牛客暑期多校训练营6

J - Number Game

签到题。

G - Icon Design

签到题,依题意模拟即可。

B - Eezie and Pie

给你一棵有根树,每个点 i i i 会对它的 0 0 0 ~ d i d_i di 级祖先产生 1 1 1 的价值,求最终每个点的价值。

签到题,树上倍增找每个点 i i i d i d_i di 级祖先,然后树上差分即可。

PS:题解给出了一种 O ( n ) O(n) O(n) 的做法,在 d f s dfs dfs 的时候维护 d f s dfs dfs 栈,那么访问到点 i i i 时,它的 d i d_i di 级祖先一定在当前栈顶向下 d i d_i di 个。

A - Array

给你一个长度为 n n n 的序列 ,满足 ∑ i = 1 n 1 a i ≤ 1 2 \sum\limits_{i=1}^n\frac{1}{a_i}\leq\frac{1}{2} i=1nai121

你需要构造一个长度为 m m m 的序列 c 0 , c 1 , . . . , c m − 1 c_0,c_1,...,c_{m-1} c0,c1,...,cm1。根据序列 c c c,我们可以生成一个无限长的序列 b b b,使得 b i = c i % m b_i=c_{i\%m} bi=ci%m。你需要保证序列 b b b 满足:任意连续 a i a_i ai 个数中,必有一个值为 i i i

你可以决定 m m m 的取值,试求序列 c c c

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值