2022牛客寒假算法基础集训营1

比赛链接

https://ac.nowcoder.com/acm/contest/23106

A.九小时九个人九扇门

题目描述

在打越钢太郎的著名解谜游戏系列《极限脱出》的第一作《九小时九个人九扇门》中,有这样一个有趣的设定:游戏中,99位主人公被困在一座大型的豪华巨轮中,每个人手上都有一个奇怪的手表,手表上有一个数字,9个人的数字分别是19;在巨轮中,还有很多紧闭的数字门,每扇数字门上也有一个19的数字,要想打开数字门逃出生天,主角们必须要满足一个奇怪的条件:

k个人能够打开门上数字为d的一扇数字门,当且仅当这k个人的腕表数字之和的数字根恰好为d

一个数字的数字根是指:将该数字各数位上的数字相加得到一个新的数,直到得到的数字小于10为止,例如,149的数字根为149=>1+4+9=14=>1+4=5,故149的数字根为5。我们约定,小于10的数字,其数字根就为其本身。

例如,如果游戏中的一宫(手表数字为1)、四叶(手表数字为4)、八代(手表数字为8)三人组合在一起,就可以打开编号为4的数字门,这是因为1+4+8=13,而13的数字根为4

现在,你是游戏的主角,淳平,你知道船上包括自己在内的n个人的手表数字,为了分析局势,你想要计算出可以打开19号门的人物组合有多少种,你可以完成这项任务吗?

输入描述:

输入的第一行包含一个整数n(1n105),主人公的数量。

下面一行n个数,第i个数字ai(1ai109)表示第i位主人公的腕表数字。

输出描述:

你需要输出9个数字,第i个数字表示有多少种不同的人物组合,可以打开编号为i的数字门。

答案可能很大,请你将答案对998244353取模后输出。

输入

9 1 2 3 4 5 6 7 8 9

输出

56 56 58 56 56 58 56 56 59

解题思路

01背包变形

数字根结论:一个数的数字根等于这个数对9取模的结果(特别地,取模得
0则数字根为9)

  • 状态表示:f[i][j] 表示从前 i 个数中选若干数使其之和的数字根为 j 的方案数
  • 状态计算:
    • f[i][j]+=1
    • f[i][j]+=f[i1][(ja[i]
      分析:先分为两种情况:选择一个数和选择大于一个数,选择一个数即 f[i][j]+=1,选择大于一个数也可考虑两种情况:是否选择最后一个数,几种情况相加即为总的方案数

代码

// Problem: 九小时九个人九扇门 // Contest: NowCoder // URL: https://ac.nowcoder.com/acm/contest/23106/A // Memory Limit: 524288 MB // Time Limit: 2000 ms // %%%Skyqwq #include <bits/stdc++.h> #define pb push_back #define fi first #define se second #define mp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } const int mod=998244353; int n,a[100005],f[100005][10]; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); f[1][a[1]%9]=1; for(int i=2;i<=n;i++) { f[i][a[i]%9]++; for(int j=0;j<9;j++) f[i][j]=(f[i][j]+f[i-1][(j-a[i]%9+9)%9]+f[i-1][j])%mod; } for(int i=1;i<=8;i++) printf("%d ",f[n][i]); printf("%d",f[n][0]); return 0; }

B.炸鸡块君与FIFA22

题目描述

热爱足球(仅限游戏)的炸鸡块君最近购买了FIFA22,并且沉迷于FIFA22的Rivals排位上分。

在该排位系统中,每局游戏可能有胜利(用W表示)、失败(用L表示)、平局(用D表示)三种结果,胜利将使得排位分加一、失败使排位分减一、平局使排位分不变。特别地,该排位系统有着存档点机制,其可以简化的描述为:若你当前的排位分是3的整倍数(包括0倍),则若下一局游戏失败,你的排位分将不变(而不是减一)。

现在,给定一个游戏结果字符串和若干次询问,你需要回答这些询问。

每次询问格式为(l,r,s),询问若你初始有s分,按从左到右的顺序经历了[l,r]这一子串的游戏结果后,最终分数是多少。

输入描述:

输入第一行输入两个整数n,q(1n,q2×105),表示游戏结果字符串长度与询问次数。

第二行输入一个字符串,表示游戏结果字符串,保证之中只含有W、L、D三种字符。

接下来q行,每行三个数l,r,s(1l,rn,0s109)代表一组询问,询问含义如题面所述。

输出描述:

对于每个询问,输出一个整数,表示该组询问的答案。

输入

10 7 WLDLWWLLLD 2 6 0 2 6 1 2 6 2 2 6 9 1 7 0 7 10 10 10 10 100

输出

2 2 2 11 1 9 100

解题思路

倍增

由题意,初始分数如果对 3 取模一样,则在某一段区间上的分数变化量相同,则只用计算区间上的变化量即可:

  • 状态表示:f[i][j][k] 表示初始分数对 3 取模结果为 i,从 j 变化到 j+2k1 的分数变化量
  • 状态计算:f[i][j][k]=f[i][j][k1]+f[((i+f[i][j][k1])

需要注意初始化时当取模结果为 0 且当前为败局时分数不变即为 0

  • 时间复杂度:O((n+q)×logn)

代码

// Problem: 炸鸡块君与FIFA22 // Contest: NowCoder // URL: https://ac.nowcoder.com/acm/contest/23106/B // Memory Limit: 524288 MB // Time Limit: 4000 ms // %%%Skyqwq #include <bits/stdc++.h> #define pb push_back #define fi first #define se second #define mkp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } const int N=2e5+5; int n,q; char S[N]; //f[i][j][k]表示初始分数对3取模结果为i,从j变化到j+2^k-1的分数变化量 int f[3][N][20]; int main() { scanf("%d%d",&n,&q); scanf("%s",S+1); for(int j=1;j<=n;j++) for(int i=0;i<3;i++) { if(i==0&&S[j]=='L')continue; if(S[j]=='W')f[i][j][0]=1; if(S[j]=='L')f[i][j][0]=-1; } int t=log(n)/log(2); for(int k=1;k<=t;k++) for(int j=1;j<=n;j++) for(int i=0;i<3;i++) { if(j+(1<<(k-1))<=n) f[i][j][k]=f[i][j][k-1]+f[((i+f[i][j][k-1])%3+3)%3][j+(1<<(k-1))][k-1]; } while(q--) { int l,r,res; scanf("%d%d%d",&l,&r,&res); int pos=l; while(pos<=r) { int x=0; while(pos+(1<<x)-1<=r)x++; x--; res+=f[(res%3+3)%3][pos][x]; pos+=(1<<x); } printf("%d\n",res); } return 0; }

C.Baby's first attempt on CPU

题目描述

在硬件小学期课程上,学生们要分组使用Verilog编写流水线CPU,并学会处理各种流水线CPU中常见的相关问题,包括数据相关、结构相关与控制(转移)相关,炸鸡块君也学到了许多。现在,他也要教你解决流水线CPU中数据相关下的先写后读相关问题。

在汇编语言程序中,程序是由多句汇编语句组成的,每句汇编语句会从寄存器中读一些数据(可能不读)和向寄存器中写一些数据(可能不写)。但由于CPU流水线式的架构,一条语句A写入寄存器的数据若想被语句B读到,则语句B和A之间至少要间隔三条语句,否则B将读到该寄存器中的老数据而不是A刚刚写入的数据,这被称为寄存器的先写后读相关问题。

例如,第一条语句写入了十号寄存器一个数据,若想写一个读取十号寄存器中数据的语句,则该语句最快也要等到第五句才可以(因为此时两条语句才恰好间隔三句)。

解决先写后读相关问题往往可以使用插入空操作(空操作也是一种汇编语句,该语句什么都不做,只起到占位的作用)的方法,即填充一些什么都不做的语句到原程序中。例如,现在第一条语句写入了八号寄存器、第二条语句读取了八号寄存器,因此存在对八号寄存器的先写后读相关问题,可以通过在之中插入三句空语句来解决该问题,即原程序在插入后变为:原第一条语句、空语句、空语句、空语句、原第二条语句。

现在,给出一个原有n个语句的汇编程序,并给出程序中语句之间发生先写后读相关的情况,请你求出最少添加多少个空语句可以使得该程序完全不存在任何先写后读相关问题。

输入描述:

输入第一行包括一个整数n(3n100),程序原有语句总数。

接下来有n行,第i描述了第i条程序语句,每行有三个数字。第i行第j个数字ai,j{0,1}表示第i句与第ij句间是否发生了先写后读相关,为1表示有发生先写后读相关(即第ij句写入了某一寄存器,而第i句又要读取同一寄存器),为0表示没有。

输入保证对于ij0ai,jai,j=0成立。

(你可以通过样例进一步理解输入的含义)

输出描述:

输出一个整数,表示为完全消除先写后读相关至少需加入多少条空语句。

输入

4 0 0 0 1 0 0 0 1 0 0 0 0

输出

3

说明

输入表示发生先写后读相关问题的语句有:第二句读了第一句所写的寄存器、第三句读了第一句所写的寄存器。
一种插入三个空语句的最优策略为变成:
原第一句、空语句、空语句、空语句、原第二句、原第三句、原第四句。

解题思路

模拟

不妨打印出前 5 个句子之间的关系:

1:0 -1 -2 2:1 0 -1 3:2 1 0 4:3 2 1 5:4 3 2

对于第一个句子不用考虑,第二个句子只用考虑和第一个句子之间的关系,第三个句子只用考虑和第一个和第二个句子之间的关系,这三种情况特殊处理,对于当前句子来说,只存在三种关系,并且优先选择靠的近的句子中间加空格越优,对于靠得最近的句子,如果有冲突,则加上三个空格,此时与其他句子断然不会有冲突,如果没有冲突的话,考虑靠得次近的句子,如果有冲突的话,考虑在最近的句子中间加上若干个空格,这些空格数由上一个句子与最近的句子之间的空格数决定,对于靠得最远的句子也是如此

  • 时间复杂度:O(n)

代码

// Problem: Baby's first attempt on CPU // Contest: NowCoder // URL: https://ac.nowcoder.com/acm/contest/23106/C // Memory Limit: 524288 MB // Time Limit: 2000 ms // %%%Skyqwq #include <bits/stdc++.h> #define pb push_back #define fi first #define se second #define mp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } int n,res; int main() { int a,b,c; scanf("%d",&n); int lst=0,lst1=0; for(int i=1;i<=n;i++) { scanf("%d%d%d",&a,&b,&c); if(i==1)continue; else if(i==2) { if(a)lst=3,res+=3; } else if(i==3) { if(a)lst1=lst,lst=3,res+=3; else if(b) { if(lst)lst1=lst,lst=0; else lst1=lst,lst=2,res+=2; } } else { if(a)lst1=lst,lst=3,res+=3; else if(b) { int t=2-lst; if(t<=0)lst1=lst,lst=0; else lst1=lst,lst=t,res+=t; } else if(c) { if(lst1||lst)lst1=lst,lst=0; else lst1=lst,lst=1,res++; } else lst1=lst,lst=0; } } printf("%d",res); return 0; }

D.牛牛做数论

题目描述

牛牛最近做了这么一道题:

"对于一给定的 n(1n109),计算ϕ(n), 之中 ϕ(x)是满足1yxgcd(x,y)=1y的个数。例如: ϕ(6)=2ϕ(5)=4."

牛牛一眼看出这就是欧拉函数,于是立刻用嘴巴解决了这道题。

牛牛意犹未尽,于是他又给你出了一道题。对于一个正整数 x,牛牛定义H(x) :

H(x)=ϕ(x)x

牛牛还规定,该函数的定义域为除了1的所有正整数。

于是,牛牛给出了一个整数n,想要你回答两个关于H(x)的问题:

1、 回答一个 x0[2,n],使得 H(x0) 取到 H(x)[2,n]的最小值。若存在多个这样的x0,输出最小的一个。

2、 回答一个 x0[2,n],使得 H(x0) 取到 H(x)[2,n]的最大值。若存在多个这样的x0,输出最大的一个。

输入描述:

第一行为一个整数T(1T100),表示测试组数。

接下来的T行,每行包括一个整数n(1n109),牛牛给出的整数。

输出描述:

对于每个测试用例,输出两个空格分割的整数,依次为你对牛牛两个问题的答案。

特别的,若n=1,请输出1表示H(1)没有定义。

输入

3 2 5 1

输出

2 2 2 5 -1

说明

第二组样例中,H(1)未定义,H(2)=12,H(3)=23,H(4)=24,H(5)=45

解题思路

欧拉函数,打表

一种做法可以打表找规律:

#include<bits/stdc++.h> using namespace std; inline int euler(int n) { int res=0; for(int i=1;i<n;i++)res+=__gcd(i,n)==1; return res; } int main() { int n; while(cin>>n) { int mxpos=0,mnpos=0; double mn=1e9,mx=0; for(int i=2;i<=n;i++) { double t=1.*euler(i)/i; if(mn>t) { mn=t; mnpos=i; } if(mx<=t) { mx=t; mxpos=i; } } cout<<mnpos<<' '<<mxpos<<'\n'; } return 0; }

一种比较正规的做法是欧拉函数:ϕ(N)=N×p11p1×p21p2××pm1pm=N×p|N(11p),则:H(N)=p|N(11p),要使其值最小,则应使 p 小且多,即应寻找素数的前缀积中不大于 n的数,同时这样的数是满足题目要求的最小的数;要使其值最大,则应使 p 大而少,即最靠近 n 的素数,可以从 n 往前枚举,这是因为 109 以内最大的两个素数间隔是 282,可以直接暴力枚举

t 为不超过 109 的前缀积的个数,则:

  • 时间复杂度:O(106+T×logt)

代码

// Problem: 牛牛做数论 // Contest: NowCoder // URL: https://ac.nowcoder.com/acm/contest/23106/D // Memory Limit: 524288 MB // Time Limit: 2000 ms // %%%Skyqwq #include <bits/stdc++.h> #define pb push_back #define fi first #define se second #define mp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } const int N=1e6+5; int T,n,m,prime[N],v[N],t,product[N]; void primes(int n) { memset(v,0,sizeof v); for(int i=2;i<=n;i++) { if(v[i]==0) { v[i]=i; prime[++m]=i; } for(int j=1;j<=m;j++) { if(i*prime[j]>n||v[i]<prime[j])break; v[i*prime[j]]=prime[j]; } } } inline bool is_prime(int n) { for(int i=2;i<=sqrt(n);i++) if(n%i==0)return false; return true; } signed main() { primes(1000000); product[0]=1; for(int i=1;i<=m;i++) if(1ll*prime[i]*product[t]>1000000000ll)break; else t++,product[t]=prime[i]*product[t-1]; for(scanf("%d",&T);T;T--) { scanf("%d",&n); if(n==1)puts("-1"); else { int pos=upper_bound(product+1,product+1+t,n)-product; while(!is_prime(n))n--; printf("%d %d\n",product[pos-1],n); } } return 0; }

F.中位数切分

题目描述

给定一个长为n的数组a和一个整数m,你需要将其切成连续的若干段,使得每一段的中位数都大于等于m,求最多可以划分成多少段。

我们定义,偶数个数的中位数为中间两个数中较小的那一个,奇数个数的中位数就是正中间的数。如[2,3,1,5]的中位数为2[2,3,1,2]的中位数为2[3,5,9,7,11]的中位数为7

输入描述:

输入第一行是一个整数T(1T20),测试组数。

每个测试第一行是两个整数n,m(1n105,1m109),含义如题目所示。

第二行输入n个数,数组a,满足1ai109

由于本题输入量较大,建议使用scanf等高效输入方式。

输出描述:

每个测试用例,输出一个整数,表示最多可以划分成多少段,若无论如何划分都不能满足条件,输出1

输入

4 5 4 10 3 2 3 2 5 3 5 2 3 3 2 2 5 4 5 5 2 10 3 2 3 2

输出

-1 1 -1 5

解题思路

数学

记数列中 𝒎 的数字有 𝒄𝒏𝒕𝟏 个,<𝒎 的数字有 𝒄𝒏𝒕𝟐 个,,则答案为 𝒄𝒏𝒕𝟏𝒄𝒏𝒕𝟐,该值 𝟎 时输出 1

证明:
𝑓(𝑙,𝑟) 为原数组中 𝑎[𝑙]𝑎[𝑟] 一段中的元素对应的 𝑐𝑛𝑡1𝑐𝑛𝑡2 的值
𝑓() 的性质:
𝑓(𝑙,𝑟)>0 表示该段单独拿出来满足中位数 𝑚
𝑓(𝑙,𝑟)=𝑓(𝑙,𝑚𝑖𝑑)+𝑓(𝑚𝑖𝑑+1,𝑟)
原问题 𝑓(1,𝑛)0时输出 1 是显然的;
欲证明:𝑓(1,𝑛)>0 时,𝑓(1,𝑛) 即为原问题答案;
若可以找到一个位置 𝑚𝑖𝑑,使 𝑓(1,𝑚𝑖𝑑)>0&&𝑓(𝑚𝑖𝑑+1,𝑛)>0,则
沿 𝑚𝑖𝑑 将数组切开得到的两部分中位数依然满足条件,此时区间数
+=1
所以我们要探究下什么时候数组可以切:
定理:当且仅当 𝑓(𝑙,𝑟)>1 时存在一种切法使
𝑓(𝑙,𝑚𝑖𝑑>0)&&𝑓(𝑚𝑖𝑑+1,𝑟)>0
证明:
• 若有一个位置 𝑚𝑖𝑑 使得 𝑓(𝑙,𝑚𝑖𝑑)=1,则该位置是满足条件的切
割位置,因为此时 𝑓(𝑙,𝑚𝑖𝑑)=1>0𝑓(𝑚𝑖𝑑+1,𝑟)=𝑓(𝑙,𝑟)𝑓(𝑙,𝑚𝑖𝑑)>11=0
• 又因为 𝑓(𝑙,𝑙1)=0(表示空区间)且 𝑓(𝑙,𝑟)>1𝑓(𝑙,𝑥𝑓(𝑙,𝑥+1) 时值只会变化 1,因此过程中一定存在某一时刻 𝑚𝑖𝑑 使
𝑓(𝑙,𝑚𝑖𝑑)=1

由上,只要 𝑓(𝑙,𝑟)>1 就可以切,而我们又希望切得尽可能多,
因此最终状态一定是所有切割得到的段都有 𝑓(𝑙,𝑟)=1(否则还
可以再切),因此,li,rif(li,ri)=最终切成的段数,又因为初始时有li,rif(li,ri)=f(1,n)cnt1cnt2 所以最终切成的段数 =𝑐𝑛𝑡1𝑐𝑛𝑡2,证毕。

  • 时间复杂度:O(n)

代码

// Problem: 中位数切分 // Contest: NowCoder // URL: https://ac.nowcoder.com/acm/contest/23106/F // Memory Limit: 524288 MB // Time Limit: 2000 ms // %%%Skyqwq #include <bits/stdc++.h> #define pb push_back #define fi first #define se second #define mp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } int main() { int t; for(scanf("%d",&t);t;t--) { int n,m,x; scanf("%d%d",&n,&m); int cnt1=0,cnt2=0; while(n--) { scanf("%d",&x); cnt1+=x>=m; cnt2+=x<m; } if(cnt1-cnt2<=0)puts("-1"); else printf("%d\n",cnt1-cnt2); } return 0; }

G.ACM is all you need

题目描述

作为一名ACM退役选手,炸鸡块君除了刷刷区域赛榜单看看ddl战神又在哪里拿金了、踢一踢FIFA22,也就是学习一些深度学习小知识。

众所周知,在深度学习当中,任务往往可以理解为找到一组参数使得loss函数可以取全局最小值,实际操作中往往使用梯度下降的方法来近似解决该问题,但这也会导致陷入local minimum的问题,为了尽可能解决这一问题,也有许多相关的研究,其中有研究指出(Do We Need Zero Training Loss After Achieving Zero Training Error? ICML2020),对损失函数J(θ)进行一次简单的变换得到新的损失函数,就是一种有效的解决方法,变换如下:J(θ)=|J(θ)b|+b

为了验证这一问题,炸鸡块君给出了一个一维函数上的n个整数点f1,f2...,fn,定义位置i处有一个local minimum当且仅当2in1fi<min(fi+1,fi1)。现在,退役选手可以选择任意的整数值作为b并对所有函数值进行一次变换:fi=|fib|+b。请你求出进行这样的一次变换后,整个函数中local minimum最少有多少个。

输入描述:

第一行输入一个整数T(1T104),表示测试组数。

每个测试用例第一行输入一个整数n(3n105),表示函数值的个数。

每个测试用例第二行是n个整数fi(1fi109),表示该函数的函数值。

保证所有测试用例的Σn106

输出描述:

对每个测试用例,输出一个整数,表示该函数local minimum最少的个数。

输入

3 3 2 1 2 8 1 3 2 4 7 5 6 8 8 1 3 2 2 3 1 4 1

输出

0 2 0

解题思路

区间覆盖

大概思路:满足条件的 b 都会形成一段区间,进而转换为区间重叠次数最少问题
具体如下:

  1. 如果当前数 f[i] 等于 f[i1]f[i1] 中的任意一个,则肯定不是local minimum
  2. 对于任意两个相邻的数 x,y
  • x>y,需要使变换后 x<y,则:b>y+(xy)/2=(x+y)/2
  • x<y,反之,b<y+(xy)/2=(x+y)/2

另外还有一些细节需要处理:区间这里时大于小于关系,需要注意上取整和下取整,另外当不存在下限时,不能加上负无穷而是直接区间个数加一,因为这里求的是最小次数,区间个数加一是符合要求的,另外不会对求取最小次数产生影响(因为求取最小次数是每次取最小的)

  • 时间复杂度:O(t×nlogn)

代码

// Problem: ACM is all you need // Contest: NowCoder // URL: https://ac.nowcoder.com/acm/contest/23106/G // Memory Limit: 524288 MB // Time Limit: 4000 ms // %%%Skyqwq #include <bits/stdc++.h> #define pb push_back #define fi first #define se second #define mkp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } const int N=1e5+5; int n,f[N],t; int main() { for(scanf("%d",&t);t;t--) { scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%d",&f[i]); int res=0; vector<PII> a; for(int i=2;i<=n-1;i++) { if(f[i]==f[i-1]||f[i]==f[i+1])continue; int l=0,r=1e9+10; for(int x:{f[i-1],f[i+1]}) if(f[i]<x)r=min(r,(f[i]+x-1)/2); else l=max(l,(f[i]+x)/2+1); if(l<=r) { if(l==0)res++; else a.emplace_back(l,1); if(r<=1e9)a.emplace_back(r+1,-1); } } sort(a.begin(),a.end(),[](auto x,auto y) { if(x.fi!=y.fi)return x.fi<y.fi; return x.se>y.se; }); int t=res; for(auto x:a)t+=x.se,res=min(res,t); printf("%d\n",res); } return 0; }

H.牛牛看云

题目描述

就像罗夏墨迹测试一样,同一片形状的云在不同人的眼中会看起来像各种各样不同的东西。

例如,现在天上飘过了一片长条状的云彩,hina说这片云长得像是薯条,moca说这片云长得像宾堡豆沙面包(5枚装),kasumi说这片云在闪闪发光,kokoro说这片云怎么看上去不开心呢,牛牛说这片云长得就像是:

Σi=1nΣj=in|ai+aj1000|

现在给出整数序列a,请你帮牛牛求出这个式子的值。

输入描述:
第一行包括一个整数n(3n106)n,整数序列的长度。

第二行输入n个以空格分隔的整数ai(0ai1000),表示序列a

输出描述:

输出一个整数,表示该式子的值。

输入

4 500 501 500 499

输出

8

解题思路

二分

考虑去绝对值,由于式子结果与序列的顺序无关,可以将序列排序,对于固定的 i,找出满足 ai+aj1000>0 的第一个 j,其后面的数都满足该条件,即去绝对值后符号不变,前面的数要变号。另外注意第一个 j 出现的位置

  • 时间复杂度:O(nlogn)

代码

// Problem: 牛牛看云 // Contest: NowCoder // URL: https://ac.nowcoder.com/acm/contest/23106/H // Memory Limit: 524288 MB // Time Limit: 2000 ms // %%%Skyqwq #include <bits/stdc++.h> #define pb push_back #define fi first #define se second #define mp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } int n,a[1000005],s[1000005]; int main() { scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%d",&a[i]); int res=0; sort(a+1,a+1+n); for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i]; for(int i=1;i<=n;i++) { int pos=upper_bound(a+1,a+1+n,1000-a[i])-a; if(pos==n+1) { res+=1000*(n-i+1)-(s[n]-s[i-1])-(n-i+1)*a[i]; continue; } if(i<=pos) { res+=1000*(pos-1-i+1)-(s[pos-1]-s[i-1])-(pos-1-i+1)*a[i]; res+=s[n]-s[pos-1]+(n-pos+1)*a[i]-1000*(n-pos+1); } else res+=s[n]-s[i-1]+(n-i+1)*a[i]-1000*(n-i+1); } printf("%d",res); return 0; }

枚举

由于数据量比较小,可以统计每个数出现的次数,要求的即为从序列中任选两个数求 |ai+aj1000| 之和,可以从值域的角度来考虑,即从 01000 枚举 aiaj,找出其可以形成的组合数再乘以 |ai+aj1000| 即为这两个数的贡献,这里需要注意两个数可以相等或为同一个数

  • 时间复杂度:O(106)

代码

// Problem: 牛牛看云 // Contest: NowCoder // URL: https://ac.nowcoder.com/acm/contest/23106/H // Memory Limit: 524288 MB // Time Limit: 2000 ms // %%%Skyqwq #include <bits/stdc++.h> #define pb push_back #define fi first #define se second #define mp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } int n,cnt[1005]; LL res; int main() { for(scanf("%d",&n);n;n--) { int a; scanf("%d",&a); cnt[a]++; } for(int i=0;i<=1000;i++) for(int j=i;j<=1000;j++) if(i==j)res+=1ll*abs(2*i-1000)*(cnt[i]+cnt[i]*(cnt[i]-1ll)/2ll); else res+=1ll*cnt[i]*cnt[j]*abs(i+j-1000); printf("%lld",res); return 0; }

I.B站与各唱各的

题目描述

最近炸鸡块君在逛B站时发现了有趣的视频(https://www.bilibili.com/video/BV1MR4y1H7NV ),这种视频被称作"各唱各的"挑战,基于此,炸鸡块君提出了一种有趣的"各唱各的"游戏,其具体规则如下:

n4UPm$句的歌曲;

n个UP主先各自独立的在不能与其他UP主交流的情况下录制一份唱歌的音频。对于这首歌中的每一句,每个UP主可以选择唱或不唱;

在所有UP主都录制完成后,将这n份唱歌的音频合到一起。若在合成后的音频中,某一句歌词所有人都没唱或同时被所有人都唱了,则认为这句唱失败了,否则认为这句唱成功了。

现在,炸鸡块君想知道:假设这n位UP主都足够聪明,每位UP主都精通编程且有一台计算速度无限快的超级计算机,但UP主之间不能交流,他们的目标是让成功唱出的句子数尽可能多,求期望唱成功的句子数量对109+7取模的结果。

输入描述:

输入第一行是一个整数T(1T104),测试用例的组数。

每组测试用例包括两个整数n,m(1n,m109),含义如题面所述。

输出描述:

对于每组样例输出一个整数,表示答案对109+7取模的结果。

若你的答案是一个形如pq的分数,则应输出p×q1109+7取模的结果,之中4q^{-1}q$在模109+7意义下的逆元。

输入

1 1 100

输出

0

解题思路

数学

由于无法交流,每个人在唱每句时唯一的策略就是随机以 𝑝𝑖 的概率决定唱
或不唱这一句,于是失败的概率即为:pi+(1pi),当 𝑝1=𝑝2==𝑝n=p 时最小,即失败概率为 pn+(1p)n,当 p=1/2 时最小,最终一个句子成功的概率为 2n22n,所有句子的期望为:m×2n22n

代码

// Problem: B站与各唱各的 // Contest: NowCoder // URL: https://ac.nowcoder.com/acm/contest/23106/I // Memory Limit: 524288 MB // Time Limit: 2000 ms // %%%Skyqwq #include <bits/stdc++.h> #define pb push_back #define fi first #define se second #define mp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } const int mod=1e9+7; int ksm(int a,int b,int p) { int res=1%p; for(;b;b>>=1) { if(b&1)res=1ll*res*a%p; a=1ll*a*a%p; } return res; } int main() { int t,n,m; for(scanf("%d",&t);t;t--) { scanf("%d%d",&n,&m); printf("%d\n",(1ll*m*((ksm(2,n,mod)-2+mod))%mod)%mod*ksm(ksm(2,n,mod),mod-2,mod)%mod); } return 0; }

J.小朋友做游戏

题目描述

牛牛是一个幼儿园老师,他经常带小朋友们一起做游戏。

现在,牛牛的班里有A个安静的小朋友和B个闹腾的小朋友,牛牛想要从中选出恰好n个人来做游戏。这个游戏需要小朋友们手拉手围成一个圆圈,但不妙的是,如果两个闹腾的小朋友在圆圈中紧挨着,他们就会打闹,导致游戏无法进行。

每个小朋友还有一个幸福度v,若这位小朋友被选中参加游戏,则会使得班级的幸福度增加v

请你求出,在满足上述所有限制的情况下,恰当的安排围成圆圈的方法,能使得班级的幸福度最大为多少。

输入描述:

输入第一行是一个整数T(1T103),测试数据组数。

每组测试数据,第一行是三个整数A,B,n(2A,B104,3nA+B),含义如题目所示。

第二行是A个数,第i个数vai(1vai104)表示某位安静小朋友的幸福度。

第三行是B个数,第i个数vbi(1vbi104)表示某位闹腾小朋友的幸福度。

此外,保证所有测试数据的(A+B)之和不会超过2105

输出描述:

每组测试用例,输出一行一个整数,表示最大幸福度。若无论如何安排都不能进行游戏,输出$-14。

输入

3 3 6 7 1 3 4 5 4 3 4 3 5 4 6 7 1 3 4 1 5 4 3 4 3 5 7 7 7 1 2 3 4 5 6 7 9 8 7 6 5 4 3

输出

-1 23 46

解题思路

模拟

由题意,安静的小朋友的个数一定不小于 n/2,接着枚举安静的小朋友的个数即可,注意对应的闹腾的小朋友的个数还要满足不能超过总的闹腾的小朋友的个数。取幸福度由大到小取即可

  • 时间复杂度:O(t×(AlogA+BlogB))

代码

// Problem: 小朋友做游戏 // Contest: NowCoder // URL: https://ac.nowcoder.com/acm/contest/23106/J // Memory Limit: 524288 MB // Time Limit: 2000 ms // %%%Skyqwq #include <bits/stdc++.h> #define pb push_back #define fi first #define se second #define mp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } int t,n,A,B,a[10005],b[10005]; int main() { for(scanf("%d",&t);t;t--) { scanf("%d%d%d",&A,&B,&n); for(int i=1;i<=A;i++)scanf("%d",&a[i]); for(int i=1;i<=B;i++)scanf("%d",&b[i]); sort(a+1,a+1+A,greater<int>()); sort(b+1,b+1+B,greater<int>()); for(int i=1;i<=A;i++)a[i]+=a[i-1]; for(int i=1;i<=B;i++)b[i]+=b[i-1]; int C=(n+2-1)/2; if(A<C) { puts("-1"); continue; } int res=0; for(int i=C;i<=min(A,n);i++) if(n-i<=B) res=max(res,a[i]+b[n-i]); printf("%d\n",res); } return 0; }

K.冒险公社

题目描述

最近炸鸡块君在steam上玩一款叫做《冒险公社》的游戏,这是一款在线桌游,玩家要在一连串接踵而来的岛屿冒险中选择最优的策略,在保证安全的前提下赚取尽可能多的金币。

该游戏的地图可以视作n座连续的岛屿,玩家从第一座岛屿出发,依次经过所有岛屿最终到达第n座岛屿。岛屿有三种类型,分别为象征财富的绿岛(用G表示)、象征敌人的红岛(用R表示)与象征灾难的黑岛(用B表示)。

玩家手中还有一个可以预测岛屿颜色的罗盘,该罗盘也可以发出绿色(G)、红色(R)、黑色(B)三种颜色,其预测的规则是:玩家在第i(3i)座岛屿上时,若ii1i2三座岛中绿岛数量多于红岛则发绿光、若红岛数量多于绿岛则发红光、若两者数量相等则发黑光。

现在,给定罗盘对某一张游戏地图的全部预测结果,请你告诉渴望财富的炸鸡块君,这张地图最多有多少个绿岛。

输入描述:

输入第一行包括一个整数n(3n105),岛的总个数。

第二行是一个长为n的字符串S,表示罗盘对该地图的预测结果,之中第i个字符表示罗盘在第ii座岛时发出的光。保证S1=S2=X,表示在前两座岛上时罗盘无法预测,且保证在字符串中的其他字符一定是RGB三者之一。

输出描述:

输出一个整数,表示在满足罗盘预测的前提下,这张地图最多有多少绿岛。特别地,若不存在一个满足罗盘预测的地图,输出1

示例1

输入

10 XXGGGGGBGB

输出

7

示例2

输入

10 XXBBBBBBRG

输出

4

解题思路

dp

  • 状态表示:f[i][j][k][l] 表示前 i 个字符最后三个岛分别为 j,k,l 时绿岛最多的个数,0,1,2 分别表示为绿、红、黑岛

  • 状态计算:f[i][j][k][l]=max(f[i][j][k][l],f[i1][x][j][k]+(l==0)),其中需满足罗盘的第 l 和第 k 个预测
    分析:s[l] 决定状态 j,k,l 成立时,考虑 k 的前三个字符,s[k] 决定前一个状态 x,j,k 成立时则可以发生转移,另外需要注意最后一个岛可能为绿岛

  • 时间复杂度:O(81×n)

代码

// Problem: 冒险公社 // Contest: NowCoder // URL: https://ac.nowcoder.com/acm/contest/23106/K // Memory Limit: 524288 MB // Time Limit: 2000 ms // %%%Skyqwq #include <bits/stdc++.h> #define pb push_back #define fi first #define se second #define mkp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } const int N=1e5+5,inf=0x3f3f3f3f; //f[i][j][k][l]表示前i个字符最后三个岛分别为j,k,l时绿岛最多的个数,0,1,2分别表示为绿、红、黑岛 int n,f[N][3][3][3]; char s[N]; inline char get(int j,int k,int l) { int green=(j==0)+(k==0)+(l==0),red=(j==1)+(k==1)+(l==1); if(green>red)return 'G'; if(green<red)return 'R'; return 'B'; } int main() { scanf("%d",&n); scanf("%s",s+1); for(int i=1;i<=n;i++) for(int j=0;j<3;j++) for(int k=0;k<3;k++) for(int l=0;l<3;l++)f[i][j][k][l]=-inf; for(int j=0;j<3;j++) for(int k=0;k<3;k++) for(int l=0;l<3;l++) if(get(j,k,l)==s[3])f[3][j][k][l]=(j==0)+(k==0)+(l==0); for(int i=4;i<=n;i++) for(int j=0;j<3;j++) for(int k=0;k<3;k++) for(int l=0;l<3;l++) { if(get(j,k,l)==s[i]) { for(int x=0;x<3;x++) if(get(x,j,k)==s[i-1])f[i][j][k][l]=max(f[i][j][k][l],f[i-1][x][j][k]+(l==0)); } } int res=-inf; for(int j=0;j<3;j++) for(int k=0;k<3;k++) for(int l=0;l<3;l++) if(get(j,k,l)==s[n])res=max(res,f[n][j][k][l]); printf("%d",res<0?-1:res); return 0; }

__EOF__

本文作者acwing_zyy
本文链接https://www.cnblogs.com/zyyun/p/15840658.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zyy2001  阅读(211)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示