2023年第五届河南省CCPC大学生程序设计竞赛

Problem A. 小水獭游河南

小水獭来到河南旅游,它认为一个字符串 s 是 HENAN 的当且仅当存在两个非. 空. 字符串 a 和 b 满 足如下三个条件:

• a 由小写字母组成,且 a 中每种字母只出现了一次。

• b 由小写字母组成,且 b 是回文串,也就是说将 b 翻转后得到的字符串和 b 相同。

• 将 a 和 b 顺序拼接得到的字符串和 s 相同,也就是说 s = a + b。

第一行包含一个整数 T(1 ≤ T ≤ 103),表示数据组数。 对于每组数据: 一行包含一个由小写字母组成的字符串 s(1 ≤ |s| ≤ 105),表示小水獭询问的字符串。 保证所有数据的 P|s| ≤ 105。 输出格式 对于每组数据: 输出一行包含一个字符串。如果 s 是 HENAN 的,输出 HE;否则输出 NaN。

输入输出

3

henan

hhnan

ysmihoyocom

HE

NaN

NaN

题解:(官方题解如下)

 先遍历所有小写字母个数,如果小写字母大于2,再判断之后是否是回文串,不是直接输出"NaN",是回文串输出"HE",同时a和b均不为空字符串,整个字符串长度不能为1,(此时a为空),b不需特判(比如abc字符串,b="c",a="ab")

当时不是这样写的,特判了样例naan,abc,a,naanna

#include<bits/stdc++.h>
using namespace std;
#define int long long
int book[110000]={0};
int check(string ss,int a)
{
	int i=a,j=ss.size()-1;
	while(i<j)
	{
		if(ss[i]!=ss[j])
		return 0;
		else
		{
			i++;
			j--;
		}
	}
	return 1;
}
signed main()
{
	string s;
	int n,t;
	cin>>t;
	while(t--)
	{
		memset(book,0,sizeof book);
		cin>>s;
		n=s.size();
		if(n==1)
		{
			cout<<"NaN\n";
		}
		else
		{
			int f=0;
			for(int i=1;i<=26;i++)
			{
				int x=s[i-1]-'a';//字母对应的ASII码
				book[x]++;
				if(book[x]>=2)
				{
					break;
				}
				if(check(s,i))
				{
					cout<<"HE\n";
					f=1;
					break;
				}
			}
			if(!f)
			cout<<"NaN\n";									
		}	
	}
	return 0;
}

 Problem F. Art for Last

给定一个长度为 n 的非负整数序列 A1, A2, . . . , An,其中 n ≥ 2。 给定正整数 k,满足 2 ≤ k ≤ n。要求从 A1, A2, . . . , An 中选择 k 项 Ap1 , Ap2 , . . . , Apk (1 ≤ p1 < p2 < · · · < pk ≤ n),使得下式取得最小值: min 1≤i

第一行包含两个正整数 n,k(2 ≤ k ≤ n ≤ 5 × 105),表示非负整数序列 A 的长度及选取的项数。 第二行包含 n 个非负整数 A1, . . . , An(0 ≤ Ai ≤ 109),表示给定的序列 A。

输入输出

13 7

1 1 4 5 1 4 1 9 1 9 8 1 0

0

4 2

114 514 1919 810

87616

6 3

121 117 114 105 107 111

12

题解:本题结果的最大值为2e18,显然不能用暴力解,但思路是对的,先排序,相邻的k个数中,最小值是相邻两个数的差,最大值为最后一个数-第一个数,要使他们乘积最小,最大值也必须最小,可以用rmq求相邻两个数的差的最小值,(rmq就是为了解决数组下标某一区间内的最值),用数组a[i+k-1]-a[i],求k个数中最后一个数-第一个数,结果找乘积的最小值。

#include <bits/stdc++.h>//rmq时间复杂度O(nlogn)。
#include<string.h>
#define int long long
using namespace std;
const int N=5e5+10;
int LogN=25,n,k;
int a[N],log1[N],f[N][25];
void init()
{
 	log1[0]=-1;
 	for(int i=1;i<N;i++)
 	log1[i]=log1[i/2]+1;	
	int i,j;
	for(int i=1;i<n;i++)//根据题意定义f二维数组
	f[i][0]=a[i+1]-a[i];
	for(j=1;j<=log1[n];j++)
	for(i=1;i+(1<<j)-1<=n;i++)
	{
		f[i][j]=min(f[i][j-1],f[i+(1<<j-1)][j-1]);
	}
}
int rmq(int x,int y)//求数组中下标从x到y内数值的最值
{
	int s=log1[y-x+1];
	return min(f[x][s],f[y-(1<<s)+1][s]);
}
signed main()
{
  ios::sync_with_stdio(0);
  cin.tie(0);
  cout.tie(0);
  int mx=2e18;
  cin>>n>>k;  
  for(int i=1;i<=n;i++)
  cin>>a[i];
  sort(a+1,a+n+1); 
  init();
  for(int i=k;i<=n;i++)
  {
  	int p=rmq(i-k+1,i-1)*(a[i]-a[i-k+1]);//第一个下标i-k+1,最后一个下标i,rmq是从第一个到i-1个最小的f[j][0],(f[j)[0]是相邻两个数的差值,即rmq是从第一个数到i-1个数相邻两个数最小的差值
  	mx=min(mx,p);
  }
  cout<<mx;
  return 0;
}

 rmq模板

int LogN=25,n;
int a[N],log1[N],f[N][25];
void init()
{
 	log1[0]=-1;
 	for(int i=1;i<N;i++)
 	log1[i]=log1[i/2]+1;	
	int i,j;
	//for(int i=1;i<n;i++)//根据题意定义f二维数组
	//f[i][0]=a[i+1]-a[i];
	for(j=1;j<=log1[n];j++)
	for(i=1;i+(1<<j)-1<=n;i++)
	{
		f[i][j]=min(f[i][j-1],f[i+(1<<j-1)][j-1]);
	}
}
int rmq(int x,int y)//求数组中下标从x到y内数值的最值
{
	int s=log1[y-x+1];
	return min(f[x][s],f[y-(1<<s)+1][s]);
}

Problem H. Travel Begins

给定正整数 n, k,我们称实数列 a1, a2, . . . , ak 为关于 n 的 k–实数划分当且仅当 {ai} 满足下列条件: • Pk i=1 ai = n • ∀1 ≤ i ≤ k : 0 ≤ ai ≤ n 记 n 的所有 k–实数划分构成的集合为 Dk(n)。 对于实数 x,我们定义 [x] 为不大于 x 的最大整数,并定义 x 的小数部分为 {x} = x − [x]。定义四 舍五入函数如下: r(x) =    [x] , {x} < 0.5 [x] + 1 , {x} ≥ 0.5 试求下述两式的值: min a∈Dk(n) (X k i=1 r(ai) ) , max a∈Dk(n) (X k i=1 r(ai) )

第一行包含一个整数 T(1 ≤ T ≤ 105),表示数据组数。 对于每组数据: 一行包含两个正整数 n, k(1 ≤ n, k ≤ 109)。

输入输出

5

1 3

4 1

2 2

3 7

4 3

0 2

4 4

2 3

0 6

3 5

 题解:这个题当时比赛的时候,看队友在写,我没看懂题,直接放弃了,比赛完自己又写了一遍,我看有别人评价这个题找规律,我试着找了一下,竟然给找出来了,写了两次就过了,第一次是因为小数开少了,g,第一次做出了题有点难受竟然,

如果y=1,min=max=x,是特例

其余情况

1.最小值   

让y-1个数均为0.499999999999999(double可以表示到小数点后15位),用x-(y-1)*0.499999999999999就是最后一个数的值,因为前y-1个数小数部分<0.5,r(a[i])的和为0,最后一位(int)(w+0.5)就是最小值

2.最大值

让y-1个数都为0.5,x-(y-1)*0.5就是最后一个数的值,前y-1个数小数部分=0.5,r(a[i])的和为y-1,最后一位(int)(w+0.5),结果为二者相加,就是最大值

#include <bits/stdc++.h>
#include<string.h>
#define int long long
using namespace std;
const int N=5e5+10;
signed main()
{
  ios::sync_with_stdio(0);
  cin.tie(0);
  cout.tie(0);
  int t,n;
  cin>>t;
  while(t--)
  {
  	int mn,mx;
  	double x,y;
  	cin>>x>>y;
  	if(y==1)
  	cout<<x<<" "<<x<<"\n";
  	else
  	{
	  	double e=x-(y-1)*0.499999999999999;
	  	if(e<0.5)
	  	mn=0;
	  	else
	  	mn=(int)(e+0.5);
	  	double r=x*1.0/0.5;
	  	if(r<y)
	  	mx=r;
	  	else
	  	{
		  	double u=y-1;
		  	double w=x-0.5*(y-1);
		  	mx=u+(int )(w+0.5);
		  }
		  cout<<mn<<" "<<mx<<"\n";
	  }
  }
  return 0;
}

注意long double 与double区别:

1.long double在不同的编译器里面可以是8,10,16字节,double只能是8字节,long double字节大,运行速度慢

2.long double可以表示更大的精度

3.long double 可以表示更多的浮点数

Problem C. Toxel 与随机数生成器 

这个题很水,

就是说字符串不够随机,只需判断前100个字符重复的次数,小于100,就是随机的,反之不随机,代码很短 

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector> 
using namespace std;
 
int main()
{
	string s; cin >> s;
	// 记录重复数量 
	int cnt = 0;
	string tar = s.substr(0, 100);
	for(int i = 0; i + 100 < s.size(); i ++)
	{
		if(tar == s.substr(i, 100)){
			cnt ++;
			i += 100;
		}
	}
	if(cnt < 2) cout << "Yes\n";//这个题给的太小了,2都能过,也可以开大点
	else cout << "No\n";
}

Problem B. Art for Rest 

题意:将序列分为floor(n/k)段,每一段都升序排序,得到的新的序列必须整体都是升序排列求这样的k的个数

思路:左边前缀的最大值<右边后缀的最小值,判断每一个下标的这种情况

 

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector> 
 
#define int long long
using namespace std;
const int N=1e6+10;
int a[N],dp[N],f[N];
signed main()
{
  ios::sync_with_stdio(0);
  cin.tie(0);
  cout.tie(0);
	int n,l=0;
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>a[i];
	dp[1]=a[1],f[n]=a[n];
	for(int i=2;i<=n;i++)
	dp[i]=max(a[i],dp[i-1]);//前半段最大值
	for(int i=n-1;i>=1;i--)
	f[i]=min(f[i+1],a[i]);//后半段最小值
	for(int i=1;i<=n;i++)
	{
		int g=0;
		for(int j=i+1;j<=n;j+=i)
		{
			if(dp[j-1]>f[j])
			{
				g=1;
				break;
			}
		}
		if(g==0)
		l++;
	}
	cout<<l;
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值