训练日记 | 2021.03.21 | 天梯赛选拔赛

本文分享了作者在算法竞赛中的解题经验,包括使用暴力、哈希和数学方法解决段位判断、最近公共祖先、回文串检查和排列操作等问题,强调了理解题意、优化算法和熟练运用技巧的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

选拔赛用的是 第十八届浙大城院程设竞赛

A - Genshin and KFC

直接输出

B - Codeforces

题意:判断段位有没有改变,如果有就输出“(原段位 to 现段位)”,否则输出“No”

这里有个关于怎么去记录和输出相应字符串的,当时打的时候懒得多想别的方法就直接莽夫行动用最低端的方法暴力无脑打了,借鉴一下大佬的方法↓↓

#include<bits/stdc++.h>
using namesapce std;
int main()
{
    int res[10] = {0, 1200, 1400, 1600, 1900, 2100, 2400, 3000};
    //因为等一下找的是>=的值,所以把下一级的下限作为比较标准
    char ch[10][50] = {"Newbie", "Pupil", "Specialist", "Expert", "Candidate Master", "Master", "Grandmaster", "Legendary Grandmaster"};
    scanf("%d %d", &a, &b);
	int index1 = lower_bound(res, res + 8, a) - res;
	int index2 = lower_bound(res, res + 8, b) - res;
	//返回第一个大于等于查找值的下标
	if(a != res[index1]) index1--;
	if(b != res[index2]) index2--;
	//如果返回的是等于的值,即恰好是这一级的下限,则不变,否则下标--,最后比较下标输出即可
	if(index1 == index2) 
		puts("No");
	else{
		printf("%s to %s\n", ch[index1], ch[index2]);
	}
	return 0;
}

D - LCA On N-ary Tree

题意:n叉数,按从上到下、从左往右的顺序,从1开始编号,求x、y的最近公共祖先。

思路:这是一道思维题。每个子节点son和父节点fa都满足“fa=(son+n-2)/n ”,因此可以从x、y开始依次向上找父节点,直到两者父节点相等为止

这题的标题误导性也太大了…看题的时候看到lca还乐了一下,想起苏妈妈给我们详细讲过,就开始想套板子做。结果,哈哈,板子我记不完全,只记得一部分,就开始自己自创(笑),当然,wa了。卡了很久在草稿纸上写,因为这题的每个点编号都很有规律,所以就开始考虑用数学方法做,尝试写通项,ac了。事后被告知就算用tarjan方法做也会超时,感谢,还好我板子没背出(bushi)。
problem d

#include<iostream>
using namespace std;
#define ll long long
const int maxn=1e9+10;
int t,n,a,b,x,y,k,ans;
int main()
{
    scanf("%d",&t);
    while(t--)
    {   
    	scanf("%d%d%d",&n,&a,&b);
        x=min(a,b);
        y=max(a,b);
    	if(n==1||x==y)  printf("%d\n",x);
        else if(x==1)  printf("1\n");
        else
        {
            while(x!=y)
            {
                if(x<y)  y=(y+n-2)/n;
                else   x=(x+n-2)/n;
            }
            printf("%d\n",x);
        }
	}
    return 0;
}

F - Palindrome

题意:删除任意两个字符,判断能否使新字符串变成回文串

思路:字符哈希,前后枚举,判断回文。①枚举删除的字符位置 ②用哈希处理前中后子串合成新字符串 ③判断是否是回文串–>正向哈希值==逆向哈希值?(如果相等即为回文串)哈希
给我好好理解一下啊喂!!

做这题的时候只有半小时了,读完题啥思路也没有,时间又快没了,就想干脆直接for循环里套for循环暴力枚举判断,emm不过最后还剩一点点没写完。后来结束才想起来可以用hash做,虽然我也不会(哈哈)。其实说到底还是对哈希的理解没有特别透彻,也还没有到可以投入实践的地步。
不少在回文串上做文章的题目都可以用哈希来做(eg.UVA - 11475 博客写过)

#include<bits/stdc++.h> 
using namespace std;
#define ll long long
#define ull unsigned long long
const int P=13;
int n,t;
char s[20005];
ull hashP[2005],hashR[2005];
ull bin[2005];
void makeBin()
{
	bin[0]=1;
	for(int i=1;i<=2000;i++)  bin[i]=bin[i-1]*P;
}
void makeHash()
{
	for(int i=1;i<=n;i++) hashP[i]=hashP[i-1]*P+s[i]-'a';
	for(int i=n;i>=1;i--) hashR[n+1-i]=hashR[n-i]*P+s[i]-'a';
}
ull GetHashP(int p1,int p2)  //p1 p2为删除字符位置 
{   //正向哈希值计算 
	ull t1=hashP[p1-1];  //前 
	ull t2=hashP[p2-1]-hashP[p1]*bin[p2-1-p1];  //中 
	ull t3=hashP[n]-hashP[p2]*(bin[n-p2]);  //后 
	return t1*bin[(p2-p1-1)+(n-p2)]+t2*bin[(n-p2)]+t3;
	//前部 * 中后部长度 + 中部 * 后部长度 + 后部  
}
ull GetHashR(int p1,int p2)
{   //逆向哈希值计算 (同理) 
	ull t1=hashR[p1-1];
	ull t2=hashR[p2-1]-hashR[p1]*bin[p2-1-p1];
	ull t3=hashR[n]-hashR[p2]*(bin[n-p2]);
	return t1*bin[(p2-p1-1)+(n-p2)]+t2*bin[(n-p2)]+t3;
}
int main()
{
	makeBin();   //bin数组赋值 
	scanf("%d",&t);
	while(t--)
	{
	 	cin>>n>>s+1;
	 	makeHash();  //正逆hash数组赋值 
	 	bool flag=false;
		for(int i=1;i<=n;i++)
		{
		 	for(int j=i+1;j<=n;j++)
			{   //枚举字符位置并判断 
		 	 	ull left=GetHashP(i,j);
		 	 	ull right=GetHashR(n+1-j,n+1-i);
		 	 	if(left==right)  //判断回文 
				{
				    flag=true;
				    break;
				} 
			}
			if(flag) break;
		}
		if(flag) printf("Yes\n");
		else printf("No\n");
	}
	return 0;
}

此外,这次还可以用dfs搜索来做,代码会短很多,但是比哈希稍微难理解一点(对我来说)。参考博客:dfs搜索的方法

#include<cstdio>
const int MAXN=2e3+10;
char a[MAXN];;
bool dfs(int l,int r,int num){
    if(l>=r)return 1;
    if(a[l]==a[r])return dfs(l+1,r-1,num);
    if(num>=2)return 0;
    return dfs(l+1,r,num+1)||dfs(l,r-1,num+1);
}
int main()
{
    int t,n;
    scanf("%d",&t);
    while(t--){
        scanf("%d%s",&n,a);
        if(dfs(0,n-1,0))printf("Yes\n");
        else printf("No\n");
    }
}

G - Permutation

题意:给出两个排列 a 和 b,可以进行其中一个操作
操作一:删去 a 的 1 个头部整数并在尾部添加 1 个任意整数
操作二:修改 1 个 ai 为任意整数
求最少多少次操作能使 a, b 相同

思路:移动的效率值一定>=1,而修改只能是1。又因为数组中的数字是单一的,所以可以利用下标位置找出移动效率最高(即次数最多)的那个。
次数 = 总长度 - 效率最高次数
参考博客:维护排列的偏移量贡献(里面代码开头注释部分有详细解释如何计算次数)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e6+5;
ll a[maxn],b[maxn];
int main()
{
	int x,n;
	while(cin>>n){
		long long  maxn=0;
		b[0]=0;
		for(int i=1;i<=n;i++){
			cin>>x;
			a[x]=i;
			b[i]=0;
		}
		for(int i=1;i<=n;i++){
			cin>>x;
			if(a[x]-i>=0){
				b[a[x]-i]++;
				maxn=max(b[a[x]-i],maxn);
			}
		}
		cout<<n-maxn<<endl;
	} 
	return 0;
}

J - K-clearing

题意:n个数中如果有等于k的则-1,最后所有得到的新数据再做同样的处理,直到不含等于k的值(最小减到0为止)

思路:排序后寻找从等于k开始的连续自然数的最大长度sum (eg.n=5 k=2 1,2,3,4,6 sum=3)。最后遍历统一减去sum(如果为负则赋值为0)。

一开始依然莽夫行动在while循环套for循环,每次循环-1,遍历处理做,一直超时。想了很久以后发现其实可以for循环一次性做完,因为如果是在-1操作后才得到等于k的值,逆向思维想就相当于在一开始要寻找等于k+1的数,以此类推,可以把a[ i ] - - 的操作等价成k + + 的操作,降低复杂度。
把数组排序一遍,一边遍历一边计数并更新k,最后统一减去sum,用max函数更新数组的每个元素(避免小于0的情况)

注:①题目要求行末不能有多余空格,所以最后一个数单独输出 ②牛客网在线编程里面如果max函数中的是long long类型的0,不能直接写0,要写0ll(#define ll long long),不然会报错: ) 【之前不知道 还特地写了max函数

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll maxn=1e6+3;
ll n,k,a[maxn],b[maxn],i;
int main()
{
    scanf("%lld%lld",&n,&k);
    for(i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        b[i]=a[i];
    }
    sort(a+1,a+1+n);
    ll sum=0;
    for(i=1;i<=n;i++)
    {
        if(a[i]==k)
        {
            sum++;
            k++;
        }
    }
	for(i=1;i<n;i++)
    {
        b[i]=max(0ll,b[i]-sum);
        printf("%lld ",b[i]);
    }
    b[n]=max(0,b[n]-sum);
    printf("%lld",b[n]);
    return 0;
}

也可以用set做,因为会涉及到排序,所以可以直接用自动去重且升序的set即可

/*
set<int>s;
通过s.insert添加元素
*/
int sum = 0;
for(int i = k; i <= 1000000000; i++) 
{
    if(s.count(i))
        sum++;
    else break;
    /*
    count函数统计容器中等于value值的元素个数
    因为如果能计数,说明一开始这些数肯定是连续的,所以一旦发生断层就终止计数
    */
}

L - Polygon

题意:给出n条边及每条边的长度,判断能否组成n边形。

思路:判断是否满足“最大边<其余边之和

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n,ans=0,maxx=0;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        int x;
        cin>>x;
        if(x>maxx)  maxx=x;
        ans+=x;
    }
    ans=ans-maxx;
    if(ans>maxx)  cout<<"Yes"<<endl;
    else  cout<<"No"<<endl;
    return 0;
}

解题感想:
①看懂了题解的题目再做一遍大概率不会马上过甚至还是过不了,所以懂了以后建议还是自己再梳理一遍思路手打一遍。
②一些自己不熟的函数或者stl里面一些用起来还不熟练的容器,建议平时有空的话有些合适的题目可以刻意去试试用,毕竟这玩意熟能生巧。
③一些有点熟悉的算法的板子还是要多记记,不求能全部手打,但是思路顺序步骤至少要能记住。还有一些算法适用的情况也要多总结,这个就要靠刷题刷上去了。
④常见的一些会拿来做文章的考点模型可以多留心一点,总结一些常用方法或者通用步骤思路,多熟悉熟悉。

碎碎念:虽然有名额,但是这次状态和运气成分太大而且还是吊车尾,and有预感大概率只是拿了张体验卡去体验自己有多废物(呜呜)。虽然力不从心,还是干巴爹吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值