枚举,模拟和排序
1.递增三元组
给定三个整数数组
A=[A1,A2,…AN]
B=[B1,B2,…BN]
C=[C1,C2,…CN]
请你统计有多少个三元组 (i,j,k) 满足:
- 1≤i,j,k≤N
- Ai<Bj<Ck
数据范围
1≤N≤105
0≤Ai,Bi,Ci≤105
思路:
最简单的思路就是暴力枚举,但是这里行不通,数据范围是105,三重循环就是1015,肯定会超时,根据数据范围可以反推,这里用的算法时间复杂度是nlogn。
那就只能用遍历一个数组了,遍历A和C是一样的,如果遍历A,B和C不独立,没法做。只能遍历B,遍历B的时候那就只需要找A中比B小的个数,C中比B大的个数。
可以考虑的算法有:1.前缀和 2.排序加二分
代码(前缀和):
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=100010;
typedef long long LL;
int a[N],b[N],c[N];
int cnt[N],s[N];
int as[N],cs[N];
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d",&a[i]),a[i]++;
for(int i=0;i<n;i++) scanf("%d",&b[i]),b[i]++;
for(int i=0;i<n;i++) scanf("%d",&c[i]),c[i]++;
for(int i=0;i<n;i++) cnt[a[i]]++;
for(int i=0;i<N;i++) s[i]=s[i-1]+cnt[i];
for(int i=0;i<n;i++) as[i]=s[b[i]-1];
memset(cnt,0,sizeof cnt);
memset(s,0,sizeof s);
for(int i=0;i<n;i++) cnt[c[i]]++;
for(int i=0;i<N;i++) s[i]=s[i-1]+cnt[i];
for(int i=0;i<n;i++) cs[i]=s[N-1]-s[b[i]];
LL res=0;
for(int i=0;i<n;i++)
{
res+=(LL)as[i]*cs[i];
}
cout<<res<<endl;
return 0;
}
2.特别数的和
题意:
小明对数位中含有 2、0、1、9 的数字很感兴趣(不包括前导 00),在 1到 40 中这样的数包括 1、2、9、10 至 32、39 和 40,共 28 个,他们的和是 574
请问,在 1 到 n中,所有这样的数的和是多少?
数据范围
1≤n≤10000
思路:
这里的数据范围是10^4,暴力枚举一遍再逐个判断不会超时,可以直接用暴力来做。
这里要用到的就是求每一位上的数是多少。
代码:
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int n;
cin>>n;
int cnt=0;
for(int i=1;i<=n;i++)
{
bool flag=false;
int j=i;
while(j/10)
{
int d=j%10;
if(d==1 || d==2 || d==0 ||d==9)
{
flag=true;
break;
}
j/=10;
}
if(j==1 || j==2 || j==0 || j==9) flag=true;
if(flag) cnt+=i;
}
cout<<cnt;
return 0;
}
3.错误票据
题意:
输入一个数n,接下来输入n个数(ID),这n个数(ID)是连续的,但ID的开始数码是随机选定的。
因为工作人员疏忽,在录入ID号的时候发生了一处错误,造成了某个ID断号,另外一个ID重号。找出断号的ID和重号的ID。
思路:
俩种做法:1. 排序加查找 2.下标数组,判断是否为空
这里输入的数据有点特殊,是一行行输入的,所有需要先将数据存入string中,再一个个的从string中读入。
代码(排序)
#include <iostream>
#include <algorithm>
#include <sstream>
using namespace std;
const int N=10010;
int a[N];
int n;
int main()
{
int cnt;
string line;
cin>>cnt;
getline(cin,line);
while(cnt--)
{
getline(cin,line);
stringstream ssin(line);
while(ssin>>a[n]) n++;
}
int start=0,end=0;
sort(a,a+n);
for(int i=1;i<n;i++)
if(a[i]==a[i-1]) end=a[i];
else if(a[i]-a[i-1]>1) start=a[i]-1;
cout<<start<<' '<<end<<endl;
return 0;
}
4.回文日期
题意:
牛牛习惯用 8 位数字表示一个日期,其中,前 4 位代表年份,接下来 2 位代表月份,最后 2 位代表日期。
给定俩个日期,求指定的两个日期之间(包含这两个日期本身),有多少个真实存在的日期是回文的。
思路:
根据日期的表示方式,可以知道年份的数据范围是1000-10000.可以找到这期中回文的日期,再判断合法。
代码:
#include <iostream>
#include <algorithm>
int days[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
bool check(int d)
{
int year=d/10000;
int month=d%10000/100;
int day=d%100;
if(month==0 || month>12) return false;
if(day==0 || (month!=2 && day>days[month])) return false;
if(month==2)
{
int reap=year%400==0 || (year%100!=0)&& year%4==0;
if(day>days[month]+reap) return false;
}
return true;
}
int main()
{
int day1,day2;
scanf("%d%d",&day1,&day2);
int res=0;
for(int i=1000;i<10000;i++)
{
int day=i,x=i;
for(int j=0;j<4;j++)
{
day=day*10+x%10;
x/=10;
}
if(day1<=day && day<=day2)
{
if(check(day))
res++;
}
}
printf("%d",res);
return 0;
}
5. 归并排序
题意:
给定你一个长度为n的整数数列。
请你使用归并排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。
思路:
归并就是不断递归,将数据俩俩划分,通过俩俩排序最后来完成整个排序,时间复杂度nlogn。
代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int N=1000100;
int a[N],temp[N];
int n;
void merge_sort(int q[],int l,int r)
{
if(l>=r) return ;
int k=0;
int mid=(l+r)>>1;
merge_sort(q,l,mid),merge_sort(q,mid+1,r);
int i=l,j=mid+1;
while(i<=mid && j<=r)
if(q[i]<=q[j]) temp[k++]=q[i++];
else temp[k++]=q[j++];
while(i<=mid) temp[k++]=q[i++];
while(j<=r) temp[k++]=q[j++];
for(i=l,j=0;i<=r;i++,j++) q[i]=temp[j];
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
cin>>a[i];
}
merge_sort(a,0,n-1);
for(int i=0;i<n;i++) printf("%d ",a[i]);
return 0;
}