目录
A.周小美是学长大人
题目描述:
指定一字符串s,求出字符串中相同字符对的个数。
即对于 i, j(0<=i,j<n)有s[i]==s[j];
注意这里没有i!=j的限制,也就是说(1,1)也属于题目中的字符对,且(1,2),(2,1)是不同的字符对
解题思路:
这题简单,直接循环找相同的个数
#include <stdio.h>
#include <string.h>
int main()
{
char s[100005];
int n,cnt=0;
scanf("%d%s",&n,s);
for(int i=0;i<n;i++)
for(int j=i;j<n;j++)
if(s[i]==s[j])cnt++;
printf("%d",cnt);
return 0;
}
结果发现时间超限!!!
这个题目显然不能直接求,我们思考一下怎样直接算出字符对的个数:
假设我们已经知道某个字符在字符串的数量为m,则这个字符能够产生的字符对为m*m(i的m种取法*j的m中取法)。比如样例中的zxm666,字符对总数应该为1*1+1*1+1*1+3*3=12.
具体过程:
#include <stdio.h>
char s[100005];
long long sum, cnt[150], n;
int main()
{
scanf("%d%s", &n, s);
for (int i = 0; i < n; i++)
cnt[s[i]]++; //cnt数组记录每个字符出现的次数,通过ascii码映射
for (int i = 0; i < 150; i++) //范围随便取,只要能包括题目字符中的ascii码范围就行
sum += cnt[i] * cnt[i]; //公式
printf("%lld", sum);
return 0;
}
B.卷王日记
题目描述:
有两种开关,一种按一下会在灯泡亮时让灯泡熄灭,在灯泡灭时让灯泡打开;另一种无论灯泡为什么状态都会让灯泡熄灭,根据题意字符串中必定会出现X且灯泡初始状态为开。模拟两种灯泡的快关情况,判断最终灯泡是否还开
解题思路:
这原本是一个思维题,对于一个字符串,其实只需要考虑最后两位即可得出答案。
为啥呢?
我们考虑几种情况:如果最后一位为X,那灯必定熄的;如果最后一位是Q且倒数第二位是一个偶数,则灯也是熄的;其余情况均为亮。
很简单,直接上代码
#include <stdio.h>
int n;
char ch[1000005];
int main()
{
scanf("%d%s", &n, ch);
if (ch[n - 1] == 'X')//判断最后一位
printf("no\n");
else if (n > 1 && ch[n - 2] != 'X' && (ch[n - 2] - '0') % 2 == 0)//这时最后一位是Q,如果倒数第二位是偶数,那么灯必定是关的
printf("no\n");
else
printf("yes\n");
return 0;
}
没错,一个if条件句就可以解决。
这个题目其实直接模拟完全也是可以过的,不愧是签到题哈哈
//c语言代码这里就不放了
#include <cmath>
#include <iostream>
using namespace std;
int main()
{
int n, flag = 0, x = 1;
char ch;
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> ch;
if (isdigit(ch))//处理数字
x = ch - '0';
else //处理字母
{
if (ch == 'X')
flag = 0;
else
flag ^= x % 2; //只需要看奇偶就行啦
x = 1;
}
}
if (flag)
cout << "yes" << endl;
else
cout << "no" << endl;
return 0;
}
代码内容可以理解一下,大佬们可以来补充一下c语言代码
C.卷王日记(二)
题目描述:
简而言之就是求出到初始点曼哈顿距离最短的点的距离及其编号。
解题思路:
这个题目考察空间想象能力,认真想想会发现,这就是相当于把xy坐标轴向右平移了一下。平移后题目就转化成了求点到坐标原点的距离,我们只要对每一个空闲的位置求出距离,然后取最小值即可。
具体过程:
看代码!此代码为小玉同学提供!c++版
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
const int N = 1e4+100;
#define LL long long
int n,m;
char g[N][N];
int main()
{
scanf("%d%d", &n, &m);
if(!n || !m)
{
printf("0 0\n");
return;
}
for(int i=0; i<n; i++) scanf("%s", g[i]);
LL min_num = -1, min_distance = 2e9;
for(int i=0; i<n; i++)
{
for(int j=0; j<m; j++)
{
LL num = (LL)i*m + j + 1;
//printf("%d ", num);
if(g[i][j] == 'F')
{
LL now_distance;
if(j < m/2) now_distance = 2 + i + abs(j+1-m/2);
else now_distance = 2 + i + abs((m/2+1)-(j+1));
if(now_distance < min_distance)
{
min_distance = now_distance;
min_num = num;
}
//printf("%d ", now_distance);
}
}
//puts("");
}
if(min_num == -1) printf("0 0\n");
else printf("%lld %lld\n", min_distance, min_num);
return 0;
}
理解题目意思之后比较简单,只要求出最短距离的点即可。
鉴于c++代码可能看不明白,这里专门写了一个c语言代码帮助大家理解,主要是解题的思维。
#include <stdio.h>
int main()
{
int n, m, minx = 0x3f3f3f3f, flag = 0;
char ch[10005];
scanf("%d%d",&n,&m);
int len = m / 2;
for (int i = 1; i <= n; i++)
{
scanf("%s",ch+1); //在这里以字符串形式输入可以让读入效率更高
for (int j = 1; j <= m; j++)
{
if (ch[j] == 'F') //当位置空闲时进行计算
{
if (j > len && minx > i + j - len) //座位在图书馆右侧
{
minx = i + j - len;
flag = (i - 1) * n + j;
}
else if (j <= len && minx > i + len - j + 1) //座位在图书馆左侧
{
minx = i + len - j + 1;
flag = (i - 1) * n + j;
}
}
}
}
if (flag)
printf("%d %d\n",minx,flag);
else
printf("0 0\n");
return 0;
}
这个题目必须给大家道个歉(真的非常非常抱歉),首先是数据有问题,然后是时间超限的问题,这个题目会因为输入次数太多导致时间超限。
数据问题题目已经进行过重审,如果当初提交时是对的但是误判了的已经判为ac。
减少输入次数的方法:每行输入一个字符串代替每个位置输入字符
G.破碎的音符
题目描述:
有一个只有一行的拼图,每一次拼接都需要消耗特定数量的音符,问最少需要消耗多少音符。
这里注意拼图的最左边和最右边位置是固定的,但其他的拼块可以随意放在中间的任何位置。
解题思路:
首先需要用到贪心的思想:要想让总的音符消耗最少,需要每次取出两个音符消耗最少的拼块来拼接,类似于哈夫曼树。
本题主要难点在于怎样使拼图的两边位置不变,我们可以对每一个拼块做一个标记,其中左右两边的拼块标记为0,中间的拼块标记为1.
接下来是题目的关键,很容易理解:
标记为1的拼块可以相互随意拼接,标记为0的拼块可以与标记为1的拼块拼接,在拼图的最后一步进行0和0的拼接。注意,如果在过程中进行了0和0的拼接,其它拼块将不能继续拼接!
具体过程:
先将拼块做好标记,每次取出最小的两块拼块。之后需要分类讨论:如果两个拼块标记都为1,则形成新的标记为1的大拼块;如果一个拼块为0,一个拼块为1,则形成标记为0的大拼块(因为新的拼块变成了边界);如果两个拼块标记都为0,若此时只有两个拼块则完成最后拼接,若多于两个拼块则需取出第三小的拼块并重复上述过程。
我们直接上代码:
#include <iostream>
#include <queue>
#include <vector>
#define v first
#define w second
using namespace std;
typedef long long LL;
typedef pair<LL, bool> PLB;
priority_queue<PLB, vector<PLB>, greater<PLB>> q;//优先队列维护拼块结构
int n;
void solve()
{
cin >> n;
long long sum = 0;
q = priority_queue<PLB, vector<PLB>, greater<PLB>>();
for (int i = 1; i <= n; i++)
{
int x;
cin >> x;
q.push(make_pair(x, i != 1 && i != n));
}
while (q.size() > 2)//当拼块数大于二时取两个最小拼块
{
auto a = q.top();
q.pop();
auto b = q.top();
q.pop();
if (!(a.w || b.w))//分类讨论过程
{
auto c = q.top();
q.pop();
q.push(b);
b = c;
}
LL num = a.v + b.v;
sum += num;
q.push(make_pair(num, a.w && b.w));
}
auto a = q.top();
q.pop();
auto b = q.top();
q.pop();
sum += a.v + b.v;
cout << sum << endl;
return;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while (t--)
solve();
return 0;
}
鉴于c++代码可能看不明白,这里专门写了一个c语言代码帮助大家理解,主要是解题的思维。
#include <stdio.h>
long long a[10005];
int st[10005];
int main()
{
int t;
scanf("%d", &t);
a[0] = 1e17L;
while (t--)
{
// printf("1\n");
int n;
scanf("%d", &n);
long long sum = 0;
for (int i = 1; i <= n; i++)
{
scanf("%lld", &a[i]);
st[i] = i;
}
for (int k = 1; k < n - 1; k++)
{
long long min1 = 0, min2 = 0, min3 = 0;//用来去三个最小拼块
for (int i = 1; i <= n; i++)
{
if (st[i] != i)
continue;
//这样可以求三个最小拼块
if (a[min1] > a[i])
{
min3 = min2;
min2 = min1;
min1 = i;
}
else if (a[min2] > a[i])
{
min3 = min2;
min2 = i;
}
else if (a[min3] > a[i])
{
min3 = i;
}
}
//分类讨论
if (st[min1] == 1 && st[min2] == n || st[min1] == n && st[min2] == 1)
{
a[min1] += a[min3];
st[min3] = min1;
}
else
{
if (min2 == 1 || min2 == n)
{
int temp = min1;
min1 = min2;
min2 = temp;
}
a[min1] += a[min2];
st[min2] = min1;
}
sum += a[min1];
}
printf("%lld\n", sum + a[1] + a[n]);
}
return 0;
}
这个题目有一定难度,需要一定的思考
题目还有一个
我自己也没想到的点需要特别注意,感谢cxz同学的反馈:当有多个消耗量相同的拼块存在时,应当优先选择标记为0的拼块,即排序时应将标记为0的拼块排在前面(使用小根堆)。如3 3 3 3 四个拼块,如果先将标记为1的中间两个拼块结合,两个标记为0的拼块将无法继续结合,导致答案偏大。
F.一起去旅行
题目描述:
这个题目就有意思了,一般人看到会推出各种貌似正确的公式,然后利用公式看能否到达指定位置,对于所有这些,答案当然是否定的,方程无解!
这里简单解释一下为什么会无法通过规律推出,设前进距离为a,后退距离为b,到达s点需要前进x次,后退y次,会有公式a*x-b*y=s,这是一个二元方程,二元方程x与y可以取任意值,而且x,y必须为整数。
解题思路:
这个题目如果直接下手的话,由于不能连续后退,会非常棘手,需要讨论很多问题。
题目的突破口在于:
每次后退之前必有一次前进,也就是说完全可以把每一次后退和它的前一次前进联系为一个整体!这样题目就变成了每一次前进可以选择前进 x 或前进 x-y ,此时便没有了后退必须前进的约束条件。
完成转换后,我们发现还是不能用前进 x 和前进 x-y 推出能否到达一个指定的点,原因与前面相同,这是一个二元方程。也就是说这个题目不能做到在线证明某个点是否能够到达,对于这种问题,我们一般采用预处理打表的方式。
什么是打表:
打表就是把程序在线处理转化为预处理。简单来说,就是用一个数组存储所有能到达的点,当询问某个点是否能到达时只需要判断概述是否在数组当中即可。
如何打表:
一个位置m是否能够到达取决于m-x和m-(x-y)是否能够达到,只要其中有一个是可到达的点,那么m必定能到达,我们可以根据这个推出递推公式。
我们来看具体代码:
#include <stdbool.h>
#include <stdio.h>
bool st[10000005];//下标表示位置,当st[i]值为1时
int x, y, n;
int main()
{
scanf("%d%d%d", &n, &x, &y);
st[0] = 1;
for (int i = 0; i <= 10000000; i++)//打表打表打表打表
{
if (i >= x) st[i] |= st[i - x];
if (i >= x - y) st[i] |= st[i - x + y];
}
for (int i = 0; i < n; i++)
{
int s;
scanf("%d", &s);
if (st[s])printf("yes\n");//如果数组中存在,输出yes,否则输出no
else printf("no\n");
}
return 0;
}
代码只有这么长哦。
J.旅行者的涂色游戏
题目描述:
派蒙来和你解释
解题思路:
这个题目我直接放代码吧,防ak题,看看就好。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5+50;
int n, m;
int X[N], Y[N];
signed main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
char ch;
cin >> X[i] >> ch;
if(ch=='R')Y[i] = 0;
else Y[i] = 1;
}
X[0] = 0;
Y[0] = 2;
X[m + 1] = n + 1;
Y[m + 1] = 2;
if (m == 0) {
if (n % 2 == 1) cout << "Aether" << endl;
else cout << "Lumine" << endl;
return 0;
}
int res = 0;
for (int i = 0; i <= m; i++) {
if (Y[i] == 2 || Y[i + 1] == 2) res ^= (X[i + 1] - X[i] - 1);
else if (Y[i] == Y[i + 1]) res ^= 1;
else res ^= 0;
}
if (res == 0) cout << "Lumine" << endl;
else cout << "Aether" << endl;
return 0;
}
写了一晚上呜呜,希望能对大家有所帮助!
特别提示:不要用gets! 不要用gets! 字符串输入可以直接用 scanf("%s",ch).
暂时只能写到这里,对自己的题了解一点,以后有机会再更新其他题吧,祝大家新生赛都取得好成绩!