2021.牛客暑假多校 第一场 补题

  1. A题:https://ac.nowcoder.com/acm/contest/11166/A 博弈

大意:有 T 组测试样例,(T<=1e4)。给出两堆石子的数目,每堆最多5e3个石子,每次可以从其中一堆取 k(k>=1) 个,顺便从另一堆带走 k * s(s>=0)。不能操作的判输。

思路:(1)暴力 SG,用记忆化搜索写SG函数提前打表,把表中的数据存下来。这样打表会很费时,没几个小时数据跑不完。也可能是我写的SG函数不好。

(2)结论+递推/打表: 记两堆石子A,B。通过暴力SG,求一部分数据,我们发现,对于A堆石子任意个数 i , B堆石子中最多出现一种情况会后手必胜。 证明如下: 假设有两种情况后手必胜记为 (i,p) (i,q) 。 p!=q。 不妨令 p > q。 这样先手可以将 p 变成 q 。 这时候先手变成了后手,根据前提 (i,q)是后手必胜。也就是说 (i,p) 是先手必胜。矛盾。证毕。接下来我们就可以根据这个结论去递推或着打表。最多O(n)个先手必败态,根据先手必败,加1步就是先手必胜。时间复杂度 O( n 2 n^2 n2 l o g ( n ) log(n) log(n))。代码如下:

#include <bits/stdc++.h>
using namespace std;
const int N=5010;
bool f[N][N];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    f[0][0]=0;
    for(int i=0;i<=5000;i++)
    	for(int j=0;j<=i;j++)
    	{
    		if(!f[i][j])
    		{
    			for(int k=1;k+i<=5000;k++)
    				for(int s=0;j+k*s<=5000;s++)
    					f[i+k][j+k*s]=f[j+k*s][i+k]=1;
    					
    			for(int k=1;k+j<=5000;k++)
    				for(int s=0;i+k*s<=5000;s++)
    					f[i+k*s][j+k]=f[j+k][i+k*s]=1;		
			}
		}
    int _;
    cin>>_;
    while(_--)
    {
        int a,b;
        cin>>a>>b;
        if(f[a][b])cout<<"Alice"<<"\n";
        else cout<<"Bob"<<"\n";
    }
    return 0;
}

总结:并不是所有的ICG都直接用SG。像此题,操作简单,方便枚举的我们可以递推来完成。更加省时。

  1. B题: https://ac.nowcoder.com/acm/contest/11166/B 几何题

问求能不能不穿过等腰梯形,如果不能穿过求出 ? 标记的那段高度。 a,h,r,b。是给出的数据。b!=2 * r。

思路:什么时候能穿过呢?横着看,圆最长的边就是 2 * r 。梯形最短的边是 b。 当 b > 2 * r的时候是可以穿过的。

在这里插入图片描述

接下来看怎么求距离。(高中知识)作如图辅助线;

根据三角形相似:z/y = 腰/x 带入数据 h/r=sqrt( h 2 h^2 h2+ ( ( a − b ) / 2 ) 2 ((a-b)/2)^2 ((ab)/2)2)/x 得 x 。

再根据相似:t / h=(x-b/2) / ((a-b)/2) 。得 t 。

  1. D题签到
  2. E题 非常复杂的bfs。
  3. F题: https://ac.nowcoder.com/acm/contest/11166/F

大意:定义3—friendly 为 如果 一个数,%3为0, 或者子串构成的数%3 为0,则这个数为 3—friendly。给出一段区间 [l,r]。问区间内有多少个 3—friendly的数。(1<=l,r<=1e18)。

思路:首先对于 x>=100 的数,一定是3—friendly的数。按照数位构造模数前缀和,并将前缀和%3。这时某一连续段%3=0等价于前缀和s[i]-s[j]=0,即s[i]=s[j]。根据鸽巢原理,这种情况必定存在。所以对于>=100的数一定是3—friendly的数。对于<100的数直接暴力就行了。

总结:给的数据范围很大,很多题都是具有某种特点,只需要考虑其中的一部分就行了。

  1. G题: https://ac.nowcoder.com/acm/contest/11166/G

大意:给定两个长度为 n 的 序列。交换 k 次,求交换之后的 ∑ i = 1 n ∣ a i − b i ∣ \sum^{n}_{i = 1}{|a_i-b_i|} i=1naibi 的最大值。n<=1e5。

思路:很容易想到是道贪心题,对于贪心题我们可以先试着交换一下,看什么时候可以使得答案更优。首先对于绝对值的理解。对于绝对值一般是要么转化成实际意义即距离,要么想办法去掉绝对值。我们可以先假定 a i a_i ai > b i b_i bi 。对于 a i a_i ai < b i b_i bi 根据绝对值的特性,我们相当于交换了 a i a_i ai, b i b_i bi 。对于 a 1 a_1 a1 a 2 a_2 a2 b 1 b_1 b1 b 2 b_2 b2 (我们可以假定 a 1 a_1 a1< a 2 a_2 a2,对于不满足的我们可以倒过来分析。)计算的结果为 a 1 a_1 a1 − - b 1 b_1 b1 + + + a 2 a _2 a2 − - b 2 b_2 b2 。交换后 | a 2 a_2 a2 − - b 1 b_1 b1 | + + + |$ a_1$ − - b 2 b_2 b2| 当 a[2] > b[1]时,则根据 a[1]>a[2]可以推出a[2]>b[2]。我们可以推出 a[1]>b[2],

交换后结果为 a 2 a_2 a2 − - b 1 b_1 b1 + + + a 1 a _1 a1 − - b 2 b_2 b2 。没有变化。当a[2]<b[1] 交换后的结果为 b 1 b_1 b1 − - a 2 a_2 a2 + + + a 1 a _1 a1 − - b 2 b_2 b2 贡献的结果为 2 * b1 - 2 * a2。也就是 2 * min(a1,b1) - 2 * max(a2,b2)。进行 k 次交换,基于贪心的思想,我们尽可能让每次交换贡献大即2 * min(a1,b1) 尽可能大, 2 * max(a2,b2)尽可能小,对于不足 k 次就达到了最优解的,我们让剩下的操作无意义交换就好了。但是对于 n=2的情况我们没得选,只能老老实实的交换,就算达到了最优解,但是不足k次还要破坏最优解。

总结:对于绝对值问题一般是根据实际意义或者去绝对值。对于贪心问题我们试着去交换一部分,看什么情况下会是更优。

  1. H题: https://ac.nowcoder.com/acm/contest/11166/H

大意:给定一个序列长度为 n 的序列a,求使得序列哈希操作不会产生冲突的最小正整数x,哈希方式:a[i]%x。(1<=n<=5e5 ,0 <=ai<=5e5,且 ai != aj。

思路:首先对于不产生冲突进行转化,即对于任意的ai,aj都有 ai%x!=aj%x ,即 (ai-aj)%x !=0。也就是说 x 需满足不是任意两个数的差值的约数。我们把任意两个数的差值求出来,找到最小的一个数,不是它们任意两个数的差值的约数就行了。把所有的差值求出来之后可以通过nlogn的时间复杂度判断某个数是不是所有差值的约数,每次对于一个x,枚举 x 的倍数,看存不存在这个倍数,时间复杂度 nlogn。接下来就是怎么求出任意两个数的差值,暴力枚举时间复杂度为 o( n 2 n^2 n2)。我们可以用 fft加速这个过程。先来看下面一道题:即 A={a1,a2,…an}.B={b1,b2…bm}。求 A+B。这里的A+B是集合相加,即每次各从集合中任选一个相加。暴力枚举时间复杂度 o( n 2 n^2 n2)。我们可以构造 A(x)= x a 1 x^{a1} xa1 + x a 2 x^{a2} xa2 +…+ x a n x^{an} xan B(x)= x b 1 x^{b1} xb1 + x b 2 x^{b2} xb2 +…+ x b m x^{bm} xbm

这样就将 C=A+B转化成 C(x)=A(x) * B(x)。然后判断对应次幂的系数存不存在就行了。对于多项式相乘利用 fft,可以 nlogn的时间复杂度实现。对于 A-B同理。只需要加个偏移量就好。对于fft的部分在理解的基础上背板子就行了。

  1. I题: https://ac.nowcoder.com/acm/contest/11166/I 期望dp

大意:给定一个 1~n长度为n 排列的序列,乱序。两个人现在玩一场游戏。两人轮流从序列中取数,每次取一个为回合数加1。取数有以下限制,每次取得数都比之前取过的所有数要大。并且对于每个人来说,每次取的数的下边要这个人之前取过的大。每次在能取的数中选择的概率都相同。n<=5000。求总回合数的期望值。

思路:很容易能看出是期望dp。对于期望dp问题,一般是根据前一个状态的期望去更新下一个状态的期望。有时候也会根据最终状态往初始状态更新。对于本体,如果知道了上一个状态回合数的期望值f1, 那么对于下一个回合的期望值f2,则有:

假设对于下一轮有 cnt 的数可以选。 f 2 f_2 f2 = ∑ i = 1 c n t ( ( f 1 + 1 ) ∗ 1 c n t ) \sum_{i=1}^{cnt}{( ({f_1}+1) * \frac{1}{cnt}) } i=1cnt((f1+1)cnt1) = 1 + 1 c n t \frac{1}{cnt} cnt1* ∑ i = 1 c n t f 1 \sum_{i=1}^{cnt}{f_1} i=1cntf1

如果我们定义f[x,y]表示上一回合选x,这一个回合选 y 则对于下一回合 k 状态转移方程 f[y,k] = 1 + 1 c n t \frac{1}{cnt} cnt1* ∑ i = 1 c n t f [ x , y ] \sum_{i=1}^{cnt}{f[x,y]} i=1cntf[x,y]

这样的状态转移方程因为 y 所在的维度不一样,是很难通过递推去实现的,因为我们不能保证要用的状态都是被更新过的,利用记忆化搜索又去不掉连加符号,时间复杂度变成 O( n 3 n^3 n3) 会超时。显然这样进行状态定义是不行的。我们换个状态定义方式。

令f[i,j]表示任意两个回合,选了i,j的数。

显然 当 a[i]>a[j] 时, 对于下一个回合,选下标为 k 的数 即 f[i,j] = 1 + 1 c n t 1 \frac{1}{cnt1} cnt11* ∑ i = 1 c n t 1 f [ i , k ] \sum_{i=1}^{cnt1}{f[i,k]} i=1cnt1f[i,k] k>j ,a[k] > a[i]

​ 当 a[i]<a[j]时 ,对于下一个回合,选下标为 k 的数即 f[i,j] = 1 + 1 c n t 2 \frac{1}{cnt2} cnt21* ∑ i = 1 c n t 2 f [ k , j ] \sum_{i=1}^{cnt2}{f[k,j]} i=1cnt2f[k,j] k>i , a[k] > a[j]

k 比 i , j 大,所以从后往前更新,这样更新确保下标都是合法的,有点疑惑?没关系,具体代码部分还会说。但是每次如果枚举 k 的话,时间复杂度还是 O( n 3 n^3 n3) 。怎么办呢?因为我们是递推更新的,我们可以记录类似于前缀和的东西这样时间复杂度就变成 O( n 2 n^2 n2)了。

仔细观察上面两个状态转移方程我们会发现:我们上面那个式子的 cnt1 和 连加值 都会 在下面那个式子中更新的,下面的cnt2,以及连加值都是在上面的那个式子中更新的。例如:对于下面的那个式子是求所有满足 k>i , a[k]>a[j]的k, 恰好可以通过在 if(a[i]>a[j]) 中更新,具体看代码部分。

另外对于除法取模要求逆元,提前预处理出来。状态转移方程部分代码如下:

for(int i=n;i>=0;i--)
    for(int j=n;j>=0;j--)
    {
		if(i==j&&i&&j)continue;//显然当i==j且不为0的时候直接跳过
        if(a[i]>a[j])
        {
			f=cnt1[i]?(1+inv[cnt1[i]]*s1[i]%M):1; //对于这部分状态转移方程我们要的是 k>j,a[k]>a[i] 的部分,看下面那个if的进入条件,就是对于 a[j] > a[i]的部分, 对于下标来说 k > j.我们的 j 下标是从大到下枚举的,显然也是符合条件的。对于“前缀和部分”,上下两边是对称的,我们根据下面的分析。
            cnt2[j]++,s2[j]=(s2[j]+f)%M; 
        }
        else 
        {
			f=cnt2[i]?(1+inv[cnt2[i]]*s2[i]%M):1;//分析和上面的相似,对于连加值的分析:对于这部分状态更新是要用到的是f[k,j]。即所有满足 k>i,a[k]>a[j].正好对应上面的。
            
            cnt1[i]++,s1[i]=(s1[j]+f)%M;
        }
    }

cout<<(f-1+M)%M<<"\n";  // f-1 可以理解为我们算的时候回合数从1开始,实际上是从 0 开始的

总结:这题难在对代码的优化上,将 O( n 3 n^3 n3)的时间复杂度优化成 O( n 2 n^2 n2) 。重点是要观察出可以利用类似于前缀和的方式去优化。细节非常多。

  1. K题: https://ac.nowcoder.com/acm/contest/11166/K 贪心,乱搞很容易

大意:T组测试样例,每组随机生成一个0n-1的序列,你需要用读入0n-1的序列去和它匹配,匹配函数是sqrt,即f(a,b)= ∑ ∣ai−bi∣.( 0 <= i < n )

使得损失 <=4% 。评测机会算出最小的 fk ,而你算的 (f - fk)/fk<=4%。(10<=n<=1000)

思路:这是一道乱搞题,考察随机应变的能力。因为损失尽可能小,根据f(x)=sqrt(x)函数的性质,数越小的函数值变化幅度越大,越往后函数值反而变化越不明显。也就是说我们出现了ai-bi较大但不一定不优。所以我们不能用简单的sort去匹配,可以从贪心的角度,先把和随机生成的序列相同的数去抵消,然后抵消差值为1的,抵消差值为2的…直到全部匹配。

另一种比较容易实现的思路,误差不超过%1,调整15次,每次暴力枚举整个序列,如果调整后更优就调整。

  1. https://ac.nowcoder.com/acm/contest/11166/J 线段树

大意:一段路上有N个点,每个点有个合法时间段 [ u i u_i ui, v i v_i vi] 。相邻两个点有一段距离,每次问在 u i ui ui

时间从 i 出发后,能否一次经过i+1~j的所有点,使得到达时间满足,每个点的合法区间(如果提前到了可以等待,迟到了则失败)。同时还可以修改一段路的长度,或者修改一个合法时间段。N,Q<=1000000.

思路:区间查询,单点修改,并且是动态查询很容易想到是线段树,此题难就难在维护线段树的什么信息。对于此题,首先需要明确的一点,对于题目的描述,实际上是一个分段函数,如下图:

在这里插入图片描述

对于分段函数是可以合并的的,我们可以从函数合并的角度去维护线段树。对于每个节点,首先需要纪录该区间能否在合法时间段到达每个点,对于区间合并的时候还得需要纪录此段的u和v。另外对于 u,v的更新是否还需要维护额外信息呢?还得维护某段区间的路径的总长度w,具体不太好解释,模拟下样例就会发现w的作用。我们进行合并随着时间的增加,重心是往后移动的,也就是说两段区间进行合并的时候例如 1-3 ,4-6两个区间合并的时候,我们可以用 w 纪录的是 从 4 到 6所用的时间,这段也是不能少的。最关键pushup()函数代码如下:

void pushup(node l,node r,int cost)
{
    node res;
    res.f=l.f&r.f;
    if(l.u+cost>r.v)res.f=0;//主要用来更新相邻的两点
    res.u=max(r.u,l.u+cost+r.w);
    res.v=min(l.v,r.v-cost-l.w);//与上面的对称
    res.w=l.w+r.w+cost;
    return res;
}

总结:转化成函数之后函数是可以合并的。通过分析函数的角度找出我们所需要维护的信息。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值