题目分析
题目描述
人类的本质是复读机。
小可喜欢在群里复读别人说过的话,这种人通常被称为复读机。现在小可想模拟复读机过程,过程如下:
现在给定一个长度为 n 的仅包含小写字母和数字的字符串,字母表示需要复读的消息,数字表示要复读的次数。
例如:kdy3,表示将kdy复读3遍,输出为:kdykdykdy;
然而复读没有这么简单,小可想进行一个更复杂的复读模拟, 于是这个字符串中可能包含多个数字, 当多次出现数字时,例如 a5b2,我们从左到右解析这个字符串,a5 表示将 a 复读 5 遍,即原字符串变 为 aaaaab ,然后遇到数字 2 ,再将所有消息全部复读 2 遍,即 aaaaabaaaaab 。
输入描述
第一行输入一个数字 t,表示本题共有 t 组数据。
接下来 tt 行每行包含一个数字 n和一个字符串 s。
输出描述
输出共 t 行。每行一个字符串表示需要复读的内容。
思考过程
这题由于数据范围给力,所以们我可以直接进行模拟.
遍历整个字符串,对于遇到小写字母的情况,可以将字母加到一个临时的字符串中;遇到数字,先遍历完整个数字,再按倍数加入ans字符串中,最后输出字符串ans即可.
这道题要注意处理好临时的字符串和循环变量(我的思路不用考虑循环变量,但是解析的代码是用while来遍历提取数字.所以当使用while提取数字时,一定要妥善处理好外层循环变量和内层循环变量之间的关系)
期望得分
期望得分100,实际得分100,这是一道基础题,AC是应该的
完整代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll t,n,sum;
string s,c,ans;
int main()
{
freopen("repeater.in","r",stdin);
freopen("repeater.out","w",stdout);
scanf("%lld",&t);
while(t--)
{
scanf("%lld",&n);
cin>>s;
c.clear();
for(ll i=0;i<n;i++)
{
if(s[i]>='0'&&s[i]<='9')
{
sum*=10,sum+=(s[i]-'0');
if((s[i+1]>='a'&&s[i+1]<='z')||i+1==n)
{
ans.clear();
for(ll i=1;i<=sum;i++) ans+=c;
c=ans,sum=0;
}
}
if(s[i]>='a'&&s[i]<='z') c+=s[i];
}
cout<<ans<<endl;
}
return 0;
}
第二题-小可的矛与盾
题目描述
有n个小可战士站成一排,他们的编号从1到n,每个小可都有一个战斗力xi=i,但是小可们有不同的分工,有的充当队伍的矛,有的充当队伍的盾,矛的攻击力和盾的防御力和小可本身的战斗力相同。我们要将小可们分成两个阵营,编号 [1,pos] 为第一阵营,第一阵营中我们只考虑矛的攻击力总和w,编号[pos+1,n] 为第二阵营,第二阵营中我们只考虑盾的防御力总和v,请问对于所有的 0≤pos≤n+1 ,∣w−v∣ 的值最小,请问∣w−v∣ 最小为多少。
输入描述
第一行:输入一个正整数 n 。
第二行:输入一行长度为 n 的只含有 0 或 1 的字符串,如果字符串某一位是0,则表示小可充当队伍矛,否则为盾。
输出描述
输出一个整数答案。
思考过程
我们整体的思想可以采用模拟解决问题.但是考虑用前缀和来维护两个数组.根据所给的字符串,0为矛,1为盾,其战斗力/防御力为其输入顺序(即num[i]的攻击力/防御力为i).dp0用来存当i为pos时矛的总攻击力,dp1则是当i为pos时盾的总防御力.最后从0遍历到n(一定要从0开始,以防pos选择为0).在计算的过程中,我们可以使用前缀和计算的方式来计算矛与盾在i为pos点时分别的攻击力与防御力.注意,dp1为倒序,注意特殊计算.最后用ans取其中的最小值,随后输出即可.
期望得分
期望得分100,实际得分90,出现偏差的原因为:
1.没有考虑到特殊情况(pos点可以出现在0上面,这一点我没有想到).
2.没有考虑到用前缀和来实现代码,而是采用了更为复杂的动态规划与递推(虽然这点并不影响最终得分).
完整代码
注:第一份代码是修改之前的90分代码,采用动态规划与递推实现的.
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e5+20;
ll n,ans=N,dp[N],dp0[N],dp1[N];
string s;
bool num[N];
int main()
{
freopen("spearshield.in","r",stdin);
freopen("spearshield.out","w",stdout);
scanf("%lld",&n);
cin>>s;
memset(dp,0x3f,sizeof(dp));
for(ll i=1;i<=n;i++)
{
if(s[i-1]=='0') num[i]=false;
else if(s[i-1]=='1') num[i]=true;
}
for(ll i=1;i<=n;i++)
{
if(!num[i]) dp0[i]=dp0[i-1]+i;
else dp0[i]=dp0[i-1];
}
for(ll i=n;i>=1;i--)
{
if(num[i+1]) dp1[i]=dp1[i+1]+i+1;
else dp1[i]=dp1[i+1];
}
for(ll i=1;i<=n;i++)
dp[i]=min(dp[i-1],abs(dp0[i]-dp1[i]));
printf("%lld",dp[n]);
return 0;
}
注:第二分代码是用前缀和实现的AC,但是思路与上一份是一样的,只是实现方式不同.
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e5+20;
ll n,ans=N,dp0[N],dp1[N];
string s;
bool num[N];
int main()
{
//freopen("spearshield.in","r",stdin);
//freopen("spearshield.out","w",stdout);
scanf("%lld",&n);
cin>>s;
for(ll i=1;i<=n;i++)
{
if(s[i-1]=='0') num[i]=false;
else if(s[i-1]=='1') num[i]=true;
}
for(ll i=1;i<=n;i++)
{
dp0[i]=dp0[i-1],dp1[i]=dp1[i-1];
num[i]==0?(dp0[i]+=i):(dp1[i]+=i);
}
for(ll i=0;i<=n;i++)
ans=min(ans,abs(dp0[i]-abs(dp1[n]-dp1[i])));
printf("%lld",ans);
return 0;
}
第三题-不合法的字符串
题目描述
小可是一名小说审核员,他的工作是看小说,然后把小说中不合法字符串和谐掉。
现在给出若干个不合法的字符串 s[i],和一篇小说 str ,小可需要把 str 中的不合法字符串用 *
和谐掉。当然小可是一个很聪明的审核员,他会用最少的 *
和谐字符串。
比如:
有三个不合法字符串:abc
、ab
、a
。str=abcd
他会只和谐a
,使得str=*bcd
,这样小说中就没了不合法字符串。
请输出和谐之后的小说。
输入描述
第一行:输入一个整数 TT 表示测试用例数。
对于每组测试样例:
第一行:输入一个正整数 n ,表示不合法字符串的数量。
接下来n行:每行输入一个字符串,表示不合法字符串。
接下来一行:输入一个字符串,表示小说。
输出描述
输出和谐之后的小说。如果存在多种答案,输出任意一种即可。
思考过程
我们可以使用vector向量和string字符串进行组合使用.从"小说"的第一个字符开始遍历(在"小说"前后可以各加一个空格,既可以在遍历的时候更加整齐方便,又可以防止截取子串和遍历比较的时候越界,十分方便).通过循环遍历作为子串末尾来与需要改变的非法字符串"进行比较.并更改非法子串的末尾字符为*.最后输出从1到n的字符串(因为我们一开始在开头和结尾各加了两个空格,不能输出空格,所以从n开始输出).
期望得分
期望得分20,实际得分20,当n=1的情况成功通过,其余没有通过也合情合理,对于如何处理字符串等问题掌握得还是不够熟练.
完整代码
注:第一份代码是在模拟赛中专为n=1的情况写得特判代码,共得了20分.
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e5+20;
ll t,n;
string s[N],c,ch;
bool cmp(string a,string b)
{
return a.size()<b.size();
}
int main()
{
freopen("illegality.in","r",stdin);
freopen("illegality.out","w",stdout);
scanf("%lld",&t);
while(t--)
{
scanf("%lld",&n);
for(ll i=1;i<=n;i++) cin>>s[i];
cin>>c;
if(n==1)
{
for(ll i=0;i<c.size();i++)
{
ch.clear();
for(ll j=i;j<c.size();j++)
{
ch+=c[j];
if(ch==s[1])
{
c[i]='*';
break;
}
}
}
}
cout<<c<<endl;
}
return 0;
}
注:第二分代码是用暴力实现的AC,是第一份代码的拓展.
#include<bits/stdc++.h>
using namespace std;
int T,n,m;
string str[100000],tgt;
int main(){
//freopen("illegality.in","r",stdin);
//freopen("illegality.out","w",stdout);
cin>>T;
while(T--){
cin>>m;
for(int i=1;i<=m;i++)
cin>>str[i];
cin>>tgt;
n=tgt.length();
tgt=' '+tgt;
for(int i=1;i<=n;i++){//找哪个[i]可以改
for(int j=1;j<=m;j++){
if(i<str[j].length())
continue;
if(tgt.substr(i-str[j].length()+1,str[j].length())==str[j]){
//以[i]为结尾的单词
tgt[i]='*';
}
}
}
for(int i=1;i<=n;i++)
cout<<tgt[i];
cout<<endl;
}
return 0;
}
第四题-虚假的珂朵莉树
题目描述
我家门前有两棵树,,一棵是枣树,另一棵也是枣树。——鲁迅
小可有一棵树,有 n 个节点,根节点为 1, 每个节点都有一个权值。
设每个节点与根节点距离是这个节点的深度。
小可会在这棵树上增加 m 条虚假边,任意一条虚假边不会和原来的树边或其他虚假边重合(增加的虚假边不影响节点深度)。
之后小可会进行 q 次操作:
操作1:让结点 u 的权值增加 k ,并对与结点 u 相邻的结点中,深度比结点 u 小的结点重复操作1。
操作2:让结点 u 的权值增加 k ,并对与结点 u 相邻的结点中,深度比结点 u 大的结点重复操作2。
小可想知道,经过 q 次操作之后,所有的节点的权值是多少。
输入描述
第一行:输入三个整数 n、m、q,表示节点个数。虚假边条数,操作次数。
第二行:输入n个整数 ai,表示节点的权值。
接下来 n-1 行:每行输入两个整数 u、v,表示树结构。
接下来 m 行:每行输入两个整数 u、v,表示虚假边。
接下来 q 行:每行输入三个整数 t、u、k。t表示操作类型、u表示要操作的节点,k表示权值增加k。
输出描述
一行 n 个整数,表示树上每个结点的权值,由于权值可能很大,请将权值对 109+7109+7进行取模。
思考过程
注:思路摘抄于题目解析-STD4:
对于10分左右:建树 -> 求深度 -> 连辅助边 -> 爆搜求答案 -> 输出。每个点的权值影响都进行搜索,会时间超限。
虽然题目中说:操作1:“深度比结点 k 小的结点重复操作1。“但是可以把每次的权值影响先记忆下来,然后统一一次搜索即可。
100分:
首先,可以发现,我们只需要查询一次,并且每个操作之间无关,那么我们可以在做完所有操作后再进行计算。
其次,若是对同一个点进行多次操作一,可以将这多次操作一合成一次操作一,操作一的权值为多次操作一的权值和,而由其他结点传递过来的操作一也可以和当前结点的操作一进行合并。操作二同理。用两个桶标记数组即可。
对于操作一,可以发现,深度最大的结点不会受到对其他结点进行操作一的影响,那么深度最大的结点操作完后,深度次大的结点就不会再次受到影响。依此类推,我们可以根据结点深度从大到小进行操作一,这样只需要进行一次遍历即可完成所有的操作一操作。
对于操作二,类似于操作一,可以根据结点深度从小到大进行操作。
因此,我们只需要先dfs或bfs处理出各个结点的深度,然后将操作进行记录,在up和down数组中记录操作一和操作二累积的权值,再根据操作按深度从小到大或从大到小进行传递,最后将up、down数组与原本的权值数组进行求和输出即可。
1. 打表每次操作的up增加值、down增加值,并不更新其他相邻的节点。2. 对于操作一,从深度最大的节点开始往上更新up值。
3. 对于操作二,从深度最小的节点开始往下更新down值。
期望得分
期望得分0,实际得分,主要是对于这种题目确实没有思路.
完整代码
注:AC代码摘抄于题目解析-STD4:
#include<bits/stdc++.h>
#define pr pair<int,int>
#define mk make_pair
using namespace std;
const long long p=1e9+7;struct node{
int to,next;
}e[5000005];
vector<pr> g;
long long a[1000005],up[1000005],down[1000005];int n,m,q,cnt,head[1000005],d[1000005];
void add(int u,int v){
e[++cnt].to=v,e[cnt].next=head[u],head[u]=cnt;
}
void dfs(int u,int fa){
g.push_back(mk(d[u],u));
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(v==fa) continue;
d[v]=d[u]+1;
dfs(v,u);
}
}
int main(){
//freopen("kodori.in","r",stdin);
//freopen("kodori.out","w",stdout);
cin>>n>>m>>q;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n-1;i++){
int u,v;
cin>>u>>v;
add(u,v);
add(v,u);
}
dfs(1,1);
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
add(u,v);
add(v,u);
}
for(int i=1;i<=q;i++){
int pd,u,v;
cin>>pd>>u>>v;
if(pd==1) up[u]=(up[u]+v)%p;
else down[u]=(down[u]+v)%p;
}
sort(g.begin(),g.end());//按照深度从小到大排序,更新操作二增加的权值
for(int i=0;i<g.size();i++){
int x=g[i].second;
for(int j=head[x];j;j=e[j].next){
int y=e[j].to;
if(d[y]>d[x]) down[y]=(down[y]+down[x])%p;
}
}
reverse(g.begin(),g.end());//按照深度从大到小排序,更新操作一增加的权值
for(int i=0;i<g.size();i++){
int x=g[i].second;
for(int j=head[x];j;j=e[j].next){
int y=e[j].to;
if(d[y]<d[x]) up[y]=(up[y]+up[x])%p;
}
}
for(int i=1;i<=n;i++) cout<<(a[i]+up[i]+down[i])%p<<" ";
return 0;
}
考试分析
做题顺序与时间分配
这次对于时间的规划和做题熟练度已经比较平稳了,前两题分配到的时间算是比较合理,对于后两道题所分配到的时间也相对来说比较合理(虽然后期空闲的时间比较多,但是也足够用于检查了).所以对于时间分配这一块,这次考试的时间分配和做题顺序还算是比较合理.
考试心态
这次考试的发挥比较平稳,该拿的分都拿到了,该稳住的题也都稳住了.保持住这种做题状态与手感,稳住这种考试的心态,争取在正式考试中能有更好的发挥!