CSPM2

1. 一个序列中不同数字的个数

题目大意

对于一个序列,是否存在一个数K,使得一些数加上K,一些数减去K,一些数不变,使得整个序列中所有的数相等,其中对于序列中的每个位置上的数字,至多只能执行一次加运算或
减运算或是对该位置不进行任何操作。

思路

这个题目从思路上来说很简单,只需要考察一个序列中不同数字的个数就可以了,当不同数字的个数小于等于3的时候就可以了。最简单的做法是用set判断一下去重,但是考试的时候并没有想到用set,采用的排序之后,寻找不同数字的方法,结果因为少考虑了不同数字个数少于3的情况而出错(其实考虑了,但是判断条件写错了,只考虑了原序列个数小于3的情况)

代码

考试中的代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long llong;
llong a[100000];
int main()
{
	llong t;
	llong n;
	scanf("%lld",&t);
	while(t--)
	{
		scanf("%lld",&n);
		for(int i=0;i<n;i++)
		{
			scanf("%lld",a+i);
		}
		sort(a,a+n);
		int p1=0,p2=0,p3=0;
		bool flag =1;
		for(int j=1;j<n;j++)
		{
			if(a[j]==a[p1]||a[j]==a[p2]||a[j]==a[p3]) continue;
			else if(a[j]!=a[p1]&&p2==0) p2=j;
			else if(a[j]!=a[p1]&&a[j]!=a[p2]&&p3==0) p3=j;
			else if(p3!=0) {
				flag=0;
				break;
			}
		}
		if(flag&&(a[p3]-a[p2]==a[p2]-a[p1]))
		{
		  
		  printf("YES\n");
		
		}
		else if(p3==0&&p2==0 || p3==0) printf("YES\n");  //就是这个地方啊,有两个数相同的时候 
		else printf("NO\n");
	}
	
	return 0;
}

最优代码:注意一下set每轮要清空

#include<bits/stdc++.h>
using namespace std;
typedef long long llong;
set<llong> s;
int main()
{
	llong t, n ,a;
	scanf("%lld",&t);
	while(t--){
		s.clear();
		scanf("%lld",&n);
		for(int i=0;i<n;i++){
			scanf("%lld",&a);
			s.insert(a);
		}
		if(s.size()<=2) printf("YES\n");
		else if(s.size()==3){
			set<llong>::iterator it = s.begin();
			llong x = *(it++) , b = *(it++) ,c= *(it++);
			if(c-b == b-x) printf("YES\n");
			else printf("NO\n");
		}
		else printf("NO\n");
	}
	return 0;
}

2. 寻找连续字母序列

题目大意

现在给定一个字符串,字符串中包括26个大写字母和特殊字符’?’,特殊字符’?'可以代表任何一个大写字母。是否存在一个位置连续的且由26个大写字母组成的子串,在这个子串中每个字母出现且仅出现一次,如果存在,请输出从左侧算起的第一个出现的符合要求的子串,并且要求,如果有多组解同时符合位置最靠左,则输出字典序最小的那个解!如果
不存在,输出-1

思路

这个题目第一感觉是比较类似于尺取法,满足尺取法的两个条件:

  1. 要求一个连续的区间
  2. 左右端点有一个明确的移动方向

所以只需要维护两个指针,并用一个set记录一下,当前区间内已经有的字母,不需要记录问号,当r指向的字母在set中已经存在的时候,处理方法是:将l直接移动到set中重复字母对应的序列位置的下一个位置,并记录r指向的字母的位置。 当r-l <25 的时候,r++,直到r-l==25 或者是r>=n的时候,退出查找过程。
对于这个长度为26的序列,只需对其进行一次遍历,如果是字母直接输出,若是?的话,从A开始,寻找还不在set中的,字典序最小的字母,找到后将这个字母加入到set中去。
考试的时候,主要是判断条件写成了 while(r-l<25 && r<n)这样只判了25个数,r对应位置上的数并没有判断是否可以加入序列,会导致查找提前结束。

代码
#include<bits/stdc++.h>
using namespace std;
set<char>a;
string s;
map<char,int> dem; //position
void solve1() //26个 
{
	int cnt=0; //?
//	string ans;
	for(int i=0;i<s.size();i++)
	{
		if(s[i]>='A'&&s[i]<='Z') 
		{
			if(a.find(s[i])==a.end()) a.insert(s[i]);
			else 
			{
				cout<<-1;
				return ;
		    }
		}
		else if(s[i]=='?') cnt++;
	}
	char base='A';
	if(cnt+a.size() ==26) 
	{
		for(int i=0;i<s.size();i++)
		{
			base='A';
			if(s[i]>='A'&&s[i]<='Z') cout<<s[i];
			else if(s[i]=='?')
			{
				for(int k=0;k<26;k++)
				{
					char ch =(char)(base+k);
					if(a.find(ch)==a.end()){
						cout<<ch;
						a.insert(ch);
						break;
					}
				}
			}
		}
		
	}
	else cout<<-1;
	
}
void solve2()
{
	int l=0,r=0;
	int cnt =0; 
	int n= s.size(); 
	//while(r-l<25 && r<n) :这样只判了25个数, 
	while(r<n)  
	{ 
		if(s[r]>='A'&&s[r]<='Z')
		{
			if(a.find(s[r])==a.end())
			{
				a.insert(s[r]);
				dem[s[r]] = r; 
			}
			else 
			{
				l=dem[s[r]]+1; //l前移
				a.clear(); dem.clear(); //重新维护这2个
				for(int k =l;k<=r;k++)
				{
					if(s[k]>='A'&&s[k]<='Z'){
						a.insert(s[k]);
						dem[s[k]]=k;
					}
				} 
			}
		}
		 if(r-l==25) break; 
	     r++;
      }
//      cout<<l<<" "<<r<<endl; 
      if(r>n-1) {
      	cout<<-1;
      	return;
      }
      for(int i=l;i<=r;i++)
	  {
			char base='A';
			if(s[i]>='A'&&s[i]<='Z') cout<<s[i];
			else if(s[i]=='?')
			{
				for(int h=0;h<26;h++)
				{
					char ch =(char)(base+h);
					if(a.find(ch)==a.end()){
						cout<<ch;
						a.insert(ch);
						break;
					}
				}
			}
		}

}
int main()
{
	
	std::ios::sync_with_stdio(false);
	cin>>s;
	a.clear();
	dem.clear();
//	if(s.size()<=26) solve1();
	solve2();
	return 0;
}
3. 奇怪的数列
题目大意

一个奇怪的无限序列:112123123412345 …这个序列由连续正整数组成的若干部分构成,其中第一部分包含1至1之间的所有数字,第二部分包含1至2之间的所有数字,第三部分包含1至3之间的所有数字,第i部分总是包含1至i之间的所有数字。所以,这个序列的前56项会是11212312341234512345612345671234567812345678912345678910,其中第1项是1,第3项是2,第20项是5,第38项是2,第56项是0。现在想知道第 k 项数字是多少?k不超过1e18

思路

从考试的角度上说,当k<1e8的时候,是可以打表的,从解决问题的角度说,需要确定两个问题:

  1. k在哪一项的序列里
  2. 在一项里的序列中的第几个
第一个问题

对于第一个问题,设pos(j)为第j项开始的位置
对于第j项,当j<10的时候, p o s ( j ) = ∑ ( 1 + 2 + . . ) pos(j)=\sum(1+2+..) pos(j)=(1+2+..),一共j-1项

当10<=j<=99的时候, p o s ( j ) = p o s [ 10 ] + ∑ ( 11 + 13 + 15 + . . . ) pos(j)=pos[10] + \sum(11+13+15+... ) pos(j)=pos[10]+(11+13+15+...), pre[10]是第10项之前的数的总个数,11是第10项的序列长度, 上式后边是一个公差为2, 长度是 j − 10 j-10 j10的等差数列,

对于100<=j<=999 ,
p o s ( j ) = p o s [ 100 ] + ∑ ( 192 + 195 + 198 + . . . ) pos(j)=pos[100] + \sum(192+195+198+... ) pos(j)=pos[100]+(192+195+198+...)
类似的,192是第100项的长度,后边是一个长度为 j − 100 j-100 j100的等差序列
有以上分析可知,确定一个项开始的位置,需要确定对应他是几位数,知道了其位数x,再根据pos( 1 0 x − 1 10^{x-1} 10x1),即可决定其位置:

llong pos(llong n)   //开始位置
{
   if(prenum.find(n)!=prenum.end()){
  	return prenum[n];
  } 
  llong len=wei(n); //位数 
  llong base = ten(len-1);
  llong a1=keynum[base]; 
  llong num = n-base; //与base之间的数的个数 
  return S(a1,num,len)+ prenum[base];  
} 

所以需要在计算位置之前,先把 p o s ( 1 0 x ) pos(10^x) pos(10x)确定出来,可以有如下规律:
p o s ( 10 ) = p o s ( 1 ) + 1 + 2 + . . . + 9 , 共 9 项 , p o s ( 1 ) = 0 pos(10)=pos(1)+1+2+...+9, 共9项,pos(1)=0 pos(10)=pos(1)+1+2+...+9,9pos(1)=0
p o s ( 100 ) = p o s ( 10 ) + 11 + 13 + . . . , 共 90 项 pos(100)=pos(10)+11+13+..., 共90项 pos(100)=pos(10)+11+13+...,90
p o s ( 1000 ) = p o s ( 100 ) + 192 + . . . . , 共 900 项 pos(1000)=pos(100)+192+...., 共900项 pos(1000)=pos(100)+192+....,900
所以,递推关系比较明确,只是去要提前计算一下首项,可以如下计算, a 1 = 9 ∗ 1 + 90 ∗ 2 + 900 ∗ 3 + . . . + i a1=9*1+90*2+900*3+...+i a1=91+902+9003+...+i(10的幂次)

 llong a1=i; // 10的幂次
 for(int k=i-1;k;k--) //计算首项  {
	   a1+= k*9*ten(k-1); }
 keynum[x/10] = a1; 
	     //初始化这个数组 ,注意,a1对应的是10^(i-1)的首项 

所以,计算pos(10^x)的方法:

   prenum[1]=0 ;//初始化 ,这里计算10,100,1000的开始位置,也是9,99,999的结束位置 
	preseq[1]=0; 
	for(llong i=1;i<=9;i++)	{   
	    llong x =  ten(i);
	    llong px = ten(i-1);
	    llong e =  x-px; //个数 
	    llong a1=i; // 10的幂次 
	    for(int k=i-1;k;k--) //计算首项 
	    {
	    	a1+= k*9*ten(k-1);
	    }
	    keynum[x/10] = a1;  //初始化这个数组 ,注意,a1对应的是10^(i-1)的首项 
		prenum[x] = prenum[px] +S(a1,e,i) ;	
		preseq[x] = preseq[px] +e*i;
//		cout<<prenum[x]<<": "<<preseq[x]<<endl;
    }

以上我们可以获得,任意一项的开始位置,那么在已知位置k的情况下,需要反推出是哪一项,需要使用二分答案的方法来查找,二分的范围l,r,就是满足: p o s ( l = 1 0 i ) < k < p o s ( r = 1 0 i + 1 ) pos(l=10^i) <k <pos(r=10^{i+1}) pos(l=10i)<k<pos(r=10i+1),二分的找出k属于l,r 之间的哪一项

第二个问题

对于第2个问题,思路相对简单一些,首先需要确定k对应的是几位数,以k指向第200项的105中的1为例,首先要确定,在第200项中,100的位置是多少,例如是t,则通过k-t就知道了,k距离100开始的位置的距离dd,这个距离如果能整除k指向数据的位数(3),则k对应的数就是:t+=dd/3 对应的位置的数的个位;如果不能整除,t+=dd/3+dd%3 对应的位置的数。

llong gete(llong d) //若d>0 , x>=1, dd>=0 
{
	llong len = wei(d);
	llong x=0;
	for(llong i=1;i<=len;i++){  //先缩小范围
   	   if(d<=preseq[ten(i)]){
   	   	  x=i; break;
   	   } 
   }
   llong dd = d - preseq[ten(x-1)];
   llong t = ten(x-1)-1; //上一个数 
   t+=dd/x;
   if(dd%x==0) //整除  	  
   	  printf("%lld\n",t%10);
   
   else{
   	  dd%= x;   //在数里的哪一位(高位) 
   	  llong p=t+1; //对这个数操作 
   	  for(int j=0;j<x-dd;j++) p/=10;
   	  printf("%lld\n",p%10);
   }
}
代码

这种题考试的时候一定要打表。

#include<bits/stdc++.h>
using namespace std;
typedef long long llong ;
llong x,y;
llong d; //由于等差数列,增加的位数 
map<llong,llong> prenum; //10^n:begin pos 
map<llong,llong> keynum; //10,100,1000 这一项的个数 
map<llong,llong> preseq; //在具体序列里的个数 
llong ten(int a) {
	return (llong)pow(10,a);
}
int wei(llong n){ //位数
  int num=0;
  while(n>0) {
   	num++;  n/=10;
  }
  return num;
}
llong S(llong a1, llong n,llong d){  //等差数列求和 
	return n*a1+n*(n-1)*d/2;
}
void init() 
{
    prenum[1]=0 ;//初始化 ,这里计算10,100,1000的开始位置,也是9,99,999的结束位置 
	preseq[1]=0; 
	for(llong i=1;i<=9;i++)
	{   
	    llong x =  ten(i);
	    llong px = ten(i-1);
	    llong e =  x-px; //个数 
	    llong a1=i; // 10的幂次 
	    for(int k=i-1;k;k--) //计算首项 
	    {
	    	a1+= k*9*ten(k-1);
	    }
	    keynum[x/10] = a1;  //初始化这个数组 ,注意,a1对应的是10^(i-1)的首项 
		prenum[x] = prenum[px] +S(a1,e,i) ;	
		preseq[x] = preseq[px] +e*i;
//		cout<<prenum[x]<<": "<<preseq[x]<<endl;
    }
} 
llong pos(llong n)   //begin_posi
{
   if(prenum.find(n)!=prenum.end()){
  	return prenum[n];
  } 
  llong len=wei(n); //位数 
  llong base = ten(len-1);
  llong a1=keynum[base]; 
  llong num = n-base; //与base之间的数的个数 
  return S(a1,num,len)+ prenum[base];  
}  
llong gete(llong d) //若d>0 , x>=1, dd>=0 
{
	llong len = wei(d);
	llong x=0;
	for(llong i=1;i<=len;i++){  //先缩小范围
   	   if(d<=preseq[ten(i)]){
   	   	  x=i; break;
   	   } 
   }
   llong dd = d - preseq[ten(x-1)];
   llong t = ten(x-1)-1; //上一个数 
   t+=dd/x;
   if(dd%x==0) //整除  	  
   	  printf("%lld\n",t%10);
   
   else{
   	  dd%= x;   //在数里的哪一位(高位) 
   	  llong p=t+1; //对这个数操作 
   	  for(int j=0;j<x-dd;j++) p/=10;
   	  printf("%lld\n",p%10);
   }
}
void solve2(llong k)
{
   llong l=0,r=1,mid=0,posk=0,d=0;
   for(int i=1;i<=9;i++){  //先缩小范围
   	if(k>prenum[ten(i)]) {
   		l=ten(i)-1;
      }
     else if(k<prenum[ten(i)]){
     	r=ten(i);
     	break;
     }
  }
  while(l<r) //二分 
  {
  	mid=(l+r)>>1;
  	if(pos(mid)>=k){
  		r=mid;
  	}
  	else l=mid+1;
  }
  if(pos(mid)<k) mid++;
  if(pos(mid)==k) { 
  	printf("%lld\n",(mid-1)%10);
  	return ;
  }
    d= k-pos(mid-1);
    gete(d);
}
void solve1(int k)
{
	string s;
    s="11212312341234512345612345671234567812345678912345678910";	
	printf("%c\n",s[k-1]);
}
int main()
{
//	freopen("data.txt","r",stdin);
	int q =0; 
	init();
//	while(1)
	scanf("%d",&q);
	llong k;
	for(int i=0;i<q;i++)
	{
		
		scanf("%lld",&k);
//		cout<<k<<" : ";
		if(k<=56) solve1(k);
	    else  solve2(k);
	}
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值