文章目录
| 题号 | 题目 | 难度 |
|---|---|---|
| C ✅ | 报数 | easy |
| G✅ | 找不同 | easy |
| L✅ | 神秘数字 | medium easy |
| B✅ | 果树 | medium easy |
| D✅ | 垂直电梯 | medium easy |
| J✅ | 空调 | medium |
| F | 虚狩武器 | medium |
| I✅ | 耳鸣 | medium |
| H | Clank! | medium hard |
| E | 演唱会 | hard |
| M | 你追我逃 | hard |
| K | 跳楼机 | hard |
| A | 强烈的以太波动 | very hard |
B题-果树
在开始B题之前 先来看一个Kadane卡登算法
专门用来求数组的最大子数组和

假如是以当前元素x作为结尾的连续字段的最大和
x :舍弃前面所有子段,子段从当前x重新开始
前面的和是负数,带上只会拖累
x,不如重新开始
sum+x:把x接到上一段子段后面,步步高上
举例子: 上一轮sum = -2,现在x = 3
sum+x = -2+3 = 1max(3 , 1)→ 选3含义:前面‑2 这一段不要了,直接从 3 开启新子段。
上一轮sum=3,x=-1
sum+x = 2max(-1 , 2)→选2含义:虽然 x 是负数,但接上前面总和依旧更大,继续延续子段。
sum每一轮都在更新,保存的一定是以当前位置结尾的最优连续和。
ans = max(sum, ans);
ans:记录全局历史上出现过最大的子段和
cpp
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define ull unsigned long long
#define fi first
#define se second
#define PLL pair<ll, ll>
#define YES cout<<"YES"<<endl;
#define NO cout<<"NO"<<endl;
using namespace std;
const ll MAXN=100005;
const ll mod=1e9+7;
using namespace std;
ll n;
ll ans=-1e18;
int main()
{
IOS
cin>>n;
ll sum=0;
for(ll i=1;i<=n;i++)
{
ll x;
cin>>x;
sum=max(x,sum+x);
ans=max(sum,ans);
}
cout<<ans<<endl;
// cout<<fixed<<setprecision(x)<< ;
return 0;
}
果树下标从 0 开始,只能摘偶数下标 (0,2,4...)苹果。 可以最多反转一次区间l,r。 反转之后元素位置改变;
只有区间长度是偶数,反转才会改变采摘得到的总数;
长度奇数反转完全没用,直接不用考虑。
举例:区间 1 , 3 1,3 1,3长度 3。 原来位置 1 的元素,反转后跑到位置 3;1 是奇数,3 也是奇数。 原来位置 2 的元素,还留在位置 2;偶数依旧偶数。
- l 偶数,r 奇数:例 0,1, 2,3
- l 奇数,r 偶数:例 1,2, 3,4
差值:对这段字段的增益
只要是去看 奇数-偶数 去观察什么时候 累加最大 也就是在将差值记录下来后 最大的子段和
也就是说上述两种情况
cpp
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define ull unsigned long long
#define fi first
#define se second
#define PLL pair<ll, ll>
#define YES cout<<"YES"<<endl;
#define NO cout<<"NO"<<endl;
using namespace std;
const ll MAXN=100005;
const ll mod=1e9+7;
using namespace std;
ll n;
ll a[MAXN];
ll sum[MAXN];
int main()
{
IOS
cin>>n;
ll sum=0;
vector<ll>v;//偶开头 奇结尾
vector<ll>q;//奇开头 偶结尾
for(ll i=0;i<n;i++)
{
cin>>a[i];
}
for(ll i=0;i<n;i+=2)
{
sum+=a[i];
}
for(ll i=0;i+1<n;i+=2)
{
v.push_back(a[i+1]-a[i]);
}
for(ll i=1;i+1<n;i+=2)
{
q.push_back(a[i]-a[i+1]);
}
ll sum1=0;
ll ans1=0;
ll sum2=0;
ll ans2=0;
for(ll i=0;i<v.size();i++)//寻找偶数开头的最大子段和
{
sum1=max(v[i],sum1+v[i]);
ans1=max(ans1,sum1);
}
for(ll i=0;i<q.size();i++)//寻找奇数开头的最大子段和
{
sum2=max(q[i],sum2+q[i]);
ans2=max(ans2,sum2);
}
cout<<sum+max(ans1,ans2)<<endl;
// cout<<fixed<<setprecision(x)<< ;
return 0;
}
D-垂直电梯
- i号电梯对应数字 s = i + 1 s=i+1 s=i+1;电梯停靠:
s的所有因数、s的所有 ≤ n \le n ≤n的倍数。 - 坐一次电梯 x → y x\to y x→y,花费: m + y − x m+y-x m+y−x。
- 可以在同层无限换乘,起点a,终点b,求最小总费用。
本题一定有解,不存在 No Solution,永远最多坐 2 次电梯,借助 1 层中转
路径: c 0 = a → c 1 → c 2 ⋯ → c k = b c_0=a \to c_1 \to c_2 \dots \to c_k = b c0=a→c1→c2⋯→ck=b,共乘坐 k 次电梯
总费用: W = k ⋅ m + ( b − a ) \boldsymbol{W = k\cdot m + (b-a)} W=k⋅m+(b−a)
b − a b-a b−a是固定常数,最小化总费用等价于最小化电梯乘坐次数 k
最小乘坐次数 k m i n k_{min} kmin
1 层是所有正整数的因数,全部电梯都会停靠 1 层。
- 无法直达就中转: a → 1 → b a \to 1 \to b a→1→b两次电梯,
k=2 - k m i n k_{min} kmin只能取1或2
k m i n k_{min} kmin=1(直达)
总费用: W = m + ( b − a ) \boldsymbol{W =m+ (b-a)} W=m+(b−a)
满足任意一个条件,就存在电梯同时停靠a,b
gcd(a,b)>=2s=gcd(a,b),电梯编号s-1;a和b均为s的倍数,可直达
lcm(a,b)<=ns=lcm(a,b),电梯编号s-1;a和b均为s的因数,可直达
k m i n k_{min} kmin=2(中转)
上述两个条件均不成立走1层中转
总费用: W = 2 ⋅ m + ( b − a ) \boldsymbol{W = 2\cdot m + (b-a)} W=2⋅m+(b−a)
cpp
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define ull unsigned long long
#define fi first
#define se second
#define PLL pair<string, ll>
#define YES cout<<"YES"<<endl;
#define NO cout<<"NO"<<endl;
using namespace std;
const ll MAXN=1005;
const ll mod=1e9+7;
using namespace std;
ll T;
ll n,m,a,b;
int main()
{
IOS
cin>>T;
ll ans=0;
while(T--)
{
cin>>n>>m>>a>>b;
if(__gcd(a,b)>1||a*b<=n)
{
ans=0;
}
else
{
ans=m;
}
cout<<m-a+b+ans<<endl;
}
// cout<<fixed<<setprecision(x)<< ;
return 0;
}
L-神秘数字
神秘数字 x: 1 ≤ x ≤ n 1\le x \le n 1≤x≤n,我们不知道它。
我们要造出 m 个数字集合。 把全部 m 个集合拿给对方。 对方对每一个集合回答:这个 x 在不在这个集合里(是 / 否)。
我们拿到 m 个 "是 / 否" 答案之后,必须唯一确定 x 是几 。 求:m 最小是多少,并且输出这 m 个集合。
本质是二进制
m :二进制位数,也就是 ⌈ log 2 n ⌉ \lceil \log_2 n\rceil ⌈log2n⌉。
cpp
while((1ll<<m)<n)
{
m++;
}
每一个集合对应二进制的一位。 回答 "在集合"= 这一位是 1;回答 "不在集合"= 这一位是 0
举例:5
- 第 1 个集合(最低位,第 0 位):存放二进制第 0 位等于 1 的数:
{1,3,5} - 第 2 个集合(第 1 位):存放二进制第 1 位等于 1 的数:
{2,3} - 第 3 个集合(第 2 位):存放二进制第 2 位等于 1 的数:
{4,5}

不管 x 是 1~5 中哪一个,三次回答的 01 序列就是 x 的二进制,直接还原数字
cpp
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define ull unsigned long long
#define fi first
#define se second
#define PLL pair<string, ll>
#define YES cout<<"YES"<<endl;
#define NO cout<<"NO"<<endl;
using namespace std;
const ll MAXN=1005;
const ll mod=1e9+7;
using namespace std;
ll n;
int main()
{
IOS
cin>>n;
ll m=0;
while((1ll<<m)<n)
{
m++;
}
cout<<m<<endl;
for(ll i=0;i<m;i++)
{
vector<ll>v;
for(ll j=1;j<=n;j++)
{
if(j>>i&1)
{
v.push_back(j);
}
}
cout<<v.size();
for(auto x:v)
{
cout<<' '<<x;
}
cout<<endl;
}
// cout<<fixed<<setprecision(x)<< ;
return 0;
}
J-空调
-
x = ∣ t − 26 ∣ + m − 1 x = |t-26| + m-1 x=∣t−26∣+m−1:到达目标状态最少总按键次数
-
r e s t = n − x rest = n-x rest=n−x:多余、需要被消耗掉的按键次数
-
若
n<x总按键数目 也是不够的直接输出Lie -
n>=x,下列四个条件满足一个即为Maybe否则Lie
| 条件编号 | 条件表达式 | 说明 |
|---|---|---|
| 条件 1 | rest % 2 =0 | 剩余步数为偶数。到达目标后反复+、‑来回抵消,消耗偶数步,状态不变。 |
| 条件 2 | n>=(R−26)+(R−t)+(m−1) | 步数足够跑到上边界R。跑到 R 之后反复按+无效,消耗多余步数。 |
| 条件 3 | n>=(26−L)+(t−L)+(m−1) | 步数足够跑到下边界L。跑到 L 之后反复按‑无效,消耗多余步数。 |
| 条件 4 | rest%2=1 &&K%2=1 && n>=x+K | 剩余步数奇数,K为奇数,总步数足够多。完整循环一轮模式K次,模式复原,消耗奇数步,把rest转为偶数。 |
cpp
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define ull unsigned long long
#define fi first
#define se second
#define PLL pair<string, ll>
#define YES cout<<"YES"<<endl;
#define NO cout<<"NO"<<endl;
using namespace std;
const ll MAXN=1005;
const ll mod=1e9+7;
using namespace std;
ll T;
ll L,R,k,n,t,m;
int main()
{
IOS
cin>>T;
while(T--)
{
cin>>L>>R>>k>>n>>t>>m;
ll x=abs(t-26)+m-1;
if(n<x)
{
cout<<"Lie";
}
else if((n-x)%2==0)
{
cout<<"Maybe";
}
else if(n>R-26+R-t+m-1||n>26-L+t-L+m-1)
{
cout<<"Maybe";
}
else if(k%2==1&&(n-x)%2==1&&n>=x+k)
{
cout<<"Maybe";
}
else
{
cout<<"Lie";
}
cout<<endl;
}
// cout<<fixed<<setprecision(x)<< ;
return 0;
}
I-耳鸣
刚拿到题,第一想法肯定是暴力思路(虽然直观,但是太慢)
对每一个圆,二重循环遍历圆内每一个格子,标记
vis[i][j]=1,最后统计 1 的个数 总复杂度爆炸,直接 TLE。
用差分优化 可以将复杂度降到O(1)
差分优化 差分维护行区间
将圆按行处理
-
对于圆心
(x,y)枚举圆每一行号j(纵向范围:x-a<=j<=x+a,并且限制在网格1~n,保证不越界) -
当前行距离圆心纵向距离:
dx=|j-x|由 dx2+dy2<=a2 可得 dy2<=a2-dx2 dymax=sqrt( a2-dx2 )
所以 圆覆盖 列区间 是 \[y-dy~max~,y+dy~max~\], 再裁剪到网格
[1,m],得到[l1,r1];对于本行,区间
[l1,r1]全部+1
cpp
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define ull unsigned long long
#define fi first
#define se second
#define PLL pair<string, ll>
#define YES cout<<"YES"<<endl;
#define NO cout<<"NO"<<endl;
using namespace std;
const ll MAXN=1005;
const ll mod=1e9+7;
using namespace std;
ll n,m,a,q;
int main()
{
IOS
cin>>n>>m>>a>>q;
vector<ll>diff((n+2)*(m+2),0);
//二维网格压成一维差分数组
for(ll i=1;i<=q;i++)
{
ll x,y;
cin>>x>>y;
//枚举圆纵向覆盖的每一行j
for(ll j=max(1ll,x-a);j<=min(n,x+a);j++)
{
ll s=a*a-(x-j)*(x-j);
ll l=(ll)(sqrt(s));
while((l+1)*(l+1)<=s)//修正sqrt向下取整误差,防止l偏小
{
l++;
}
ll l1=max(1ll,y-l),r1=min(m,y+l);
diff[(j-1)*m+l1]++;
diff[(j-1)*m+r1+1]--;
}
}
ll ans=0;
ll k=0;
for(ll i=1;i<=n*m;i++)
{
k+=diff[i];
if(k!=0)
{
ans++;
}
}
cout<<ans<<endl;
// cout<<fixed<<setprecision(x)<< ;
return 0;
}