A.369(思维)
题意:
给你两个整数 A A A和 B B B。
有多少个整数 x x x满足以下条件?
- 条件:可以将三个整数 A A A、 B B B和 x x x按一定顺序排列,组成一个等差序列。
当且仅当 q − p q-p q−p等于 r − q r-q r−q时,按此顺序排列的三个整数 p p p, q q q, r r r的序列是等差序列。
分析:
题意为给你两个数 A A A和 B B B,再加一个整数,使得它与 A , B A,B A,B构成等差数列,问有几个数满足条件。
分情况讨论:
-
A = B A=B A=B,答案为 1 1 1
-
( A + B ) / 2 (A+B)/2 (A+B)/2为整数,答案为 3 3 3
-
( A + B ) / 2 (A+B)/2 (A+B)/2不为整数,答案为 2 2 2
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=100;
const int MOD=1000000007;
void solve(){
int a,b;
cin>>a>>b;
if(a==b){
cout<<1<<endl;
}
else{
if((a+b)%2==0){
cout<<3<<endl;
}
else{
cout<<2<<endl;
}
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
B.Piano 3(模拟)
题意:
高桥有一架钢琴,上面有 100 100 100个琴键排成一行。从左边开始的 i i i个键叫做第 i i i个键。
他将依次按下 N N N个琴键来演奏音乐。对于第 i i i个按键,如果 S _ i = L S\_i=L S_i=L,他会用左手按 A _ i A\_i A_i键,如果 S _ i = R S\_i=R S_i=R,他会用右手按 A _ i A\_i A_i。
开始演奏前,他可以将双手放在任意键上,此时疲劳度为 0 0 0。演奏过程中,如果他将一只手从 x x x移到 y y y键上,疲劳度会增加 ∣ y − x ∣ |y-x| ∣y−x∣(反之,除移动双手外,疲劳度不会增加)。用手按下某个键时,该手必须放在该键上。
找出表演结束时可能的最低疲劳度。
分析:
按题意模拟,用一个map
记录左右手当前位置,然后移动到下一个位置时计算距离(差值的绝对值),累计求和。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=100;
const int MOD=1000000007;
void solve(){
int n;
cin>>n;
int ans=0;
map<char,int>pos;
while(n--){
int p;
string s;
cin>>p>>s;
if(pos.find(s[0])!=pos.end()) {
ans+=abs(pos[s[0]]-p);
}
pos[s[0]]=p;
}
cout<<ans<<endl;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
C.Count Arithmetic Subarrays(差分)
题意:
给你一个由 N N N个正整数 A = ( A _ 1 , A _ 2 , … , A _ N ) A=(A\_1,A\_2,\dots,A\_N) A=(A_1,A_2,…,A_N)组成的序列。
求满足 1 ≤ l ≤ r ≤ N 1\leq l\leq r\leq N 1≤l≤r≤N的一对整数 ( l , r ) (l,r) (l,r)中,子序列 ( A _ l , A _ l + 1 , … , A _ r ) (A\_l,A\_{l+1},\dots,A\_r) (A_l,A_l+1,…,A_r)构成等差数列的个数。
当且仅当存在一个 d d d使得 x _ i + 1 − x _ i = d ( 1 ≤ i < ∣ x ∣ ) x\_{i+1}-x\_i=d\ (1\leq i\lt |x|) x_i+1−x_i=d (1≤i<∣x∣)是等差数列时,序列 ( x _ 1 , x _ 2 , … , x _ ∣ x ∣ ) (x\_1,x\_2,\dots,x\_{|x|}) (x_1,x_2,…,x_∣x∣)才是等差数列。长度为 1 1 1的序列总是等差数列。
分析:
求一个a的差分数组 b b b,看差分数组中相同数字连续有几个(设为 c c c个),则这一段的答案为 C _ c 2 \displaystyle C\_{c}^2 C_c2,即 c ( c − 1 ) 2 \displaystyle\frac{c(c-1)}{2} 2c(c−1),将答案累加即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL N=200005;
const LL MOD=1000000007;
LL n;
LL v[N],b[N];
LL ans;
void solve(){
cin>>n;
if(n==1){
cout<<1<<endl;
return;
}
for(LL i=0;i<n;i++){
cin>>v[i];
}
for(LL i=1;i<n;i++){
b[i]=v[i]-v[i-1];
}
LL c=2;
vector<LL> p;
for(LL i=2;i<n;i++){
if(b[i]!=b[i-1]){
p.push_back(c);
c=2;
}else{
c++;
}
}
p.push_back(c);
for(LL x:p){
ans+=x*(x-1)/2;
}
cout<<ans+n<<endl;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
D.Bonus EXP(动态规划)
题意:
高桥将依次遇到 N N N只怪物。第 i i i个怪物 ( 1 ≤ i ≤ N ) (1\leq i\leq N) (1≤i≤N)的力量为 A _ i A\_i A_i。
对于每只怪物,他都可以选择放走或打败它。 每次行动都会获得以下经验值:
-
如果放走怪物,他将获得 0 0 0点经验值。
-
如果他以 X X X的力量击败了怪物,则会获得 X X X点经验值。 如果击败的是偶数个怪物(第2个、第4个…),他将额外获得 X X X点经验值。
求他能从 N N N只怪物身上获得的经验值上限。
分析:
题意为较朴素的 d p dp dp,显然对于每只怪兽考虑打或不打,如果选择打,其结果会受是否是偶数这一状态的影响,因此 d p dp dp状态,除了包含基本状态 考虑前 i i i只怪兽外,还要加上状态打败了奇数/偶数只怪兽这一 0 / 1 0/1 0/1状态。
由此可得递推方程:定义状态 f _ i , 0 f\_{i,0} f_i,0表示到第 i i i个为止,已经选了奇数个数的最大价值, f _ i , 1 f\_{i,1} f_i,1表示到第 i i i个为止,已经选了偶数个数的最大价值。
对于每个位置可以考虑选或不选,所以有以下转移:
-
f _ i , 0 = m a x ( f _ i − 1 , 0 , f _ i − 1 , 1 + A _ i ) f\_{i,0}=max(f\_{i−1,0},f\_{i−1,1}+A\_i) f_i,0=max(f_i−1,0,f_i−1,1+A_i)
-
f _ i , 1 = m a x ( f _ i − 1 , 1 , f _ i − 1 , 0 + 2 × A _ i f\_{i,1}=max(f\_{i−1},1,f\_{i−1,0}+2×A\_i f_i,1=max(f_i−1,1,f_i−1,0+2×A_i)
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=200005;
const int MOD=1000000007;
LL f[N][2],a[N],n;
void solve(){
cin>>n;
f[0][0]=-1e15;
for(int i=1;i<=n;i++){
cin>>a[i];
f[i][0]=max(f[i-1][0],f[i-1][1]+a[i]);
f[i][1]=max(f[i-1][1],f[i-1][0]+2*a[i]);
}
cout<<max(f[n][0],f[n][1])<<endl;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
E.Sightseeing Tour(最短路、全排列)
题意:
问题陈述
有 N N N个岛屿和 M M M座双向桥梁连接两个岛屿。这些岛屿和桥梁的编号分别为 1 1 1、 2 2 2、 … \ldots …、 N N N和 1 1 1、 2 2 2、 … \ldots …、 M M M。 大桥 i i i连接着岛屿 U _ i U\_i U_i和 V _ i V\_i V_i,从任一方向穿过大桥所需的时间为 T _ i T\_i T_i。
没有一座桥将一个岛屿与自己连接起来,但是两个岛屿有可能被不止一座桥直接连接起来。 人们可以通过一些桥梁来往于任意两个岛屿之间。
给你 Q Q Q个问题,请逐一回答。 第 i i i个查询如下:
给你 K _ i K\_i K_i个不同的桥:桥 B _ i , 1 , B _ i , 2 , … , B _ i , K _ i B\_{i,1},B\_{i,2},\ldots,B\_{i,K\_i} B_i,1,B_i,2,…,B_i,K_i,求从岛屿 1 1 1到岛屿 N N N所需的最少时间,每座桥梁至少使用一次。
只考虑过桥的时间。
你可以以任何顺序、朝任何方向通过给定的桥梁。
分析:
考虑先求出任意两点的最短路径,由 n ≤ 500 n\le500 n≤500,选择使用 F l o y d Floyd Floyd算法求一下多源最短路。
然后对于每组询问,必须要经过编号为 B _ i , 1 , B _ i , 2 , … , B _ i , K _ i B\_{i,1},B\_{i,2},\ldots,B\_{i,K\_i} B_i,1,B_i,2,…,B_i,K_i的桥,定义编号为 i i i的桥为 ( u _ i , v _ i , w _ i ) (u\_i,v\_i,w\_i) (u_i,v_i,w_i)。
同时注意到 k ≤ 5 k\le 5 k≤5非常小,考虑全排列出依次经过哪些桥,同时暴力搜索是先到达这个桥的左端还是右端即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=505;
const int MOD=1000000007;
const LL M=2e5+10;
LL n,m,q,k,x,y,w,ans;
LL a[N],p[N],A[M],B[M],W[M];
LL dis[N][N];
void dfs(LL pos,LL pre,LL sum){
if(sum>ans)
return ;
if(pos==k+1){
ans=min(ans,sum+dis[pre][n]);
return ;
}
dfs(pos+1,A[a[p[pos]]],sum+dis[pre][B[a[p[pos]]]]+W[a[p[pos]]]);
dfs(pos+1,B[a[p[pos]]],sum+dis[pre][A[a[p[pos]]]]+W[a[p[pos]]]);
}
void Floyd(){
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j)
continue;
dis[i][j]=1e18;
}
}
for(int i=1;i<=m;i++){
cin>>x>>y>>w;
dis[x][y]=min(dis[x][y],w);
dis[y][x]=min(dis[y][x],w);
A[i]=x,B[i]=y,W[i]=w;
}
Floyd();
cin>>q;
while(q--){
ans=1e18;
cin>>k;
for(int i=1;i<=k;i++){
cin>>a[i];
p[i]=i;
}
while(1){
dfs(1,1,0);
if(!next_permutation(p+1,p+k+1))
break;
}
cout<<ans<<endl;
}
return 0;
}
F.Gather Coins(思维)
题意:
有一个网格,网格中有 H H H行和 W W W列。 ( i , j ) (i,j) (i,j)表示从上往下数第 i i i行,从左往上数第 j j j列的单元格。
在这个网格中有 N N N枚硬币,通过 ( R _ i , C _ i ) (R\_i,C\_i) (R_i,C_i)单元可以拾取 i i i枚硬币。
你的目标是从 ( 1 , 1 ) (1,1) (1,1)单元格开始,反复向下或向右移动一个单元格,到达 ( H , W ) (H,W) (H,W)单元格,同时尽可能多地拾取硬币。
请找出能拾取的最大硬币数以及能达到最大值的路径之一。
分析:
由于题目限制只能向右或向下移动,所以一个格子只能从它左边或上面的格子转移而来。总格子数很多,但有硬币的格子不多,可以只考虑有硬币之间的格子的转移,最后对右下角特殊考虑即可。把硬币的坐标以行数为第一关键字,列数为第二关键字进行排序,那么一个格子就可以从之前已经扫描过的,列数小于等于它的格子转移而来,可以使用树状数组维护这一点。为了输出路径,树状数组要存储的是格子编号而非具体数量。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=2e5 + 5;
const int MOD=1000000007;
int n,m,k,t[N], pre[N], num[N];
pair<int, int> a[N];
void out(int x) {
if (!x) return;
out(pre[x]);
for(int i=a[pre[x]].first;i<a[x].first;++i) cout<<"D";
for(int i=a[pre[x]].second;i<a[x].second;++i) cout<<"R";
return ;
}
void update(int p,int id) {
for(;p<=m;p+=p & -p)
if(num[id]>num[t[p]])
t[p]=id;
return;
}
int query(int p) {
int res=0,id=0;
for(; p; p -= p & -p)
if(num[t[p]]>res) {
res=num[t[p]];
id=t[p];
}
return id;
}
void solve(){
cin>>n>>m>>k;
for(int i=1;i<=k;++i)
cin>>a[i].first>>a[i].second;
sort(a+1,a+1+k);
a[0]=make_pair(1,1);
a[k+1]=make_pair(n,m);
for (int i=1;i<=k;++i) {
pre[i]=query(a[i].second);
num[i]=num[pre[i]]+1;
update(a[i].second, i);
}
pre[k+1]=query(m);
num[k+1]=num[pre[k+1]];
cout<<num[k+1]<<endl;
out(k+1);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。