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
思路
这个题目第一感觉是比较类似于尺取法,满足尺取法的两个条件:
- 要求一个连续的区间
- 左右端点有一个明确的移动方向
所以只需要维护两个指针,并用一个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的时候,是可以打表的,从解决问题的角度说,需要确定两个问题:
- k在哪一项的序列里
- 在一项里的序列中的第几个
第一个问题
对于第一个问题,设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 j−10的等差数列,
对于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
j−100的等差序列
有以上分析可知,确定一个项开始的位置,需要确定对应他是几位数,知道了其位数x,再根据pos(
1
0
x
−
1
10^{x-1}
10x−1),即可决定其位置:
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,共9项,pos(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=9∗1+90∗2+900∗3+...+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;
}