CSP-J复赛模拟赛后补题报告Day4

日期:2023年10月5日星期四

S10698

1.比赛概况

比赛共有4题,满分400分,赛时20分,其中第一题爆零,第二题20分,第三题爆零,第四题爆零。

2.比赛过程

刚开始的时候,鼠标和键盘等设备都出现了问题,举手示意老师,老师调整了一番之后,把我的电脑关机重启了,一共花费了15min,我因此获得了15min的补赛时间。设备正常后,我先浏览了一遍题目,感觉第一题最简单,就先做的第一题。做第一题的时候,看出来了是一个大模拟,就写了一段时间的代码,但在读题的时候出现了问题,以为是出现一个数字后,只把数字前面的所有字符按照数字重复输出(题目要求的是,每次重复是当前数字之前的所有字符,而不是只重复单个字符或部分字符),这个题就没有思路了,花了一个半小时的时间,就开始做的第二题。做第二题的时候用了一个前缀和,以为自己做对了,样例也对了,但是最后只对了20%的样例,剩下的样例都超时了。做第三题的时候,根据20%的数据范围进行了骗分,感觉自己在20%的数据范围内是正确的,可是最后依然没分。做第四题的时候时间不够了,只根据数据骗分,最后也是没分。

3.题解报告

(1)第一题:复读机(repeater)

情况:赛中0分,已补题。
题意: 

赛时本题做题想法:当时读题的时候不仔细,以为是对数字前面的部分字符进行重复输出(代码也是根据这个写的),直到测试样例的时候才发现了自己读题的错误,题目要求的是,每次重复是当前数字之前的所有字符,而不是只重复单个字符或部分字符,然后就没有思路,开始做下一题了。

题解: 可以开一个答案字符串,在每次出现数字的时候,每次都后面拼接本身,当然拼接前需要保存一下自己,然后每次都拼接保存的本身。如果发现字符是一个数字,就将它转换为int类型,而且要注意,数字位数并不一定为一位。

AC代码:

#include <iostream>
#include <string>
#include <cstdio>
using namespace std;
int t,n,tmp,pos;
string a,ans,c;
int main() 
{
	scanf("%d",&t);
	while(t--) 
	{
		scanf("%d",&n);
		cin>>a;
		a=" "+a,ans ="";
		for(int i=1;i<=n;++i) 
		{
			if(a[i]>='a'&&a[i]<='z') 
			{
				ans+=a[i];
			}
			else 
			{
				tmp=0,pos=i;
				while(a[pos]>='0'&&a[pos]<='9') 
				{
					tmp*=10;
					tmp+=a[pos]-'0';
					pos++;
				}
				i=pos-1;
				c=ans,tmp--;
				while(tmp--)
				{
					ans+=c;
				}
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}

(2)第二题:小可的矛与盾(spearshield)

情况:赛中20分,已补题。
题意: 

赛时本题做题想法:做这道题的时候,运用了前缀和,以为能AC了,结果只通过了两组样例,后面的样例都超时了。

题解: 提前用前缀和打表求某一段的攻击力、某一段的防御力.假设一开始分界线在位置 0(即所有人都在第二阵营),然后逐渐移动分界线,维护双方战斗力的变化。

AC代码:

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
const int N=1e5+10;
long long v[N],w[N];
long long minn=0x3f3f3f3f,sum1,sum2;
int n;
string s;
int main()
{
    scanf("%d",&n);
	cin>>s;
	s=' '+s;
    for(int i=1;i<=n+1;i++)
	{
		w[i]=w[i-1];
		v[i]=v[i-1];
		if(s[i]=='0')
		{
			w[i]=w[i-1]+i;
		}
		else
		{
			v[i]=v[i-1]+i;
		}
	}
	for(int i=0;i<=n+1;i++)
	{
		sum1=w[i]-w[0];
		sum2=v[n]-v[i];
		if(abs(sum1-sum2)<minn)
		{
			minn=abs(sum1-sum2);
		}
	}
	printf("%lld",minn);
    return 0;
}

(3)第三题:不合法字符串(illegality)

情况:赛中0分,已补题。   

题意: 

赛时本题做题想法:当时根据20%的数据进行了骗分,最后也没过。

题解:


AC代码:

#include<bits/stdc++.h>
using namespace std;
int T,n,m;
string str[100000],tgt;
int main() 
{
	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++) 
		{
			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]) 
				{
					tgt[i]='*';
				}
			}
		}
		for(int i=1;i<=n;i++)
		{
			cout<<tgt[i];
		}
		cout<<endl;
	}
	return 0;
} 

(4)第四题:虚假的珂朵莉树(kodori)

情况:赛中,已补题。

题意:

赛时本题做题想法:做这个题的时候时间已经不够了,所以就根据了题目样例进行骗分,最后也没有得到分。

题解:首先,可以发现,我们只需要查询一次,并且每个操作之间无关,那么我们可以在做完所有操作后再进行计算。其次,若是对同一个点进行多次操作一,可以将这多次操作一合成一次操作一,操作一的权值为多次操作一的权值和,而由其他结点传递过来的操作一也可以和当前结点的操作一进行合并。操作二同理。用两个桶标记数组即可。
对于操作一,可以发现,深度最大的结点不会受到对其他结点进行操作一的影响,那么深度最大的结点操作完后,深度次大的结点就不会再次受到影响。依此类推,我们可以根据结点深度从大到小进行操作一,这样只需要进行一次遍历即可完成所有的操作一操作。
对于操作二,类似于操作一,可以根据结点深度从小到大进行操作。
因此,我们只需要先dfs或bfs处理出各个结点的深度,然后将操作进行记录,在up和down数组中记录操作一和操作二累积的权值,再根据操作按深度从小到大或从大到小进行传递,最后将up、down数组与原本的权值数组进行求和输出即可。
1. 打表每次操作的up增加值、down增加值,并不更新其他相邻的节点。
2. 对于操作一,从深度最大的节点开始往上更新up值。
3. 对于操作二,从深度最小的节点开始往下更新down值。

AC代码:

#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() {
	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;
}

4.总结

①读题要仔细。

②时间分配要更加合理。

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值