递增三元组
题目描述
给定三个整数数组
A = [A1, A2, … AN],
B = [B1, B2, … BN],
C = [C1, C2, … CN],
请你统计有多少个三元组(i, j, k) 满足:
1 <= i, j, k <= N
Ai < Bj < Ck
输入
第一行包含一个整数N。
第二行包含N个整数A1, A2, … AN。
第三行包含N个整数B1, B2, … BN。
第四行包含N个整数C1, C2, … CN。
1 <= N <= 100000 0 <= Ai, Bi, Ci <= 100000
输出
一个整数表示答案
样例输入
3
1 1 1
2 2 2
3 3 3
样例输出
27
#include<iostream> //二分
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int a[N],b[N],c[N];
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++){
cin>>a[i];
}
for(int i=0;i<n;i++){
cin>>b[i];
}for(int i=0;i<n;i++){
cin>>c[i];
}
ll ans=0;
sort(a,a+n);
sort(b,b+n);
sort(c,c+n);
for(int i=0;i<n;i++){
int l=0,r=n-1;
int mid=0;
int x1=0;
while(l<r){ //没有=
mid=l+r+1>>1;
if(a[mid]<b[i])l=mid;
else r=mid-1;
}
if(a[l]>=b[i]){
l=-1;
}
x1=l;
l=0,r=n-1;
int x2=0;
while(l<r){
mid=l+r>>1;
if(c[mid]>b[i])r=mid;
else l=mid+1;
}
if(c[l]<=b[i]){
l=n;
}
x2=l;
ans+=(ll)(x1+1)*(n-x2); //a中下标加一,b中n-1-下标+1;
}
cout<<ans;
}
二分简介
upper_bound()
找到的是大于目标数的位置lower_bound()
找到的是大于等于目标数的位置
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int a[N],b[N],c[N];
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
cin>>b[i];
}for(int i=1;i<=n;i++){
cin>>c[i];
}
ll ans=0;
sort(a+1,a+n+1);
sort(c+1,c+n+1);
for(int i=1;i<=n;i++){
int key=b[i];
int pos1=lower_bound(a+1,a+n+1,key)-a-1;//大于等于b[i]的第一个数下标不取,减去1;
int pos2=upper_bound(c+1,c+n+1,key)-c;//大于b[i]的第一个数下标;
if(pos1 >= 1 && pos2 <= n)
ans+=(ll)pos1*(n-pos2+1);//c中少算一个数,加上
}
cout<<ans;
}
双指针
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int a[N],b[N],c[N];
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++){
scanf("%d",&b[i]);
}for(int i=1;i<=n;i++){
scanf("%d",&c[i]);
}
ll ans=0;
sort(a+1,a+n+1);
sort(b+1,b+n+1);//a,b,c数组必须全部排好序,在下面pos1与Pos2的位置可以不用重置为1;减少时间复杂度
sort(c+1,c+n+1);
int pos1=1,pos2=1;//在循环外重置
for(int i=1;i<=n;i++){
int key=b[i];
while(pos1<=n&&a[pos1]<key)pos1++;//等效于lower_bound
while(pos2<=n&&c[pos2]<=key)pos2++;//等效于upper_bound
ans+=(ll)(pos1-1)*(n-pos2+1);
}
cout<<ans;
}
前缀和思想
#include<iostream> //前缀和
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int a[N],b[N],c[N];
int s1[N],s2[N];//分别统计a[i]与c[i]出现次数
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
s1[a[i]]++;
}
for(int i=1;i<=n;i++){
cin>>b[i];
}for(int i=1;i<=n;i++){
cin>>c[i];
s2[c[i]]++;
}
ll ans=0;
for(int i=1;i<=N;i++){
s1[i]+=s1[i-1];
s2[i]+=s2[i-1];
}
for(int i=1;i<=n;i++){
ans+=(s1[b[i]-1]*ll(s2[N]-s2[b[i]]));//注意n与N,S2[N]是要取所有c中所有元素数量之和;
}
cout<<ans;
}
1245. 特别数的和
小明对数位中含有 2、0、1、92、0、1、9 的数字很感兴趣(不包括前导 00),在 11 到 4040 中这样的数包括 1、2、9、101、2、9、10 至 32、3932、39 和 4040,共 2828 个,他们的和是 574574。
请问,在 11 到 nn 中,所有这样的数的和是多少?
输入格式
共一行,包含一个整数 nn。
输出格式
共一行,包含一个整数,表示满足条件的数的和。
数据范围
1≤n≤100001≤n≤10000
输入样例:
40
输出样例:
574
#include<iostream> //水题
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
bool check(int x){
int flage=0;
while(x>0){
int w=x%10;
if(w==2||w==1||w==9||w==0){
return 1;
}
x=x/10;
}
return 0;
}
int main(){
int n;
cin>>n;
int ans=0;
for(int i=1;i<=n;i++){
if(check(i)){
ans+=i;
}
}
cout<<ans;
return 0;
}
1204. 错误票据
题目描述
某涉密单位下发了某种票据,并要在年终全部收回。
每张票据有唯一的ID号。全年所有票据的ID号是连续的,但ID的开始数码是随机选定的。
因为工作人员疏忽,在录入ID号的时候发生了一处错误,造成了某个ID断号,另外一个ID重号。
你的任务是通过编程,找出断号的ID和重号的ID。
假设断号不可能发生在最大和最小号。
输入
要求程序首先输入一个整数N(N< 100)表示后面数据行数。
接着读入N行数据。
每行数据长度不等,是用空格分开的若干个(不大于100个)正整数(不大于100000),请注意行内和行末可能有多余的空格,你的程序需要能处理这些空格。
每个整数代表一个ID号。
输出
要求程序输出1行,含两个整数m n,用空格分隔。
其中,m表示断号ID,n表示重号ID
样例输入
2
5 6 8 11 9
10 12 9
样例输出
7 9
#include<iostream> //将数存入数组后,排序,比较后一个数与前一个数的差值
#include<cstring>
#include<cstdio>
#include<sstream>
#include<algorithm>
using namespace std;
const int N=10010;
int a[N];
int main(){
int n=0,cnt;
int u,v;
string line;
cin>>cnt;
getline(cin,line);
while(cnt--){
getline(cin,line);
stringstream ssin(line);
while(ssin>>a[n])n++;
}
sort(a,a+n);
for(int i=1;i<n;i++){
if(a[i]==a[i-1]){
u=a[i];
}
if(a[i]-a[i-1]>=2){
v=a[i]-1;
}
}
printf("%d %d",v,u);
}
哈希表:
#include<iostream> //存入哈希表中,从最小的数开始判断缺数,或重数
#include<cstring>
#include<cstdio>
#include<sstream>
#include<algorithm>
using namespace std;
const int N=100001;
int a[N];
bool st[N];
int main(){
int n=0,cnt;
int u,v;
string line;
cin>>cnt;
getline(cin,line);
while(cnt--){
getline(cin,line);
stringstream ssin(line);
while(ssin>>a[n])n++;
}
int min1=999999;
for(int i=0;i<n;i++){
if(st[a[i]])v=a[i];
st[a[i]]=true;
min1=min(min1,a[i]);
}
for(int i=min1;i<min1+n;i++){
if(!st[i]){
u=i;
break;
}
}
cout<<u<<" "<<v;
}
哈希优化版本:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<sstream>
#include<algorithm>
using namespace std;
const int N=100001,INF=0x3f3f3f3f;
int hp[N];
int a[N];
bool st[N];
int main(){
int n=0,cnt;
int tp;
int u,v;
cin>>cnt;
int minv=INF,maxv=-INF;
while(cin>>tp){
hp[tp]++;
minv=min(minv,tp);
maxv=max(maxv,tp);
}
for(int i=minv;i<=maxv;i++){
if(hp[i]==2){
v=i;
}
if(hp[i]==0){
u=i;
}
}
cout<<u<<' '<<v;
}
回文日期;
20202020 年春节期间,有一个特殊的日期引起了大家的注意:20202020 年 22 月 22 日。
因为如果将这个日期按 “yyyymmdd
” 的格式写成一个 88 位数是 20200202
,恰好是一个回文数。
我们称这样的日期是回文日期。
有人表示 20200202
是“千年一遇” 的特殊日子。
对此小明很不认同,因为不到 22 年之后就是下一个回文日期:20211202
即 20212021 年 1212 月 22 日。
也有人表示 20200202
并不仅仅是一个回文日期,还是一个 ABABBABA
型的回文日期。
对此小明也不认同,因为大约 100100 年后就能遇到下一个 ABABBABA
型的回文日期:21211212
即 21212121 年 1212 月 1212 日。
算不上“千年一遇”,顶多算“千年两遇”。
给定一个 88 位数的日期,请你计算该日期之后下一个回文日期和下一个 ABABBABA
型的回文日期各是哪一天。
注意
下一个回文日期和下一个 ABABBABA
型的回文日期可能是同一天。
ABABBABA
型的回文日期,需要满足 A≠B。
输入格式
输入包含一个八位整数 N,表示日期。
输出格式
输出两行,每行 11 个八位数。
第一行表示下一个回文日期,第二行表示下一个 ABABBABA
型的回文日期。
数据范围
对于所有评测用例,10000101≤N≤89991231,保证 N 是一个合法日期的 88 位数表示。
输入样例:
20200202
输出样例:
20211202
21211212
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
int months[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
int a[8];
using namespace std;
bool check(int x){
int year=x/10000;
int month=x/100%100;
int day=x%100;
if(month>12&&!month)return 0; //判断月 大于12或为0
if(year%4==0&&year/100!=0||year%400==0){
months[2]=29;
}
if(!day||months[month]<day)return 0; //判断日 超越相应月份最大天数或为0
months[2]=28;
return 1;
}
bool judge(int x){
int k=0;
if(!check(x)){
return 0;
}
while(x){
a[k++]=x%10;
x/=10;
}
for(int i=0,j=7;i<j;i++,j--){//i<j
if(a[i]!=a[j])return 0;
}
return 1;
}
int main(){
int n;
cin>>n;
int i=0,j=0;
while(1){
if(j==1&&i==1)break;
if(judge(++n)){
if(j!=1){
j++;
cout<<n<<endl;}
if(a[0]!=a[1]&&a[0]==a[2]&&a[1]==a[3]){
i++;
cout<<n<<endl;
}
}
}
}
466. 回文日期
在日常生活中,通过年、月、日这三个要素可以表示出一个唯一确定的日期。
牛牛习惯用 8 位数字表示一个日期,其中,前 4 位代表年份,接下来 2 位代表月份,最后 2 位代表日期。
显然:一个日期只有一种表示方法,而两个不同的日期的表示方法不会相同。
牛牛认为,一个日期是回文的,当且仅当表示这个日期的 8 位数字是回文的。
现在,牛牛想知道:在他指定的两个日期之间(包含这两个日期本身),有多少个真实存在的日期是回文的。
一个 8位数字是回文的,当且仅当对于所有的 i(1≤i≤8) 从左向右数的第 i 个数字和第 9−i 个数字(即从右向左数的第 i 个数字)是相同的。
例如:
•对于2016年11月19日,用8位数字20161119表示,它不是回文的。
•对于2010年1月2日,用8位数字20100102表示,它是回文的。
•对于2010年10月2日,用8位数字20101002表示,它不是回文的。
每一年中都有12个月份:
其中,1、3、5、7、8、10、12月每个月有31天;4、6、9、11月每个月有30天;而对于2月,闰年时有29天,平年时有28天。
一个年份是闰年当且仅当它满足下列两种情况其中的一种:
1.这个年份是4的整数倍,但不是100的整数倍;
2.这个年份是400的整数倍。
例如:
•以下几个年份都是闰年:2000、2012、2016。
•以下几个年份是平年:1900、2011、2014。
输入输出格式
输入格式:
输入包括两行,每行包括一个8位数字。
第一行表示牛牛指定的起始日期。
第二行表示牛牛指定的终止日期。
保证date_i和都是真实存在的日期,且年份部分一定为4位数字,且首位数字不为0。
保证date1 —定不晚于date2。
输出格式:
输出一行,包含一个整数,表示在date1和date2之间,有多少个日期是回文的。
输入样例:
20110101
20111231
输出样例:
1
该题与上题不同点在于使用枚举日期,判断是否是回文数的做法,会超时
因此,我们先将所有回文数枚举,判断是否在范围内且是合法日期;
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
int months[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
int a[8];
using namespace std;
bool check(int x){
int year=x/10000;
int month=x/100%100;
int day=x%100;
if(!month||month>12)return 0; //判断月
if(year%4==0&&year/100!=0||year%400==0){
months[2]=29;
}
if(!day||months[month]<day)return 0; //判断日
months[2]=28;
return 1;
}
int f(int x){
int a=0;
while(x){
a=a*10+x%10;
x=x/10;
}
return a;
}
int main(){
int start,end,ans=0;
cin>>start>>end;
for(int i=1000;i<=9999;i++){
int x=i*10000+f(i);
if(x>=start&&x<=end&&check(x)){
ans++;
}
}
cout<<ans;
}