此次模测依旧是针对CSP的T1,T2,T4,但是完成的不太好,有待改进,1题没有想到最简单的思路,仅仅靠特判拿到了60分,2题先用简单方法搞定了123点,然后开始写常规方法,但是时间太紧了没有调出来,直接交了拿了40分,事后发现只是未将滑动窗口的l和r指针移动罢了QAQ太尴尬了。3题没啥思路。。就靠贴数据想要骗到30分,结果还因为语言的问题导致了CE,还是需要多多磨炼。
T1 HRZ的序列
题目描述
对于一个序列,是否存在一个数K,使得一些数加上K,一些数减去K,一些数不变,使得整个序列中所有的数相等,其中对于序列中的每个位置上的数字,至多只能执行一次加运算或减运算或是对该位置不进行任何操作。
思路
如果可以完成的话,那么序列中至多只有三种数:a-k,a,a+k,如果出现三种数情况之外的数那么一定不满足题意。所以只要先排序求出最小值与最大值,然后得到a,再遍历对每个数进行判断即可。
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
int t,n,flag;
long long a[10005];
int main()
{
scanf("%d",&t);
while(t--)
{
flag=0;
memset(a,0,sizeof(a));
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%lld",&a[i]);
sort(a,a+n);
long long mid=(a[0]+a[n-1])/2;
for(int i=0;i<n;i++)
{
if(flag==1) break;
if(a[i]!=a[0] && a[i]!=a[n-1] && a[i]!=mid)
{
flag=1;
printf("NO\n");
}
else continue;
}
if(flag==0) printf("YES\n");
}
return 0;
}
T2 HRZ学英语(滑动窗口)
题目描述
现在给定一个字符串,字符串中包括26个大写字母和特殊字符’?’,特殊字符’?‘可以代表任何一个大写字母。现在TT问你是否存在一个位置连续的且由26个大写字母组成的子串,在这个子串中每个字母出现且仅出现一次,如果存在,请输出从左侧算起的第一个出现的符合要求的子串,并且要求,如果有多组解同时符合位置最靠左,则输出字典序最小的那个解!
数据范围
思路
首先看到数据范围,123点字符串长度只有26,可以利用简单的方法进行计算,判断,当前已确定字母的出现次数,如果有>1的则无法构成,如果均为1,那么按照字典序给’?‘赋值即可。(实际写的时候这个可以省略)
之后的数据点则利用位置连续的特点,采用滑动窗口的方式,窗口长度为26,判断当前窗口内的字母是否只出现一次,以及?的数量等于缺失的字母的数量。然后按照字典序给’?'赋值。
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <queue>
using namespace std;
char c[1000005];
int a[30];
int len;
bool flag;
int l=0,r=26;
//滑动窗口
queue<char> q;
bool right()
{
for(int i=0;i<26;i++)
{
if(a[i]>1)
{//26个字母中已知的发生重复则不可
return false;
}
}
//没有重复且?个数和与q.size相同,则输出
for(int i=0;i<26;i++)
{
if(a[i]==0) q.push(i+'A');
}
if(a[26]==q.size())
return true;
}
int main()
{
cin>>c;
len=strlen(c);
if(len<=26)
{//长度为26时
for(int i=0;i<len;i++)
{
if(c[i]!='?')
a[c[i]-'A']++;
else a[26]++;
}
for(int i=0;i<26;i++)
{
if(a[i]>1)
{//26个字母中已知的发生重复则不可
cout<<"-1"<<endl;
return 0;
}
}
//没有重复
for(int i=0;i<26;i++)
{
if(a[i]==0) q.push(i+'A');
}
for(int i=0;i<len;i++)
{
if(c[i]=='?')
{
char tmp=q.front();
q.pop();
c[i]=tmp;
}
}
cout<<c;
}
else
{//使用滑动窗口
for(int i=0;i<26;i++)
{
if(c[i]!='?')
a[c[i]-'A']++;
else a[26]++;
}
while(r<=len)
{
if(right())
{
flag=true;
for(int i=0;i<26;i++)
{
if(c[i+l]=='?')
{
char tmp=q.front();
q.pop();
c[i+l]=tmp;
}
}
for(int i=0;i<26;i++)
{
printf("%c",c[i+l]);
}
return 0;
}
else
{
if(c[l]=='?') a[26]--;
else a[c[l]-'A']--;
if(c[r]=='?') a[26]++;
else a[c[r]-'A']++;
l++;
r++;
while(q.size()) q.pop();
}
}
if(!flag) cout<<"-1";
}
return 0;
}
T3 咕咕东的奇妙序列
题目描述
一个奇怪的无限序列:112123123412345 …这个序列由连续正整数组成的若干部分构成,其 中第一部分包含1至1之间的所有数字,第二部分包含1至2之间的所有数字,第三部分包含1至3之间的所 有数字,第i部分总是包含1至i之间的所有数字。所以,这个序列的前56项会是 11212312341234512345612345671234567812345678912345678910,其中第1项是1,第3项是2,第20项是 5,第38项是2,第56项是0。咕咕东现在想知道第 k 项数字是多少
数据范围
思路
首先这真是一个奇怪的题。考虑123点的数据范围,可以直接把题目中给的前56项先贴在代码里,然后直接O(1)查询即可。但是后面的点就要考虑实际如何操作了。
首先分析序列,可以写成这个样子:
1
12
123
1234
12345
123456
1234567
12345678
123456789
12345678910
1234567891011
123456789101112
先定义两个概念,
- 第i组:最大值为i位数
- 第j部分:第i组的第j个序列
首先我们的思想就是不断的减去k之前的有规律的组,从而确定k是当前第i组第j部分的第几个len位数,然后再进行查询。
- 初始化:
a[i]表示第i组的位数之和,b[i]表示第i组最后一个序列的长度,count表示i位数序列的个数
第1组的位数之和为:a[1]=(9+1)*9/2
第2组的位数之和为:a[2]=[(9+2)+(9+2×90)]×90/2
第3组的位数之和为
a[3]=[(9+2×90+3)+(9+2×90+3×900)]×900/2
…
第i组:a[i]=[(b[i−1]+i)+(b[i−1]+count×i)]×count/2
这个可以在最开始初始化得到,后续也无需更改。
- 确定k为第i组
判断k与a[i]的关系,若k>a[i],则不在第i组,可以从k中减去a[i],直到k<=a[i]为止,那么k就在第i组。
(由于数据上限的限制,a[i]最大只到a[9],所以可以直接遍历比较,无需二分)
-
确定k为第i组的第j部分
这个的确定方法与刚刚确定组数的方法一致,我们可以二分求得所求位置所在串的最后一个数是多少,即k为第i组的第j部分。由于每组的序列数较多,所以我们利用二分的方法进行查找。 -
确定k在该序列中的位置
同样的,方法还是分组,然后从k中减去,直至确定k所在的实际数据的位数,设为len。求出所在数据为第num个len位数,减去前面的num-1个数字即为当前数据。然后判断k是该数据的第几个数,通过/10的方法获得,最后%10即为k所代表的的元素。
#include <math.h>
using namespace std;
long long k,a[20],b[20];
long long q,weishu;
void initial()
{//记录i位数所在组数的最大位数
long long count=9;//i位数的序列个数
a[0]=b[0]=0;
for(long long i=1;i<=9;i++)
{//i为9的时候位数会超过1e18
a[i]=((b[i-1]+i)+(b[i-1]+count*i))*count/2;//i位数的上界
b[i]=b[i-1]+count*i;//最后一个i位数所对应的序列长度
count*=10;
printf("%lld\n",a[i]);
}
}
int main()
{
scanf("%d",&q);
initial();
while(q--)
{
scanf("%lld",&k);
//确定k的位数
for(int i=1;i<=9;i++)
{
if(a[i]>=k)
{
weishu=i;
break;
}
else k-=a[i];
}
printf("%lld\n",k);
//二分查找是第i位数序列的第几组
long long l=1,r=9*pow(10,weishu-1),mid;
long long sum;//k所在组之前的数据个数。
while(l<=r)
{
mid=(l+r)/2;//mid为第i位数序列的第mid组,即名次
sum=((b[weishu-1]+weishu)+(b[weishu-1]+mid*weishu))*mid/2;
if(sum>=k) r=mid-1;
else l=mid+1;
}
//r即为k所在组的名次,将之前的sum减去
k-=((b[weishu-1]+weishu)+(b[weishu-1]+r*weishu))*r/2;
//k变为该组中的名次,直接查找
long long len=1;
while(1)
{//将范围缩小到该组的len位数
long long pre=9*pow(10,len-1)*len;//在k之前的数字的个数
if(pre>=k) break;
k-=pre;
len++;
}
long long num=(k+len-1)/len;//求出是第num个len位数
//减去前面num-1个
k-=(num-1)*len;
//第num个len位数的大小
num=pow(10,len-1)+num-1;
long long cnt=len-k+1;//k为该len位数的第cnt位
for(long long i=1;i<cnt;i++) num/=10;
printf("%lld\n",num%10);
}
return 0;
}