A
题意:定义一个填充块,填充块由 ‘o’, ‘g’ 交替出现组成,并且以 ‘o’ 开头,以 ‘o’ 结尾,形如:”ogo”, “ogogo”, “ogogogo”等,现在给出一个给定长度的字符串,需要将其中所有的填充块换为 ‘***’ 之后输出。
思路:问题的核心在于如何找出所有的填充块,我们可以发现,填充块可以转化成,以o开头与不少于一组go相连的连续字符串。只需要找到符合要求的字符串,然后尽可能向后扩展就可以了,将这部分缩为一组 ‘***’ 。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 105;
char a[maxn], b[maxn];
int main()
{
int lena, pa=0, pb=0; //pa用来遍历a字符串,pb用来往b字符串中添加
scanf("%d%s", &lena, a);
while(pa != lena){ //当遍历到结尾时结束
if(a[pa]=='o' && a[pa+1]=='g' && a[pa+2]=='o'){ //找出一个匹配的开头
b[pb] = b[pb+1] = b[pb+2] = '*'; //置*
pa += 3;
pb += 3;
while(a[pa]=='g' && a[pa+1]=='o'){ //找出之后匹配的循环节,直接跳过
pa += 2;
}
}
else b[pb++] = a[pa++]; //如果不是的话,直接添加到b字符串
}
b[pb] = '\0'; //标志字符串结尾
printf("%s\n", b);
return 0;
}
B
题意:有n*m大小的舞台,若干的演员在台上固定的位置,定义好的方向(x, y,d)满足在(x, y)位置上没有演员,并沿着d方向可以看到演员(可以根据样例理解)。例如1 0 1用1表示演员,则在(1, 2)这个位置可以从左右两个方向看到演员,所以好的方向的总数为2。现在给出舞台大小和演员位置,求好的方向的总数。
思路:这里给出一种模拟的解法。我们遍历所有位置,对有演员的位置进行check,chek思路为,从当前位置沿上下左右四个方向进行查找,遇到空的位置cnt++,遇到有演员或者边界则停止查找。这样的好处是,每一个位置最多被从上下左右四个方向查找到四次,为常数级别,那么复杂度就是O(n2) 可以通过。同时也可以通过前缀和的思想,或者枚举方向进行求解,方法不唯一。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1005;
int mp[maxn][maxn];
int n, m, cnt=0;
void check(int i, int j)
{
for(int x=i-1; x>=0; x--){ //向上查找
if(mp[x][j] == 0) cnt++; //空位置计数++
else break; //有演员则跳出
}
for(int x=i+1; x<n; x++){ //向下查找
if(mp[x][j] == 0) cnt++;
else break;
}
for(int y=j-1; y>=0; y--){ //向左查找
if(mp[i][y] == 0) cnt++;
else break;
}
for(int y=j+1; y<m; y++){ //向右查找
if(mp[i][y] == 0) cnt++;
else break;
}
}
int main()
{
cnt = 0;
scanf("%d%d", &n, &m);
for(int i=0; i<n; i++)
for(int j=0; j<m; j++)
scanf("%d", &mp[i][j]);
for(int i=0; i<n; i++)
for(int j=0; j<m; j++)
if(mp[i][j] == 1)
check(i, j);
printf("%d\n", cnt);
return 0;
}
C
题意:在一个坐标轴上需要从0点到s点(假设每个坐标点之间间距为1km),需要耗时不超过t。现在有n辆车,每辆车有两个属性,租金和油箱容量。开车有两种方式,1分钟 1千米 耗2单位油,2分钟 1千米 耗1单位油。而在(0, s)之间有若干加油站,每次可以无耗时无花费地把油加满。问从0到s点的最小花费,无解输出-1。
思路:本题可以转化为 → 找到一个最小的、满足题意的油箱容量 → 然后在满足容量的所有车中,找到花费最小的。这两步中第二步很好解决,一个遍历就完成了。关键问题就在第一部分,如何确定一个尽可能小的,满足题意的油箱容量。满足题意的值最小,我们首先可以想到的是二分搜索(之后的训练中会讲到,正确性不在此证明)。而二分搜索需要较快的判断一个值是否满足条件。而我们可以知道,车通过每一段间距的时间,只和间距的长度有关,和顺序和位置都无关。现在证明一下如何快速判断一个邮箱容量是否满足条件。
对于一个油箱容量g,对于所有的间距d ,如果d≤g2,则这段耗时为d2。如果g2<d≤g,这部分耗时为(g−d)∗1+(2∗d−g)∗2。如果d>g 则无法到达。这是我们队每一段独立的间距进行的分析,而我们不能遍历地去求每一段间距对应的时间,复杂度太高了。我们可以把所有d≤g2的总长度求出来,时间就是总长度的一半。而对于 k 段 g2<d≤g 的部分,我们求出总长度dk, 然后 这部分的耗时应该为(g∗k−dk)∗1+(2∗dk−g∗k)∗2 化简后为4∗dk−g∗k。
而我们将所有的间距进行排序之后,由于有序性,所有满足条件的部分一定是连续的。我们就可以用前缀和来维护区间和,那么之后的问题就是如何快速找到两种条件不同的分界的位置。用upper_bound() 有兴趣的可以下去自己查一下,这里不做讲解。
至此,所有问题解决。我们用二分容量,然后使用前缀和维护满足条件的间距和。快速判断是否满足条件。最后遍历找出最优解。需要注意的是,二分的时候需要判断是否会出现错误答案。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+5;
pair<int, int> car[maxn];
int p[maxn], dist[maxn], sum[maxn];
int n, k, s, t;
int found(int l, int r)
{
while(l<=r){
int mid = (l+r)/2;
int pk = upper_bound(dist, dist+k+1, mid/2)-dist; //前pk段1km/min
int sumt = sum[pk] + (3*(s-sum[pk]) - mid*(k+1-pk)); //邮箱容量为mid时, 所花费的总时间
if(sumt > t) l = mid+1;
else r = mid-1;
}
int pk = upper_bound(dist, dist+k+1, l/2)-dist; //答案检查, 防止异常结束
int sumt = sum[pk] + (3*(s-sum[pk]) - l*(k+1-pk));
if(sumt>t) return -1;
return l;
}
int main()
{
sum[0] = p[0] = 0;
scanf("%d%d%d%d", &n, &k, &s, &t);
for(int i=0; i<n; i++)
scanf("%d%d", &car[i].first, &car[i].second);
for(int i=1; i<=k; i++)
scanf("%d", &p[i]);
sort(p, p+k+1);
for(int i=1; i<=k; i++)
dist[i-1] = p[i]-p[i-1];
dist[k] = s-p[k];
sort(dist, dist+k+1);
for(int i=1; i<=k+1; i++) //处理前缀和
sum[i] = sum[i-1] + dist[i-1];
int mink = found(dist[k], 2*dist[k]+1);
int ans = INT_MAX;
for(int i=0; i<n; i++)
if(car[i].second >= mink) ans = min(ans, car[i].first);
if(ans==INT_MAX || mink==-1) puts("-1");
else printf("%d\n", ans);
return 0;
}
D
题意:已有n本书,已知买一本需要a元,两本b元,三本c元,问最少花多少钱,可以使已有的变为4的倍数。
思路:按照n模4的余数,分情况讨论,注意枚举所有情况。会爆int,记得开long long
#include<bits/stdc++.h>
using namespace std;
int main()
{
long long n, a, b, c;
scanf("%I64d%I64d%I64d%I64d", &n, &a, &b, &c);
if(n%4==0) puts("0");
else if(n%4==1) printf("%I64d\n", min(min(3*a, a+b), c));
else if(n%4==2) printf("%I64d\n", min(min(2*a, b), 2*c));
else if(n%4==3) printf("%I64d\n", min(min(a, b+c), 3*c));
return 0;
}
E
题意:给出一个n长度的序列,和m段连续子序列,可以从这些子序列中选择任意多个进行求和,问如何使和最大。
思路:贪心的想法是我们将所有和为正数的子序列加和,即是我们想要的答案,而求连续区间的和,可以用前缀和的思路。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 105;
int n, m, a[maxn], sum[maxn];
int main()
{
int ans = 0;
sum[0] = 0;
scanf("%d%d", &n, &m);
for(int i=1; i<=n; i++){
scanf("%d", &a[i]);
sum[i] = sum[i-1]+a[i];
}
for(int i=0; i<m; i++){
int l, r;
scanf("%d%d", &l, &r);
if(sum[r]-sum[l-1] > 0) ans+=sum[r]-sum[l-1];
}
printf("%d\n", ans);
return 0;
}
F
题意:需要构造一个长度为n的序列,对这个序列有m次查询,每次查询给定一个连续的区间,查询的结果mex表示的是,不在查询序列中的,最小的数。构造这个序列,是的查询到的mex的最小值,尽可能的大。
思路:这是一个构造类型的题。我们很容易想到,让一个查询序列中的所有数从0开始排并且不重复,可以使得查询的mex最大。而我么可以证明,当我们找到了最小区间长度d,那么我们就构造一个[0,d) 循环的数列,即可满足题意。因为对于最小的查询区间,他的mex的值最大即为d ,而对于所有长度大于等于d 的区间,在他们之中一定会存在一个包含[0,d) 中所有数的子序列。而他的mex值不会小于d
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int main()
{
int n, m, d=INT_MAX;
scanf("%d%d", &n, &m);
for(int i=0;i<m;i++){
int l, r;
scanf("%d%d", &l, &r);
d = min(d, r-l+1);
}
printf("%d\n", d);
for(int i=0; i<n; i++)
printf("%d%c", i%d, i==n-1?'\n':' ');
return 0;
}
G
题意:给定一个长度为n的字符串,从字母G开始,一次只能往左或往右跳正好k个单位长度,问是否能正好到达T。如果跳动的目标位置是#的话,则不能到达。
思路:从G到T还是从T到G只是相对位置,所以我们可以直接规定从下标小的位置跳到下标大的位置。for循环模拟跳动,到边界或者跳到#则输出不能,跳到目标位置则输出能。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int main()
{
int n, k, pt, pg, can=0;
char a[maxn];
scanf("%d%d", &n, &k);
scanf("%s", a);
for(int i=0; i<n; i++){
if(a[i]=='T') pt=i;
if(a[i]=='G') pg=i;
}
if(pt > pg) swap(pt, pg);
for(int i=pt; i<n; i+=k){
if(a[i] == '#') break;
if(i == pg) {
can = 1;
break;
}
}
if(can) puts("YES");
else puts("NO");
return 0;
}
H
题意:从n个人当中,选取n1个为一组,n2个为另外一组,求两组平均值的和最大是多少?
思路:贪心的想法,首先可以明确,这n1+n2 个人的和,一定是所有可能中最大的。而对于这两组,最有钱的人应该在人少的那一组,使得总体的平均值最大。证明如下 首先由题意可得平均值的和为
sum1n1+sum2n2=n2∗sum1+n1∗sum2n1∗n2
假设 n1<n2,从 n1 和 n2 中交换一人,使得交换后,sum1 减少了k, sum2 增加了k,即
sum1−kn1+sum2+kn2=n2∗sum1+n1∗sum2+(n1−n2)∗kn1∗n2
其中, n1<n2,所以平均值的和减小了。反证得,越有钱的人,应该在人少的那组,使得总体平均值最大。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5+5;
LL a[maxn];
int main()
{
int n, n1, n2;
LL sum1 = 0, sum2 = 0 ;
scanf("%d%d%d", &n, &n1, &n2);
for(int i=0; i<n; i++)
scanf("%I64d", &a[i]);
sort(a, a+n);
if(n1>n2) swap(n1, n2);
for(int i=0; i<n1; i++)
sum1 += a[n-1-i];
for(int i=0; i<n2; i++)
sum2 += a[n-1-n1-i];
printf("%f\n", (double)sum1/n1 + (double)sum2/n2);
return 0;
}
I
题意:输掉一场即为淘汰,已胜x场的人,只能和与自己胜场相差不超过1的人对战,问n个人最多有人赢几场
思路:斐波那契数列,求可满足的最大位置
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5+5;
LL a[maxn];
int main()
{
LL n;
scanf("%I64d", &n);
a[0] = 1;
a[1] = 2;
int p = 1;
while(++p){
a[p] = a[p-1] + a[p-2];
if(a[p]>n) break;
}
printf("%d\n", p-1);
return 0;
}