出题人预估难度:
签到:AJM
简单:CGL
中等:DFH
偏难:BIE
困难:K
A-别叫我那刻夏
题目大意
输入一个正整数 ,如果为
就输出 宁波大学 笔画数,否则输出 阿那克萨戈拉斯 笔画数。
解题思路
纯签到,唯一坑点是 子 的笔画是 笔。宁波大学笔画数为
。
参考代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll x;
int main(){
cin>>x;
if(x==315211)
cout<<24;
else
cout<<55;
}
J-选最好的算法
题目大意
给定一个数 ,比较
与
的大小。
解题思路
签到第二题,当 ,
明显小于
;当
,
明显大于
。
参考代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll T,n;
int main(){
cin>>T;
while(T--){
cin>>n;
if(n<=3)
cout<<1<<'\n';
else
cout<<2<<'\n';
}
}
M-手机里有一款四字游戏
题目大意
根据题目中的段位匹配规则(要求在连续大段位内),判断两名玩家能否一起匹配。(注意玩家的段位只升不降)
解题思路
签到,注意不能直接通过两名玩家的小段位差距来判断,因为像 A1 和 B5,A5 和 C1,前者差了 小段,后者差了
小段,但前者可以一起匹配后者不行,所以与小段位的差值没有必然联系。
于是本题做法是,先判断大段位只差是不是大于等于 ,若是,则转化成小段位差,最后输出结果。
参考代码
#include<bits/stdc++.h>
using namespace std;
char Ax, Ay, Bx, By;
int main(){
cin >> Ax >> Ay >> Bx >> By;
if (abs(Ax - Bx) >= 2) {
cout << "NO" << '\n';
if (Ax <= Bx) {
cout << (Bx - Ax - 2) * 5 + 6 - (Ay - '0');
}
else {
cout << (Ax - Bx - 2) * 5 + 6 - (By - '0');
}
}
else {
cout << "YES";
}
return 0;
}
C-仿生人不会梦到彩色电子羊
题目大意
给定一个 个节点的树,用
个颜色对树每个点染色,相邻节点颜色不能相同。
解题思路
假定 号节点为根节点,从根往叶子染色,根节点有
种染色方法,它的儿子节点不能与它自己颜色相同,由于他们之间不会互相影响,因此这些儿子节点都有
种染色方法。发现除了根节点的所有节点都有
种染色方法。
最终得到式子 。
参考代码
#include<bits/stdc++.h>
#define ll long long
#define MO 1000000007
using namespace std;
ll n,K,ans;
int main(){
cin>>n>>K;ans=K;
for(ll i=1,u,v;i<n;i++)
cin>>u>>v,ans*=K-1,ans%=MO;
cout<<ans;
}
G-无路可逃
题目大意
在给定的二维平面,模拟对一个矩形区域进行干扰,注意同一个地方干扰两次会被抵消。
最后输出***扰的面积。
解题思路
直接对每个单位面积进行模拟会超时,于是考虑二维差分。
对于每一个单位的面积,他的干扰次数可以通过差分来做到 存储,二维的话可以通过容斥原理来差分。
最后再进行二位前缀和就能统计出答案了,前缀和时间复杂度是 。
参考代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int _;
int n,m;
int p[3600][3600];
int x,y,xx,yy;
int main(){
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin >> n >> m;
cin >> _;
while (_--) {
cin >> x >> y >> xx >> yy;
p[x][y] += 1;
p[xx + 1][y] -= 1;
p[x][yy + 1] -= 1;
p[xx + 1][yy + 1] += 1;
}
ll ans = 0;
for (int i = 1;i <= n;++i) {
for (int j = 1;j <= m;++j) {
p[i][j] += p[i][j - 1];
}
}
for (int j = 1;j <= m;++j) {
for (int i = 1;i <= n;++i) {
p[i][j] += p[i - 1][j];
}
}
for (int i = 1;i <= n;++i) {
for (int j = 1;j <= m;++j) {
if (p[i][j] % 2 == 1) {
ans += 1;
}
}
}
cout << ans;
return 0;
}
L-人有五名,代价有三个
题目大意
求一个字符串中, 形式的子串个数。
题目要求相当于寻找所有包含子序列 "53" (即 "5" 的位置小于 "3" 的位置)的子串个数。
解题思路
枚举每个左端点。
对于所有左端点相同的子串,从左往右枚举右端点,用 变量记录是否出现 "5" 与是否出现 "53" 子序列,初始值为
。如果出现 "5",那么
;如果出现 "3" ,并且
,说明有 "53" 子序列,那么
。判断完后如果
,说明当前子串是
句式。
时间复杂度是 。
(存在时间复杂度 的算法。)
参考代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n,ans;
string s;
int main(){
cin>>s;
n=s.length();
s='@'+s;
for(ll i=1,vis5,vis53;i<=n;i++){
vis5=vis53=0;
for(ll j=i;j<=n;j++){
if(s[j]=='5')
vis5=1;//找到子序列"5"
else if(vis5&&s[j]=='3')
vis53=1;//找到子序列"53"
if(vis53) ans++;
}
}
cout<<ans;
}
D-神秘小游戏
题目大意
模拟地图中每个玩家的数值 。
地图最多容纳 个玩家。
玩家的 值会在下列情况发生变化:
-
当玩家进入地图时,全员的
值加一,而他自己的
值变为
。
-
当玩家重生时,
值小于他的人全员加一,大于他的人不变,而他自己的
值变为
。
-
当玩家离开地图时,
值小于他的人不变,大于他的人全员减一,而他自己的
值被移除。
我们需要维护所有玩家的 值,并对每一种指令做出对应的回应。输出描述看题目,就不累赘了。
解题思路
值是唯一的。
用一个字符串数组维护每个 值对应的玩家。
然后 值变化时,按要求对这个数组中的玩家进行换位即可。
时间复杂度是 (
是名字的最大长度)。
参考代码
#include<bits/stdc++.h>
#define ll long long
#define MXN 1002
using namespace std;
ll m,k,num;
string hook[MXN];
int main(){
cin>>m>>k;
while(m--){
string op,name;
ll power,x;
cin>>op;
if(op=="find"){//找power值对应的玩家
x=0;
cin>>power;
cout<<hook[power]<<'\n';
}else{
cin>>name;
if(op=="join"){{//玩家加入
x=0;
if(num<k){
for(ll i=num;i;i--)
hook[i]=hook[i-1];
hook[0]=name;
num++;
cout<<"joined\n";
}else{
cout<<"full\n";
}
}else if(op=="exit"){{//玩家退出
for(ll i=0;i<num;i++)
if(hook[i]==name) x=i;
for(ll i=x;i<num-1;i++)
hook[i]=hook[i+1];
num--;
cout<<"exited\n";
}else if(op=="kill"){//玩家重生
for(ll i=0;i<num;i++)
if(hook[i]==name) x=i;
for(ll i=x;i;i--)
hook[i]=hook[i-1];
hook[0]=name;
cout<<"killed\n";
}else{//找玩家对应的power值
for(ll i=0;i<num;i++)
if(hook[i]==name) x=i;
cout<<x<<'\n';
}
}
}
}
F-崩环:星球轨道
题目大意
有 环轨道,所有轨道的圆心在
轴上,所有轨道在同一个二维平面上。
问有多少数对 ,满足第
个轨道与第
个轨道相切或相交。
解题思路
轨道与
轨道相交,那么
轨道与
轨道相交。如果不限制数对
的
大小关系,那么每个
对应一个
这样的数量恰好是要求的答案
乘上
。也就是说,轨道的顺序不重要,我们对轨道换位也不影响最终结果。
我们求出每个轨道的左右端点 。
我们对轨道按左端点 从小到大排序,如果左端点
相同,右端点
小的在前。
对于排序后第 个轨道
,如果排在它前面的第
个轨道
,满足
,那么这两个轨道相交。
我们只需要对每个轨道,求出在排序后序号更小的轨道中,有多少个轨道,它的 值在当前轨道范围内,这样的排序方式保证了不会重复或者漏算。
使用数据结构(参考代码是线段树)维护区间和,具体维护区间有多少个轨道的 值在区间中。
时间复杂度是 。
参考代码
#include<bits/stdc++.h>
#define ll long long
#define MXN 100002
using namespace std;
struct XD_tree{
ll pl,pr,sum;//左右子树与区间和
}tr[MXN*8];//线段树
ll trt,rt;//线段树节点数与根
struct huan{
ll l,r;//轨道左右端点
}a[MXN];//轨道
ll n,m=400002,x[MXN],r[MXN],ans;//轨道覆盖的范围大小能到400000
bool cmp(huan i,huan j){
return i.r==j.r?i.l<j.l:i.r<j.r;
}
void ptup(ll p){//以下为线段树基本操作
tr[p].sum=tr[tr[p].pl].sum+tr[tr[p].pr].sum;
}
void chg(ll &p,ll l,ll r,ll x,ll k){
if(!p) p=++trt;//因为懒没写线段树的建树,而直接开点。
if(l==r){
tr[p].sum+=k;
return;
}
ll mid=l+r>>1;
if(x<=mid) chg(tr[p].pl,l,mid,x,k);
else chg(tr[p].pr,mid+1,r,x,k);
ptup(p);
}
ll ask(ll p,ll l,ll r,ll x,ll y){
if(!p) return 0;
if(x<=l&&r<=y) return tr[p].sum;
ll mid=l+r>>1,ans=0;
if(x<=mid) ans+=ask(tr[p].pl,l,mid,x,y);
if(y>mid) ans+=ask(tr[p].pr,mid+1,r,x,y);
return ans;
}//以上为线段树基本操作
int main(){
cin>>n;
for(ll i=1;i<=n;i++)
cin>>x[i];
for(ll i=1;i<=n;i++)
cin>>r[i];
for(ll i=1;i<=n;i++)
a[i]={x[i]-r[i],x[i]+r[i]};
sort(a+1,a+n+1,cmp);
for(ll i=1;i<=n;i++){
ans+=ask(rt,1,m,1,a[i].l+m/2);
chg(rt,1,m,a[i].l+m/2,1);
chg(rt,1,m,a[i].r+1+m/2,-1);
}
cout<<ans<<'\n';
}
H-阿基维利的列车
题目大意
给定 ,构造一个长度为
的数列
。使得下列条件成立。
-
相邻两个数互质。
-
相隔
个数的两个数非互质。
-
所有的值互不相同,且范围为
。
解题思路
一、思路引导:
无法与其他数满足非互质,我们不考虑
。
尝试先满足相邻互质,按顺序的数 虽然满足,但是在普遍情况下无法满足非互质。
尝试用质数数列, 全部互质,也无法满足非互质。
为了形象化,将每 个数作为一行,比如当
,列成下面形式:
那么每一列需要非互质。
如果按,那么所有数之间都互质,非互质最简单的满足方法便是给这整列乘上同一个数,比如给第
列乘上
。
既然如此那我们给第 列乘上
;但是第
列不能乘上
,会与第
列非互质,那么给第
列乘上
。
因此,为了仍然满足相邻互质,将每列乘上不同的质数,而且这个质数最好不出现在其他列的数的因子中。
二、构造方法:
尝试使用质数数列 初步作为
,给第
列的数乘上
,既保证了每列非互质,又因为质数
是独一的,不同列之间也不会存在大于
的公因数,即满足相邻互质。不难发现这样的数列每个数不相同。满足题目要求。
因此 能构造出下面数列:
(构造方法不唯一,存在许多构造方法。甚至有随机数的)
参考代码
参考代码为了取模偷点懒,数组从下标0开始
#include<bits/stdc++.h>
#define ll long long
#define MXM 3000002
#define MXN 220002
using namespace std;
ll n,k;
ll p[MXN],tt;
bool vis[MXM];
int main(){
for(ll i=2;i<MXM;i++)//埃氏筛
if(!vis[i]){
p[tt++]=i;
for(ll j=i*2;j<MXM;j+=i)
vis[j]=1;
}
cin>>n>>k;
for(ll i=n-1;i>=0;i--)
p[i]*=p[i%(k+1)];
for(ll i=0;i<n;i++)
cout<<p[i]<<' ';
}
B-阳光跑
题目大意
给定一个有向图,每次询问求 Bezime 从一个节点 出发,行走恰好
条边后,可能会出现在哪些节点,输出节点个数并从小到大输出这些节点的编号。
解题思路
题目很板,有原题。
行走的距离很大,而且也不像无向图那样可以分奇偶解决。
由于距离很大,又没什么好的贪心方法,联想到快速幂。
注意到题目给的节点个数 比较小,边数
比较大,可以考虑直接写出
的邻接矩阵。
邻接矩阵能表示每个点走 步能到的点,想到邻接矩阵自乘得到的矩阵是每个点走
步能到的点,继续自乘则是
步、
步,以此类推。
如此,便能设置初始矩阵为单位矩阵,用矩阵快速幂求出每个点走 步能到的点。但每次询问的时间复杂度为
,总时间复杂度为
。
初步写出下面代码, 初始是
邻接矩阵,
初始是
单位矩阵。
while(k){
if(k&1) a=mul(a,f);
f=mul(f,f);
k>>=1;
}
考虑优化,想到每次询问时,矩阵快速幂内部都要算一遍邻接矩阵的自乘 ,因此可以预处理邻接矩阵的自乘。但是在初始矩阵的累乘
中还是很慢,由于我们每次询问只问一个点,因此初始矩阵可以设为
的矩阵,表示初始点
能到的点,单次矩阵乘法时间复杂度降为
。
预处理时间复杂度为 ,矩阵快速幂时间复杂度为
,总时间复杂度为
。
(本题没打算塞 优化,因此
优化爆搜找循环也能过本题。)
参考代码
#include<bits/stdc++.h>
#define ll long long
#define MX 2000000000
#define MXN 102
#define LG 31
using namespace std;
ll n,m,T;
bool a[MXN],f[LG][MXN][MXN];
void mul1(bool x[MXN][MXN],bool y[MXN][MXN],bool z[MXN][MXN]){//将y*z赋值到n*n矩阵x
ll ans[MXN][MXN]={};
for(ll k=1;k<=n;++k)
for(ll i=1;i<=n;++i)
for(ll j=1;j<=n;++j)
ans[i][j]|=y[i][k]&z[k][j];
for(ll i=1;i<=n;++i)
for(ll j=1;j<=n;++j)
x[i][j]=ans[i][j];
}
void mul2(bool x[MXN],bool y[MXN],bool z[MXN][MXN]){//将y*z赋值到1*n矩阵x
ll ans[MXN]={};
for(ll k=1;k<=n;++k)
for(ll j=1;j<=n;++j)
ans[j]|=y[k]&z[k][j];
for(ll j=1;j<=n;++j)
x[j]=ans[j];
}
int main(){
cin>>n>>m>>T;
for(ll i=1,u,v;i<=m;++i)
cin>>u>>v,f[0][u][v]=1;
for(ll l=1;l<LG;++l)
mul1(f[l],f[l-1],f[l-1]);//预处理行走2的幂次距离的矩阵
while(T--){
ll x,k,j=0,num=0;
cin>>x>>k;
for(ll i=1;i<=n;++i)
a[i]=0;
a[x]=1;//初始化为行走0步能到的点
while(k){
if(k&1) mul2(a,a,f[j]);
k>>=1;
++j;
}
for(ll i=1;i<=n;++i)
num+=a[i];
cout<<num<<' ';
for(ll i=1;i<=n;++i)
if(a[i]) cout<<i<<' ';
cout<<'\n';
}
}
I-你的头怎么尖尖的
题目大意
给定一个长度为 的数组
。每次操作可以给
增减
。问最少要多少次操作,才能让所有的
,均不满足
。
解题思路
我们约定 的位置
为峰,
为山脚,发现山脚不能为峰,因此峰与峰之间不相邻。
贪心部分:
看到这种题,思考能不能贪心。
如果 值需要增加,那么
或
为峰;如果
值需要减少,那么
为峰。贪心使得消去后的峰大小关系为
或者
,并且也不会产生新的峰。
我们先尝试贪心将所有的峰值减小,减至两山脚的最大值。
如果这时我们将一个被减过的峰值回退一个减少 的操作,将最大的山脚增加
,此时总操作次数不变,但是山脚增加了
。不难发现,一个峰只有山脚会影响其他的峰,而这个山脚增加,只会利于其他峰的消去,毕竟其他峰的山脚增加,那么消去这个峰所需操作次数可能减少。
因此只增山脚不减峰的修改方式优于其他的方案。
dp 部分:
这时发现可能能 ,因为只有山脚增加,并且增加只会到峰值,也就是说每个位置只有
个可能的最终值:
山脚变化为
峰值的大小;
山脚不变;
山脚变化为
峰值的大小。
考虑线性 求从下标
开始所需的最小操作次数。
给 设定
个状态(
山脚增加至
峰值的状态,被优化进
峰值不变的状态中了),存的值均为从下标
开始所需的最小操作次数:
-
表示第
个位置
暂时不发生变化,即不变为
(可能在后面的状态转移中变为
)。
-
表示第
个位置
增加至峰值
,如果
不是峰,那么给
,表示
不需要增加。
对于 位置求状态转移:
-
如果
为峰,那么有两种方式消去:
不变,将
增加到
,考虑
是否增加过(增加的状态中
为
),两个状态取最小的操作方法,
对应转移方程:;
- 或者将
增加到
,
对应转移方程:。
-
如果
不为峰,那么
值必然不会发生变化,
对应转移方程:。
最终状态转移方程:
时间复杂度为 。
(其他做法合理均可。)
参考代码
#include<bits/stdc++.h>
#define ll long long
#define MX 1000000000000000000ll
using namespace std;
ll n,h[200002],f[200002][2];
int main(){
cin>>n;
for(ll i=1;i<=n;i++)
cin>>h[i];
f[1][1]=f[2][1]=MX;//0和1位置不可能为峰,因此1和2位置1状态赋值为MX。
for(ll i=2;i<n;i++){
if(h[i-1]<h[i]&&h[i]>h[i+1]){
f[i+1][0]=min(f[i-1][0]+h[i]-h[i-1],f[i-1][1]+max(0ll,h[i]-h[i-2]));
f[i+1][1]=f[i][0]+h[i]-h[i+1];
}else f[i+1][0]=min(f[i][0],f[i][1]),f[i+1][1]=MX;
}
cout<<min(f[n][0],f[n][1]);
}
E-密码生成
题目大意
输入,计算题目中算式的结果。
解题思路
显然直接计算是不行的,通过打表找规律可以发现答案是。
下面来证明这个结果:
由于代表了既约关系,考虑构造一个二维平面来理解,下面举一个特殊情形
。
首先我们将所有的点标出,并写上他们乘的值
。
对于值不为1的点,我们把他与原点连线。
将直线经过的整点标注出来,这时我们可以发现,网格已经恰好被填满了,并且直线上面的整点数,与首个经过的整点上的值一致。
在这个提示下,接下来我们倒着思考问题,先考虑对于网格中的经过 ,
的直线,这一直线经过的第一个整点是什么?应该是
。不妨设这个点是
,再考虑这个直线经过了多少个整点,对于
的网格,其经过的整点个数应该是
。
从这两个结论我们可以发现,网格 恰好可以表示成算式:
。可以理解成非互质的点被存入互质点的值了,从而填满了网格。
于是,题目结论得证。
参考代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a,b,c;
int main(){
cin>>a>>b>>c;
cout<<a*b%c;
return 0;
}
K-鲁珀特帝国机械齿轮
题目大意
有 个房间,玩家进入房间
时,会变化
个宇宙碎片,不会低于
;玩家离开房间
时,宇宙碎片超过
的部分会失去。
有 次操作。
操作一可以修改一个房间的 。
操作二可以修改一个房间的 。
操作三需要回答初始有一些宇宙碎片,然后从某个房间进入,到某个房间出来,最终剩下多少宇宙碎片。
解题思路
使用线段树维护区间信息。
将一个房间看作一个函数:( 为进入第
个房间前的宇宙碎片数量)
而对于范围 ,
,在范围外的都会限制在边界处。
如果自变量 取值范围(定义域)为:
。
那么因变量 取值范围(值域)能通过一系列分类讨论求得,并将
取值范围在
之外的取值都舍去。
接着用 取值范围,能够求出
取值范围。
因此能从 的取值范围求得
的取值范围,以此类推。
形象化的,我们把取值范围的变化画成下面的平行四边形。
这样的平行四边形合并满足结合律,我们可以对平行四边形的合并进行分治,即线段树维护。
我们维护区间 这种平行四边形的信息,
的下端点,
的下端点,与平行四边形的长。
能够从 轻易计算出
的值,如图所示:
参考代码
#include<bits/stdc++.h>
#define ll long long
#define MX 2000000000
#define MXN 100002
using namespace std;
struct edge{//平行四边形
ll x,y,len;//x为l边界的下端点,y为r边界的下端点,len为平行四边形的长。
};
struct bzm{
ll pl,pr;//左右子树
edge ans;//区间的平行四边形
}tr[MXN*2];//线段树
ll rt,trt;//线段树根与节点数
ll n,m,a[MXN],b[MXN];
inline ll mge(ll x,edge b){//将x值代入平行四边形b求得的值
if(x<b.x) return b.y;
if(x>b.x+b.len) return b.y+b.len;
return b.y+x-b.x;
}
inline edge mge(edge a,edge b){//合并两个平行四边形a,b,返回合并的平行四边形
ll midl=max(a.y,b.x),midr=min(a.y+a.len,b.x+b.len);
if(midr<midl)
if(a.y<b.x) midr=midl;
else midl=midr;
edge ans={midl+a.x-a.y,midl-b.x+b.y,midr-midl};
return ans;
}
inline void ptup(ll p){//合并左右区间的平行四边形
tr[p].ans=mge(tr[tr[p].pl].ans,tr[tr[p].pr].ans);
}
inline void doit(ll p,ll a,ll b){//将房间的信息转化为平行四边形
if(a<0) tr[p].ans={-a,0,b};
else tr[p].ans={0,min(b,a),max(0ll,b-a)};
}
void bd(ll &p,ll l,ll r){//建树
p=++trt;
if(l==r){
doit(p,a[l],b[l]);
return;
}
ll mid=l+r>>1;
bd(tr[p].pl,l,mid);
bd(tr[p].pr,mid+1,r);
ptup(p);
}
void chg(ll p,ll l,ll r,ll x,ll ka,ll kb){//单房间修改
if(l==r){
a[l]=ka,b[l]=kb;
doit(p,ka,kb);
return;
}
ll mid=l+r>>1;
if(x<=mid) chg(tr[p].pl,l,mid,x,ka,kb);
else chg(tr[p].pr,mid+1,r,x,ka,kb);
ptup(p);
}
edge ask(ll p,ll l,ll r,ll x,ll y){//查询区间的平行四边形
if(x<=l&&r<=y) return tr[p].ans;
ll mid=l+r>>1;
edge ans={0,0,MX};
if(x<=mid) ans=mge(ans,ask(tr[p].pl,l,mid,x,y));
if(y>mid) ans=mge(ans,ask(tr[p].pr,mid+1,r,x,y));
return ans;
}
int main(){
cin>>n>>m;
for(ll i=1;i<=n;i++)
cin>>a[i];
for(ll i=1;i<=n;i++)
cin>>b[i];
bd(rt,1,n);
while(m--){
ll op,i,x,y,w;
edge ans;
cin>>op;
if(op==1){//修改房间的a值
cin>>i>>x;
chg(rt,1,n,i,x,b[i]);
}else if(op==2){//修改房间的b值
cin>>i>>y;
chg(rt,1,n,i,a[i],y);
}else{//利用平行四边形求得最终结果
cin>>x>>y>>w;
cout<<mge(w,ask(rt,1,n,x,y))<<'\n';
}
}
}