题目大意:一条水平的路,从0开始出发,有n个加油站,我们走到x在回到0点,请问油箱的最大容量是多少。只有在加油站才可以加油。
思路:可以看到路被划分成若干段,每一段之间是不能加油的,所以要找出每段差值的最大值,另外,x到最后一个加油站之间的路应该被算两次。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,x;
scanf("%d%d",&n,&x);
int mx=0,c=0,d;
for(int i=1;i<=n;i++)
{
scanf("%d",&d);
mx=max(mx,d-c);
c=d;
}
if(d<x) mx=max(mx,2*x-2*d);
printf("%d\n",mx);
}
}
题目大意:我们有一个芯片和一个数轴,芯片最开始放在位置1,可以对它进行两种操作:
1.前进一格
2.跳到任意位置(包括本身位置)
我们它每次覆盖过的位置都会加1,我们已经得到了结果序列,现在要求最少进行多少次操作2可以得到结果序列。
输入:
4
4
1 2 2 1
5
1 0 1 0 1
5
5 4 3 2 1
1
12
输出:
1
2
4
11
思路:我最开始想的是一遍扫过去,所有的位置都减1,然后去找两个0之间的数,两个0之间的数的2操作等于这段区间的最大值,但是这样有很明显的bug,两0之间的数如果是4 3 2 1 2或者是4 3 2 2 3,那么操作将变得很麻烦。转变思路,为什么要跳,因为前一个数小于后一个数,从前往后移的时候前一个数凑齐了,后一个数就不够,那么就得在后一个数的位置跳d=a[i]-a[i-1]次,假设只有3,5两个数,我们先不管3是怎么凑齐的,但是我们可以知道,3凑齐的时候,5也必然为3,那么现在需要在5的位置跳5-3=2次。现在就考虑3怎么凑齐的,是由上一个转移来的,但是上衣给没有,所以实际上就考虑除了刚开始放在这儿产生的1次外,还需要跳3-1=2次。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int a[200010];
signed main()
{
int t;
scanf("%lld",&t);
while(t--)
{
int n;
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
}
int ans=a[1]-1;
for(int i=2;i<=n;i++)
{
if(a[i]>a[i-1])
ans+=a[i]-a[i-1];
}
printf("%lld\n",ans);
}
}
题目大意:给定n个元素,每次选一个数x,对于每个ai执行以下操作:(ai+x)/2下取整,问最少操作多少次可以使所有的ai都相等,另外,如果操作次数小于x则打印每次选的数x,否则输出最小次数即可。
思路:仔细想想这个题,我们假定有ai和aj两个元素,且ai<aj,那么(ai+aj)/2不就相当于取了中点,一直这样操作可以一点一点逼近ai,也就是二分更新的思想。所以为了使操作次数最少,我们每次都将x设定为原数组中最小的那个数。所以最重要的是要弄清楚这个操作的实际含义是什么。
#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[200010];
signed main()
{
int t;
scanf("%lld",&t);
while(t--)
{
int n;
scanf("%lld",&n);
int mi=1e9+7;
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
mi=min(mi,a[i]);
}
int mx=0;
for(int i=1;i<=n;i++)
{
int k=0;
while(a[i]!=mi)
{
k++;
a[i]=(a[i]+mi)/2;
}
mx=max(mx,k);
}
if(mx<=n)
{
printf("%lld\n",mx);
for(int i=1;i<=mx;i++) printf("%lld ",mi);
if(mx) printf("\n");
}
else printf("%lld\n",mx);
}
}
题目大意:一个攻击是这么定义的:选中一个角色,对它进行强度为x的攻击,然后攻击可以扩散开,不过每次只能左右二选一进行扩散,每次扩散的强度是递减的,问最优策略的最坏情况是初始攻击为多少
input
6 2 1 5 6 4 3output
8input
5 4 4 4 4 4output
8input
2 1 1000000000output
1000000000
思路:很显然,对于每个点来说,从开头或者结尾到它是最长的,但是我们要找所有策略中最坏情况的最少次数,翻译成人话就是,每选一个位置都是一种策略,在这种策略中,将这种策略的最大攻击视为这种策略的攻击值,然后在所有策略中找到最优的那个。
我们任意挑一个位置i,可以发现i前面的要想使次数最多要先往后延伸,然后到终点了再往前延伸,那么对初值的要求就是不小于a[j]+n-j;i后面的显然就是要从i开始,先往前延伸,延伸到头了再往后延伸,那么对初值的要求就是不小于a[j]+j-1,i处的初值就是a[i],我们要在这种策略的每一个中找一个最大的,然后作为选i的次数。从每一个数的次数中选出最小的。
因为我们可以定初始选哪个以及攻击的初值,但是每次会往哪边延伸我们不知道,所以就要讨论出最坏的情况。
n很大,肯定需要进行预处理,那么我们该如何进行预处理呢。我们仔细观察每个值,需要用到的就是它前面的最大的(前缀),它后面最大的(后缀),以及它本身的值。这里之所以提到前后缀是因为前后缀是可以进行状态累计的,那么我们只要开三个数组来存一下每个i的这些值,就得到了选这个i时的最少次数。
预处理的时候,对于每个值,记录从开头到它和从结尾到它的次数,前缀后缀计算就在统计完后进行正逆两种顺序的访问,然后进行更新即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[300010],pr[300010],la[300010];
signed main()
{
int n;
scanf("%lld",&n);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
int mx1=0,mx2=0;
for(int i=1;i<=n;i++)
pr[i]=a[i]+i-1,la[i]=a[i]+n-i;
//pr[i]表示从i到n,区间内的这些全都到开头后再转移的最大值
//i后面的要从前面来
int c=0;
for(int i=n;i>=1;i--)
c=max(c,pr[i]),pr[i]=c;//要统计的是i前面的
//la[i]表示从1到i,区间内的这些全都到结尾后再转移的最大值
c=0;
for(int i=1;i<=n;i++)
c=max(c,la[i]),la[i]=c;//要统计的是i前面的
int ans=2e9+7;
for(int i=1;i<=n;i++)
{
ans = min(ans,max(a[i],max(pr[i+1],la[i-1])));//选i,看前后
}
printf("%lld\n",ans);
}
反思:遇到最大的问题在于看到题目时产生的畏难情绪,以及一旦认定思路尝试不通就不愿再换别的思路,以及思维上需要很大的提升。
复盘:
状况:这次比赛出现严重失误,而且不是这一场,上一场也出现严重失误,div2竟然只写出一题,而且是在第二题不是很难的情况下,而且一碰到难题,第一反应是退缩。
深层剖析:最近一直在abc中写题,题目在自己能力范围之内而且没有拔高,试图通过更快的出AB来提升分数,但是最近写的题难点在于细节的处理而非思维,最近几场比赛被卡住的是思维。所以问题在于思维的提升以及面对难题逼迫自己去想的态度。
前段时间上分的分析:那时候出了比赛以外,写的题目难度都在1200-1400,同时基本都是自己写的,没有看题解,写完立即复盘思路,对思维有明显的锻炼效果。同时打完一场比赛后,也会以及写题解复盘思路。
后期练习:补题,一方面是赛后补题,一方面去补之前的比赛,div2三题,div3除了最后一题,div4ak。
询问他人:赛后补题,把自己卡壳的题补了,再去往后补一题,遇到新的知识点了就去学习,再找相应的题目练手