题目怎么说就怎么做
代码能力
卡拉兹(Callatz)猜想:
对任何一个正整数 n,如果它是偶数,那么把它砍掉一半;如果它是奇数,那么把 (3n+1) 砍掉一半。这样一直反复砍下去,最后一定在某一步得到 n=1。
我们今天的题目不是证明卡拉兹猜想,而是对给定的任一不超过 1000 的正整数 n,简单地数一下,需要多少步(砍几下)才能得到 n=1?
输入格式:每个测试输入包含 1 个测试用例,即给出正整数 n 的值。
3
输出格式:输出从 n 计算到 1 需要的步数。
5
解题思路
n=3 --> n=(3*3+1)/2=5; --> 8 --> 4 --> 2 --> 1
输入一个正整数 n ,用 m 记录步数。
AC代码
#include
using namespace std;
int main(){
int n,m=0;
scanf("%d",&n);
while(n!=1){
if(n%2==0){//偶数
n /= 2;
m++;
}
else{//奇数
n = (3*n+1)/2;
m++;
}
}
printf("%d",m);
return 0;
}
为了用事实说明挖掘机技术到底哪家强,PAT 组织了一场挖掘机技能大赛。现请你根据比赛结果统计出技术最强的那个学校。
输入格式:输入在第 1 行给出不超过 10^5 的正整数 N,即参赛人数。随后 N 行,每行给出一位参赛者的信息和成绩,包括其所代表的学校的编号(从 1 开始连续编号)、及其比赛成绩(百分制),中间以空格分隔。6 3 65 2 80 1 100 2 70 3 40 3 0
输出格式:在一行中给出总得分最高的学校的编号、及其总分,中间以空格分隔。题目保证答案唯一,没有并列。
2 150
多行输入
、数组
解题思路
输入 n ,根据 n 进入循环,输入(编号i,分数a),score[i]+=a;
比较score[i]大小,找到最大值。
AC代码
#include
using namespace std;
int main(){
int n,i=0,a=0;
scanf("%d",&n);
int score[n+1] = {0};
for(int j=0;jmax){
max = score[k];
id = k;
}
}
printf("%d %d",id,max);
return 0;
}
小范围查找——遍历;大范围查找——算法。
输入一个数n,然后输入n个数值各不相同,再输入一个值x,输出这个值在这个数组中的下标(从0开始,若不在数组中则输出-1)。
输入:测试数据有多组,输入n(1<=n<=200),接着输入n个数,然后输入x。
4 1 2 3 4 3
输出:对于每组输入,请输出结果。
2
解题思路
遍历。
AC代码
#include
using namespace std;
const int MAX = 210;
int A[MAX];
int main(){
int n,x;
while(scanf("%d",&n)!=EOF){//一直输到文件末尾
for(int i=0;i
!弄清规则:
美国总统奥巴马不仅呼吁所有人都学习编程,甚至以身作则编写代码,成为美国历史上首位编写计算机代码的总统。2014 年底,为庆祝“计算机科学教育周”正式启动,奥巴马编写了很简单的计算机代码:在屏幕上画一个正方形。现在你也跟他一起画吧!
输入格式:输入在一行中给出正方形边长 N(3≤N≤20)和组成正方形边的某种字符 C,间隔一个空格。10 a
输出格式:输出由给定字符 C 画出的正方形。但是注意到行间距比列间距大,所以为了让结果看上去更像正方形,我们输出的行数实际上是列数的 50%(四舍五入取整)。
aaaaaaaaaa a a a a a a aaaaaaaaaa
解题思路
直接输出。
AC代码
#include
using namespace std;
int main(){
int n;
char c;
scanf("%d %c",&n,&c);
int row=n%2==0?n/2:(n+1)/2;
int col=n;
int i;
for(i=0;i
绘制一个X(用*号表示线),其中长、宽、对角线的长度(即可容纳的*号个数)均为同一个奇数n。
输入:n(3<=n<=99)
5
输出:X
* * * * * * * * *
解题思路
二维数组。
AC代码
#include
#include
using namespace std;
const int MAX = 100;
char A[MAX][MAX];
int main(){
int n;
scanf("%d",&n);
int i,j;
for(i=0;i
注意平年(365)闰年(366)、大小月的区别。
有两个日期,求两个日期之间的天数,如果两个日期是连续的我们规定他们之间的天数为两天。
输入:有多组数据,每组数据有两行,分别表示两个日期,形式为YYYYMMDD20130101 20130105
输出:每组数据输出一行,即日期差值
5
解题思路
假设第一个日期早于第二个日期(否则交换);【都是 int 可直接比较,也方便后面 y,m,d 的拆分】
令日期一不断加1,直到日期一等于日期二。
具体处理:
所需数据:
快速处理:先加年,根据平年闰年加365或366。
AC代码
#include
using namespace std;
int month[13][2] = {{0,0},{31,31},{28,29},{31,31},{30,30},{31,31},{30,30},{31,31},{31,31},{30,30},{31,31},{30,30},{31,31}};
//判断平年?闰年
bool isLeap(int year){
return (year%4==0&&year%100!=0)||(year%400==0);
}
int main(){
int time1,y1,m1,d1;
int time2,y2,m2,d2;
while(scanf("%d\n%d",&time1,&time2) != EOF){
if(time1>time2){
int t = time1;
time1 = time2;
time2 = t;
}//保证time1
给定一个日期day,求它是周几。
输入描述:第一行为给定的日期day(格式为YYYY-MM-DD,范围为1900-01-01<=day<=2199-12-31),数据保证一定合法。
2021-05-01
输出描述:输出一个整数,表示周几。其中周一到周六分别用1-6表示,周天用0表示。
6
解题思路
根据 2021-05-01 是周六推算。
AC代码
#include
using namespace std;
int month[13][2] = {{0,0},{31,31},{28,29},{31,31},{30,30},{31,31},{30,30},{31,31},{31,31},{30,30},{31,31},{30,30},{31,31}};
//判断平年?闰年
bool isLeap(int year){
return (year%4==0&&year%100!=0)||(year%400==0);
}
int main(){
int y1,m1,d1,time1;
scanf("%d-%d-%d",&y1,&m1,&d1);
time1 = y1*10000+m1*100+d1;
int y2=2021,m2=05,d2=01,week2=6;
int time2 = 20210501;
int n=0;
if(time1
将 P 进制转换成 Q 进制:
P 进制 x = a 1 a 2 . . . a n x=a_{1}a_{2}...a_{n} x=a1a2...an 转换成十进制 y y y。
y = a 1 ∗ P n − 1 + a 2 ∗ P n − 2 + . . . + a n − 1 ∗ P + a n y=a_{1}*P^{n-1}+a_{2}*P^{n-2}+...+a_{n-1}*P+a_{n} y=a1∗Pn−1+a2∗Pn−2+...+an−1∗P+an
int y=0,product=1;
while(x!=0){
y += product*(x%10);//(x%10)获取每位数。从a_{n}开始取
x /= 10;//去掉已取的个位;
product *= P;
}
十进制 $ y$ 转换成 Q 进制 z z z —— “除基取余法”。
int z[40],num=0;
do{
z[num++] = y%Q;//除基取余,z[0]=0
y /= Q;
}while(y!=0);
输入两个非负 10 进制整数 A 和 B ( ≤ 2 30 − 1 \leq 2^{30}-1 ≤230−1),输出 A+B 的 D (1
输入格式:输入在一行中依次给出 3 个整数 A、B 和 D。
123 456 8
输出格式:输出 A+B 的 D 进制数。
1103
解题思路
十进制转换成 D 进制。
AC代码
#include
using namespace std;
void tenToD(int d,int c,int n){
int z[n],num=0;
do{
z[num++] = c%d;
c /= d;
}while(c!=0);
for(int i=num-1;i>=0;i--){
printf("%d",z[i]);
}
}
int main(){
int a,b,sum=0,d;
scanf("%d %d %d",&a,&b,&d);
sum = a+b;
tenToD(d,sum,31);
return 0;
}
给定一个十进制数,输出它的 K 进制形式。
输入描述:一个非负整数n(0<=n<=1024)和一个正整数K(2<=K<=16)。
45 16
输出描述:输出一行,表示n的K进制。其中超过9的位使用大写英文字母表示(10 => A、11 => B、12 => C、13 => D、14 => E、15 => F)。
2D
解题思路
升级,有ABCDEF。printf("%c",z[i]-10+'A');
AC代码
/*
*title:sunnywhy3.5.3
*description:十进制转K进制
*time:2023-01-06
*/
#include
using namespace std;
void tenToD(int d,int c,int n){
int z[n];
int num=0;
do{
z[num++] = c%d;
c /= d;
}while(c!=0);
for(int i=num-1;i>=0;i--){
if(z[i]<10){
printf("%d",z[i]);
}
else{
printf("%c",z[i]-10+'A');//ABCD
}
}
}
int main(){
int a,d;
scanf("%d %d",&a,&d);
tenToD(d,a,31);
return 0;
}
读入一串字符,判断是否是回文串。“回文串”是一个正读和反读都一样的字符串,比如“level”或者“noon”等等就是回文串。
输入:一行字符串,长度不超过255。
12321
输出:如果是回文串,输出“YES”,否则输出“NO”。
YES
解题思路
str[i]==str[len-1-i]
AC代码
#include
#include
using namespace std;
const int MAX=256;
char a[MAX];
int main(){
int k=0;
scanf("%s",a);
int len = strlen(a);
for(int i=0;i
给定一句英语,要求你编写程序,将句中所有单词的顺序颠倒输出。
输入格式:测试输入包含一个测试用例,在一行内给出总长度不超过 80 的字符串。字符串由若干单词和若干空格组成,其中单词是由英文字母(大小写有区分)组成的字符串,单词之间用 1 个空格分开,输入保证句子末尾没有多余的空格。
Hello World Here I Come
输出格式:每个测试用例的输出占一行,输出倒序后的句子。
Come I Here World Hello
解题思路
使用gets
函数读入一整行,以空格为分隔符对单词进行划分,并按序放到二维字符数组中,最后按单词逆序输出。
!PAT不支持gets
,需用cin.getline(str,字符数组大小)
代替。
#include
using namespace std;
cin.getline(str,字符数组大小);//代替gets(str),可读取一行
cin.getline(str,字符数组大小)
、单词分割
、二维字符数组
AC代码
#include
#include
#include
using namespace std;
const int MAX=88;
int main(){
char str[MAX];
cin.getline(str,MAX);
char words[MAX][MAX];
int len = strlen(str);
int j=0,k=0;
for(int i=0;i0;i--){
printf("%s ",words[i]);
}
printf("%s",words[0]);
return 0;
}
在待排序列 [1,n] 中找最小的那个,与第 i 个元素 a[i] 交换。
void selectSort(){
for(int i=1;i<=n;i++){//重复n-1次
//把第一个未排序元素设为最小值
min = a[i];
k = i;
//遍历所有未排序元素a[i~n],找最小值
for(int j=i;j<=n;j++){
if(a[j]
时间复杂度: O ( n 2 ) O(n^{2}) O(n2)。
将未排序序列中的元素一次插入前面有序序列中。
void insertSort(){
//第一个元素标记为已排序
//对于所有未排序元素a[2~n]
for(int i=2;i<=n;i++){
//获取元素a[i]
int temp=a[i],j=i;
//若temp<前一个元素a[j-1],则a[j-1]后移
while(j>1 && temp=a[j-1],找到temp的位置a[j],插入
a[j] = temp;
}
}
最好时间复杂度: O ( n ) O(n) O(n);最坏时间复杂度: O ( n 2 ) O(n^{2}) O(n2);平均时间复杂度: O ( n 2 ) O(n^{2}) O(n2)。
使用sort()排序
//实现
#include
using namespace std;
sort(首元素地址(*),尾元素地址的下一个地址(*),比较函数(非必填));
//若无比较函数,默认对前面区间进行递增排序
//char类型按字典序
实现比较函数cmp
(1)基本数据类型数组排序
#include
#include
using namespace std;
bool cmp(int a,int b){
return a>b;//当a>b时,把a放在b前面
}
int main(){
int a[] = {3,1,4,2};
sort(a,a+4,cmp);
for(int i=0;i<4;i++){
printf("%d",a[i]);//4,3,2,1
}
return 0;
}
(2)结构体数组排序
#include
#include
using namespace std;
struct node{
int x,y;
}ssd[10];
//将ssd数组按x从大到小排序
bool cmp(node a,node b){
return a.x>b.x;
}
sort(ssd,ssd+3,cmp);
(3)容器排序
在 STL 标准容器中,只有 vector、string、deque 可以用 sort。
//vector !!注意起始结束!!
sort(vi.begin(),vi.end(),cmp);
//string
sort(s,s+n,cmp);
Programming Ability Test (PAT) is organized by the College of Computer Science and Technology of Zhejiang University. Each test is supposed to run simultaneously in several places, and the ranklists will be merged immediately after the test. Now it is your job to write a program to correctly merge all the ranklists and generate the final rank.
Input Specification:
Each input file contains one test case. For each case, the first line contains a positive number N (≤100), the number of test locations. Then N ranklists follow, each starts with a line containing a positive integer K (≤300), the number of testees, and then K lines containing the registration number (a 13-digit number) and the total score of each testee. All the numbers in a line are separated by a space.
2 5 1234567890001 95 1234567890005 100 1234567890003 95 1234567890002 77 1234567890004 85 4 1234567890013 65 1234567890011 25 1234567890014 100 1234567890012 85
Output Specification:
For each test case, first print in one line the total number of testees. Then print the final ranklist in the following format:
registration_number final_rank location_number local_rank 准考证号 最终排名 考场号 考场内排名
The locations are numbered from 1 to N. The output must be sorted in nondecreasing order of the final ranks. The testees with the same score must have the same rank, and the output must be sorted in nondecreasing order of their registration numbers.
9 1234567890005 1 1 1 1234567890014 1 2 1 1234567890001 3 1 2 1234567890003 3 1 2 1234567890004 5 1 4 1234567890012 5 2 2 1234567890002 7 1 5 1234567890013 8 2 3 1234567890011 9 2 4
解题思路
输入
考场数N
考场人数K1
准考证号 分数
...
考场人数Kn
准考证号 分数
...
定义一个学生结构体
struct Student{
char uid[13];
int loc_num;
int score;
int all_rank;
int loc_rank;
}st[30010];
AC代码
/*
*title:【PAT A1025】PAT Ranking
*description:
*time:2023-01-09
*/
#include
#include
#include
#include
using namespace std;
struct Student{
char uid[13];
int loc_num;
int score;
int all_rank;
int loc_rank;
}st[30010];
bool cmp(Student a,Student b){
if(a.score!=b.score)
return a.score>b.score;//分数从高到低
else
return strcmp(a.uid,b.uid)<0;//准考证号从小到大
}
int main(){
int n,k,num=0;
scanf("%d",&n);
for(int i=0;i
AC代码
!!string类型,引用
,输入输出cin>>
,cout<<
。
#include
#include
#include
#include
using namespace std;
const int MAX = 52;
string A[MAX];
bool cmp(string a,string b){
return a>b;
}
int main(){
int n;
// scanf("%d",&n);
cin>>n;
for(int i=0;i>A[i];
}
sort(A,A+n,cmp);
for(int i=0;i
一般用于对比两个数组的相同项。
将元素 k e y key key 通过一个函数 H H H 转换为整数,使该整数 H ( k e y ) H(key) H(key) 可以尽量唯一地代表这个元素。
k e y key key 是整数的情况,常用散列函数:
解决冲突的方法:
其中方法一、二都计算了新的 h a s h hash hash 值,又称为开放定址法。
一般,可使用标准库模板库中的 m a p map map 来直接使用 h a s h hash hash 的功能。
α \alpha α 假设字符串由大写字母 A~Z 组成,将 A~Z 对应 0~25,将 26 个大写字母对应到二十六进制中。
可知,将二十六进制转换成十进制是唯一的。可通过进制转换,将字符串映射成整数。(最大整数为 2 6 l e n − 1 26^{len}-1 26len−1,所以字符串长度不要太长)
int hashFunc(char s[],int len){
int id=0;
for(int i=0;i
β \beta β 假设字符串由小写字母 a~z 和大写字母 A~Z 组成,将 A~Z 对应 025,az 对应 26~51 ⇒ \Rightarrow ⇒ 将五十二进制转化成十进制
int hashFunc(char s[],int len){
int id=0;
for(int i=0;i'A'&&s[i]<'Z')
id = id*52 + (s[i]-'A');
else if(s[i]>'a'&&s[i]<'z')
id = id*52 + (s[i]-'a') + 26;
}
return id;
}
γ \gamma γ 假设出现了数字:
按照处理字母的方法,五十二进制 ⇒ \Rightarrow ⇒ 六十二进制;
如果保证字符串末尾是确定个数的数字,则可把前面字母进制转换成整数,再将末尾数字拼接上去。
int hashFunc(char s[],int len){
int id=0;
for(int i=0;i
**例:**给出 N N N 个字符串(由恰好三位大写字母组成),再给出 M M M 个查询字符串,问:每个查询字符串在 N N N 个字符串中出现的次数。
#include
#include
using namespace std;
const int MAX = 100;
char S[MAX][5],temp[5];
int hashTable[26*26*26+10];
int hashFunc(char S[],int len){
int id = 0;
for(int i=0;i>n>>m;
for(int i=0;i>S[i];
int id = hashFunc(S[i],3);
hashTable[id]++;
}
for(int j=0;j>temp;
int id = hashFunc(temp,3);
cout<
“分而治之”。将原问题划分成若干个规模较小且结构与原问题相同或相似的子问题,然后分别解决这些子问题,最后合并子问题的解,即可得到原问题的解。
子问题数为 1 称为减治,大于 1 则为分治。
**注:**子问题之间应该相互独立、没有交叉。
递归中的重要概念:
#include
using namespace std;
int F(int n){
if(n==0)
return 1;
else
return F(n-1)*n;
}
int main(){
int n;
cin>>n;
cout<
#include
using namespace std;
int F(int n){
if(n==0||n==1)
return 1;
else
return F(n-1)+F(n-2);
}
int main(){
int n;
cin>>n;
cout<
例如:1、2、3 的全排列是(1,2,3)、(1,3,2)、(2,1,3)、(2,3,1)、(3,1,2)、(3,2,1)。
解题思路
问题:“输出 1~n 这 n 个整数的全排列”
⇒ \Rightarrow ⇒ 子问题:“输出以 1 为开头的全排列”、“输出以 2 为开头的全排列”、… “输出以 n 为开头的全排列”。
设定数组 P,用于存放当前排列;设定散列数组 hashTable,其中 hashTable[x] 当整数 x 已经在数组 P 中时为 true。
代码
#include
using namespace std;
const int MAX = 11;
int n,p[MAX],hashTable[MAX]={false};
void generateP(int index){//index指在P中的位置
for(index == n+1){//递归边界,说明已完成1~n位全排列
for(int i=1;i<=n;i++){
cout<
在 n*n 的国际象棋棋盘上放置 n 个皇后,使这 n 个皇后两两均在不同行、不同列、不同对角线上,求合法的方案数。
解决思路
将棋盘每一列皇后位置(行号)取出,用全排列思路排列 1~n,即可得到均在不同行、不同列的一个皇后位置全排列。再根据两两皇后位置是否在同一对角线上判断该排列方案是否合法。
在判断方案是否合法的过程中,一旦出现非法排列,则放弃当前排列,直接返回上层,则需回溯。
**回溯法:**一般来说,如果在到达递归边界前的某层,由于一些事实导致已经不需要往任何一个子问题递归,就可以直接返回上一层。
void generateP(int index){
if(index == n+1){
count++;//所有位都合法
return;
}
for(int x=1;x<=n;x++){//x行
if(!hashTable[x]){//第x行还没有皇后
bool flag = true;//当前皇后没有与之前皇后冲突
for(int pre=1;pre
分治法
每次都对分割后的四个小方块进行判断,判断特殊方格是否在里面。这里的判断的方法是每次先记录下整个大方块的左上角方格的行列坐标,然后再与特殊方格坐标进行比较,就可以知道特殊方格是否在该块中。
如果特殊方块在里面,这直接递归下去求即可;
如果不在,这根据分割的四个方块的不同位置,把右下角、左下角、右上角或者左上角的方格标记为特殊方块,然后继续递归。
在递归函数里,还要有一个变量 s 来记录边的方格数,每次对方块进行划分时,边的方格数都会减半,这个变量是为了方便判断特殊方格的位置。
AC代码
/*
*title:sunnywhy4.3.11
*description:棋盘覆盖问题
*time:2023-01-13
*/
#include
#include
#include
#include
using namespace std;
const int MAX = (256*256-1)/3;
struct Point{
int x;
int y;
Point(){}
Point(int _x,int _y){
x = _x;
y = _y;
}
}P[MAX];
void pan(int ltr,int ltc,int cx,int cy,int size,int &i){
if(size==1){
return;
}
int n = size/2;
if(cx=ltc+n){
pan(ltr,ltc+n,cx,cy,n,i);
P[i++] = Point(ltr+n,ltc+n-1);
}else{
pan(ltr,ltc+n,ltr+n-1,ltc+n,n,i);
}
if(cx>=ltr+n&&cy=ltr+n&&cy>=ltc+n){
pan(ltr+n,ltc+n,cx,cy,n,i);
P[i++] = Point(ltr+n-1,ltc+n-1);
}else{
pan(ltr+n,ltc+n,ltr+n,ltc+n,n,i);
}
}
bool cmp(Point a,Point b){
if(a.x!=b.x){
return a.x>k>>cx>>cy;
pan(1,1,cx,cy,pow(2,k),i);
sort(P,P+i,cmp);
for(int j=0;j
总结
贪心法,指在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。常用于求解一类最优化问题。
月饼是中国人在中秋佳节时吃的一种传统食品,不同地区有许多不同风味的月饼。现给定所有种类月饼的库存量、总售价、以及市场的最大需求量,请你计算可以获得的最大收益是多少。
注意:销售时允许取出一部分库存。
样例给出的情形是这样的:假如我们有 3 种月饼,其库存量分别为 18、15、10 万吨,总售价分别为 75、72、45 亿元。如果市场的最大需求量只有 20 万吨,那么我们最大收益策略应该是卖出全部 15 万吨第 2 种月饼、以及 5 万吨第 3 种月饼,获得 72 + 45/2 = 94.5(亿元)。
输入格式:每个输入包含一个测试用例。每个测试用例先给出一个不超过 1000 的正整数 N 表示月饼的种类数、以及不超过 500(以万吨为单位)的正整数 D 表示市场最大需求量。随后一行给出 N 个正数表示每种月饼的库存量(以万吨为单位);最后一行给出 N 个正数表示每种月饼的总售价(以亿元为单位)。数字间以空格分隔。
3 20 18 15 10 75 72 45
输出格式:对每组测试用例,在一行中输出最大收益,以亿元为单位并精确到小数点后 2 位。
94.50
解决思路
求出每种月饼的单价,单价越高卖越多。
AC代码
/*
*title:PAT B1020 月饼
*description:
*time:2023-02-01
*/
#include
#include
#include
using namespace std;
struct MoonPie{
double store;//为方便计算都设置为double型变量
double sale;
double a_sale;
}mp[1010];
bool cmp(MoonPie a,MoonPie b){
return a.a_sale>b.a_sale;
}
int main(){
int n;
double d;
cin>>n>>d;
for(int i=0;i>mp[i].store;
}
for(int i=0;i>mp[i].sale;
mp[i].a_sale = mp[i].sale/mp[i].store;
}
sort(mp,mp+n,cmp);
// double w=0;
// int j=0;
// while(w
给定数字 0-9 各若干个。你可以以任意顺序排列这些数字,但必须全部使用。目标是使得最后得到的数尽可能小(注意 0 不能做首位)。例如:给定两个 0,两个 1,三个 5,一个 8,我们得到的最小的数就是 10015558。
现给定数字,请编写程序输出能够组成的最小的数。
输入格式:输入在一行中给出 10 个非负整数,顺序表示我们拥有数字 0、数字 1、……数字 9 的个数。整数间用一个空格分隔。10 个数字的总个数不超过 50,且至少拥有 1 个非 0 的数字。
2 2 0 0 0 3 0 0 1 0
输出格式:在一行中输出能够组成的最小的数。
10015558
AC代码
/*
*title:【PAT B1023】组个最小数
*description:
*time:2023-02-01
*/
#include
#include
using namespace std;
int main(){
int a[10];
for(int i=0;i<10;i++){
cin>>a[i];
}
for(int i=1;i<10;i++){
if(a[i]!=0){
cout<0){
cout<
给出 N 个开区间 (x, y),从中选择尽可能多的开区间,使得这些开区间两两没有交集。
两个区间之间存在两种情况:
参考代码
I[i].y<=lastX#include
#include
using namespace std;
const int MAX = 110;
struct Inteval{
int x,y;//左右端点
}I[MAX];
bool cmp(Inteval a, Inteval b){
//选左大右小
if(a.x!=b.x) return a.x>b.x;
else return a.y>n&&n!=0){
for(int i=0;i>I[i].x>>I[i].y;
}
sort(I,I+n,cmp);
//用ans记录不相交区间个数,lastX记录上一个被选中区间的左端点
int ans=1,lastX=I[0].x;
for(int i=1;i
给出 N 个闭区间 (x, y),求最少需要确定多少个点,才能使每个闭区间中都至少存在一个点。
同理,两个区间之间存在两种情况:
因为是闭区间只需将I[i].y<=lastX
改为I[i].y
区间选点问题实质上就是找不相交区间个数。
贪心是用来解决一类最优化问题,并希望由局部最优策略来推出全局最优结果的算法思想。
贪心算法适用的问题一定满足最优子结构性质,即一个问题的最优解可以由它的子问题的最优解有效构造出来。
二分查找是基于有序序列的查找,时间复杂度为 O ( l o g n ) O(logn) O(logn)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J6Ei3tSB-1675660390298)(0e17c94977ad4392f2bd058fe07af17b.png)]
//A[n]为严格递增序列,二分区间为[left,right]闭区间
int binarySearch(int A[], int left, int right, int x){
int mid;
while(left<=right){
mid = (left+right)/2;
if(A[mid]==x)
return mid;
else if(A[mid]>x){
right = mid-1;
}
else{
left = mid+1;
}
}
return -1;
}
binarySearch(A,0,n-1,x);
严格递减序列则将A[mid]>x
换成A[mid]
tip:若二分上界 right 超过 int 型数据范围的一半,当 x 在序列靠后位置时,mid=(left+right)/2
中left+right
可能超过 int 而导致溢出,此时可使用mid=left+(right-left)/2
代替,避免溢出。
若递增序列中元素可能重复,对于欲查询元素 x,求出序列中第一个大于等于 x 的元素位置 L 以及第一个大于 x 的元素位置 R,则 x 在序列中的存在区间就是 [L, R)。
求序列中第一个大于等于 x 的元素位置 L
//A[n]为严格递增序列,二分区间为[left,right]闭区间,初值为[0,n]
int lower_bound(int A[], int left, int right, int x){
int mid;
while(left=x){
right = mid;
}
else{
left = mid+1;
}
}
return left;
}
tips:
left而不是left<=right
,因为在严格序列中若 x 不存在,返回 -1;而在非严格序列中会返回假设 x 存在时,它所在的位置。当left==right
时意味着找到了 x 在的唯一位置,即所返回结果。
求序列中第一个大于 x 的元素位置 R
//A[n]为严格递增序列,二分区间为[left,right]闭区间,初值为[0,n]
int upper_bound(int A[], int left, int right, int x){
int mid;
while(leftx){
right = mid;
}
else{
left = mid+1;
}
}
return left;
}
可见,lower_bound()
和upper_bound()
都在寻找有序序列中第一个能满足某条件的元素位置。
//[left, right]
int solve(int left, int right){
int mid;
while(left
//(left, right]
int solve(int left, int right){
int mid;
while(left+1
如果目的是查找“序列中是否存在满足某条件的元素”,则可用二分查找解决。
以精确到 1 0 − 5 10^{-5} 10−5 为例。
已知,对于 f ( x ) = x 2 f(x)=x^{2} f(x)=x2,在 x ∈ [ 1 , 2 ] x\in[1,2] x∈[1,2] 时, f ( x ) f(x) f(x) 随 x x x 增大而增大。
令 float 型 left 和 right 初值分别为 1 和 2,根据 f ( m i d ) f(mid) f(mid) 与 2 的大小来选择子区间。
const double eps=1e-5;
double f(double x){
return x*x;
}
double calSqrt(){
double left=1,right=2,mid;
while(right-left>eps){
mid = (left+right)/2;
if(f(mid)>2) right=mid;
else left=mid;
}
return mid;
}
⟹ \Longrightarrow ⟹ 给定一个定义在 [L, R] 上的单调函数 f ( x ) f(x) f(x),求方程 f ( x ) = 0 f(x)=0 f(x)=0 的根。
const double eps=1e-5;
double f(double x){
return ......
}
double calSqrt(){
double left=L,right=R,mid;
while(right-left>eps){
mid = (left+right)/2;
if(f(mid)>0) right=mid;
else left=mid;
}
return mid;//即f(x)=0的根
}
有一个侧面看去是半圆的储水装置,该半圆的半径为 R,要求往里面装入高度为 h 的水,使其在侧面看去的面积 S 1 S_{1} S1 与半圆面积 S 2 S_{2} S2的比例恰好为 r,先给定 R 和 r,求高度 h。
S 2 = π R 2 2 , r = S 1 S 2 S_{2}=\frac{\pi R^{2}}{2},r=\frac{S_{1}}{S_{2}} S2=2πR2,r=S2S1,已知 R R R 和 r r r ,根据 S 1 = α π × S 2 − ( R − h ) × R 2 − ( R − h ) 2 S_{1}=\frac{\alpha}{\pi}\times S_{2}-(R-h)\times \sqrt{R^{2}-(R-h)^{2}} S1=πα×S2−(R−h)×R2−(R−h)2 ,其中 α = 2 × arccos ( R − h R ) \alpha=2\times \arccos(\frac{R-h}{R}) α=2×arccos(RR−h),将 r r r 当作函数 f ( h ) f(h) f(h),易知 h ∈ [ 0 , R ] h\in[0,R] h∈[0,R]。可在其范围 [ 0 , R ] [0,R] [0,R] 内对 h h h 进行二分。
#include
#include
using namespace std;
const double PI=acos(-1.0);
const double eps=1e-5;
double f(double R,double h){
double s2=PI*R*R/2;
double alpha=2*acos((R-h)/R);
double s1=alpha*R*R/2-(R-h)*sqrt(R*R-(R-h)*(R-h));
return s1/s2;
}
double solve(double R,double r){
double left=0,right=R,mid;
while(right-left>eps){
mid=(left+right)/2;
if(f(R,mid)>r) right=mid;
else left=mid;
}
return mid;
}
int main(){
double R,r;
cin>>R>>r;
cout<
快速幂基于二分的思想,也成为二分幂。快速幂基于:
这样,在 l o g ( b ) log(b) log(b) 级别次转换后, b b b 可以变为 0,而 a 0 = 1 a^{0}=1 a0=1。
例如:求 2 10 2^{10} 210。
2 10 = 2 5 × 2 5 2^{10}=2^{5}\times 2^{5} 210=25×25;
$2^{5}=2\times 2^{4}$;
$2^{4}=2^{2}\times 2^{2}$;
$2^{2}=2^{1}\times 2^{1}$;
$2^{1}=2\times 2^{0}$;
$2^{0}=1$。
//求a^b%m
typedef long long LL;
LL binaryPow(LL a, LL b, LL m){
if(b==0) return 1;
if(b%2==1) return a*binaryPow(a,b-1,m)%m;
else{
LL mul = binaryPow(a,b/2,m);
return mul*mul%m;
}
}
//不太懂为什么每次取模和最后取模结果一样,但应该是怕超过longlong长度所以要取完再乘
LL binaryPow2(LL a, LL b){
if(b==0) return 1;
if(b%2==1) return a*binaryPow2(a,b-1);
else{
LL mul = binaryPow2(a,b/2);
return mul*mul;
}
}
cout<
对 a b a^{b} ab 来说,如果把 b b b 写成二进制,则可将 b b b ‘写成若干次幂之和。例如: 1 3 10 = 110 1 2 13_{10}=1101_{2} 1310=11012,则 13 = 2 0 + 2 2 + 2 3 = 1 + 4 + 8 13=2^{0}+2^{2}+2^{3}=1+4+8 13=20+22+23=1+4+8,所以 a 13 = a 1 + 4 + 8 = a 1 ∗ a 4 ∗ a 8 a^{13}=a^{1+4+8}=a^{1}*a^{4}*a^{8} a13=a1+4+8=a1∗a4∗a8。
⟹ \Longrightarrow ⟹ 可以将任意 a b a^{b} ab 表示成 a 2 k 、 … 、 a 4 、 a 2 、 a 1 a^{2^{k}}、…、a^{4}、a^{2}、a^{1} a2k、…、a4、a2、a1 中若干项的乘积,其中,如果 b b b 的二进制 i i i 号位为 1,则 a 2 i a^{2^{i}} a2i 被选中。
计算 a b a^{b} ab 的大致思路 ⟹ \Longrightarrow ⟹ 令 i i i 从 0 到 k 枚举 b b b 二进制的每一位,如果当前位是1,则累积 a 2 i a^{2^{i}} a2i。
因为 a 2 k 、 … 、 a 4 、 a 2 、 a 1 a^{2^{k}}、…、a^{4}、a^{2}、a^{1} a2k、…、a4、a2、a1 中前一项总是等于后一项的平方,因此可以用一个 a a a 记录 a 2 k a^{2^{k}} a2k 的值。
b=13 | b&1 | ans | a |
---|---|---|---|
1 | a a a | ||
1101 | 1 | 1 ∗ a = a 1*a=a 1∗a=a | a 2 a^{2} a2 |
110 | 0 | a a a | a 4 a^{4} a4 |
11 | 1 | a ∗ a 4 = a 5 a*a^{4}=a^{5} a∗a4=a5 | a 8 a^{8} a8 |
1 | 1 | a 5 ∗ a 8 = a 13 a^{5}*a^{8}=a^{13} a5∗a8=a13 |
//求a^b%m
typedef long long LL;
LL binaryPow(LL a, LL b, LL m){
LL ans=1;
while(b>0){
if(b&1){//b%2==1
ans = ans*a%m;
}
a = a*a%m;
b>>=1;//b右移1位,即b=b/2
}
return ans;
}
two pointers,利用问题本身与序列特性,使用两个下标 i、j 对序列进行扫描,以较低复杂度解决问题。
将两个递增序列 A、B 合并为一个递增序列 C。
int merge(int A[],int B[],int C[],int n,int m){
int i=0,j=0,index=0;
while(i
原理:分解(递归实现,主要是要将序列分成 n 个归并段)、归并(算法,合并归并段)
//将两个归并段[L1,R1]、[L2,R2]合并为有序段
void merge(int A[],int L1,int R1,int L2,int R2){
int i=L1,j=L2,index=0;
int temp[maxn];
while(i
与递归相比,没有分解的动作
令步长 s t e p = 2 step=2 step=2,将数组中每 s t e p step step 个元素作为一组,对其内部进行归并排序;再令 s t e p × 2 step\times2 step×2,循环,直到 s t e p / 2 > n step/2>n step/2>n。
void mergeSort(int A[]){
for(int step=2;step/2<=n;step*=2){//step为组内元素个数,step/2为左子区间元素个数
for(int i=0;in?i+step:n-1;
merge(A,i,mid,mid+1,min);
}
}
}
}
如果只要求给出每一趟的归并序列,可用 sort() 代替 merge()
void mergeSort(int A[]){
for(int step=2;step/2<=n;step*=2){//step为组内元素个数,step/2为左子区间元素个数
for(int i=0;in?i+step:n-1;
sort(A+i,A+min+1);
}
//此处可输出每一趟
}
}
2-路归并排序的核心操作是将一维数组中前后相邻的两个有序序列归并为一个有序序列。
时间复杂度为 O ( N l o g N ) O(NlogN) O(NlogN),空间复杂度为 O ( N ) O(N) O(N),是一种稳定的排序方法。
将 A[1] 存至临时变量 temp,并令两个下标 left、right 分别指向序列首位(left=1,right=n);
while(A[right]>temp),只要条件为真,right 不断左移;直到 A[right]<=temp,将 A[right] 处的元素移到 left 处,A[left]=A[right];
while(A[left]
重复2、3直到 left==right,left 与 right 相遇,把 temp 放到相遇的地方。
//对[left,right]进行划分
int Partition(int A[],int left,int right){
int temp=A[left];
while(lefttemp) right--;//!!别忘了left
//递归实现
void quickSort(int A[],int left,int right){
if(left
快速排序算法在序列元素排列随机时效率最高,但元素接近有序时则会达到 O ( n 2 ) O(n^{2}) O(n2),主要原因是主元没有办法将当前区间划分为两个长度接近的子区间。
⟹ \Longrightarrow ⟹ 随机选择主元。
#include
#include
#include
//选择随机主元,对[left,right]进行划分
int randPartition(int A[],int left,int right){
//生成[left,right]内的随机数p
int p = (round(1.0*rand()/RAND_MAX*(right-left)+left));
swap(A[p],A[left]);
int temp=A[left];
while(lefttemp) right--;
A[left]=A[right];
while(left
将所有可能用到的结果事先计算出来,后面用到时即可直接查表获得。用空间换时间,技巧性强。
过程中可能存在递推关系,找到递推关系可降低时间复杂度。
字符串 APPAPT 中包含了两个单词 PAT,其中第一个 PAT 是第 2 位§,第 4 位(A),第 6 位(T);第二个 PAT 是第 3 位§,第 4 位(A),第 6 位(T)。
现给定字符串,问一共可以形成多少个 PAT?
输入格式:输入只有一行,包含一个字符串,长度不超过105,只包含 P、A、T 三种字母。
APPAPT
输出格式:在一行中输出给定字符串中包含多少个 PAT。由于结果可能比较大,只输出对 1000000007 取余数的结果。
2
解题思路
AC代码
/*
*title:【PAT B1040/A1093】有几个PAT
*description:
*time:2023-02-05
*/
#include
#include
using namespace std;
const int maxn=100005;
const int mod=1000000007;
//递推法
int main(){
string str;
cin>>str;
int nump[maxn];
int numt=0;
int len=str.length();
nump[0]=0;
for(int i=0;i0){
nump[i]=nump[i-1];
}
if(str[i]=='P'){
nump[i]++;
}
}
int sum=0;
for(int i=len-1;i>=0;i--){
if(str[i]=='T'){
numt++;
}
else if(str[i]=='A'){
sum = (sum+nump[i]*numt)%mod;//!!每次都要 %mod !!
}
}
cout<
根据前面的随机快速排序算法randPartition
函数,执行一次之后,主元 A[p] 左边元素个数确定且都小于主元,则 A[p] 就是 A[left,right] 中第 p-left+1 大的数。令 M=p-left+1:
int randSelect(int A[],int left,int right,int K){
if(left==right) return A[left];
int p=randPartition(A,left,right);
int M=p-left+1;
if(K==M) return A[p];
if(K
将 n n n 个整数构成的集合 A A A 划分成两个不相交的子集 A 1 A_{1} A1 和 A 2 A_{2} A2,元素个数分别为 n 1 、 n 2 n_{1}、n_{2} n1、n2,元素之和分别为 S 1 、 S 2 S_{1}、S_{2} S1、S2,要使 ∣ n 1 − n 2 ∣ |n_{1}-n_{2}| ∣n1−n2∣ 最小且 ∣ S 1 − S 2 ∣ |S_{1}-S_{2}| ∣S1−S2∣ 最大,求 ∣ S 1 − S 2 ∣ |S_{1}-S_{2}| ∣S1−S2∣。
⟹ \Longrightarrow ⟹ 求集合中第 n / 2 n/2 n/2 大的数,根据这个数把集合分为两部分,其中 A 1 A_{1} A1 中的元素都不小于这个数, A 2 A_{2} A2 中的元素都大于这个数。
/*
*title:eg 4.7.3
*description:|n1-n2|最小,|S1-S2|最大
*time:2023-02-06
*/
#include
#include
#include
#include
#include //用于round
using namespace std;
const int maxn=100010;
int A[maxn],n;
//选择随机主元,对[left,right]进行划分
int randPartition(int A[],int left,int right){
//生成[left,right]内的随机数p
int p = round(1.0*rand()/RAND_MAX*(right-left)+left);
swap(A[p],A[left]);
int temp=A[left];
while(lefttemp) right--;
A[left]=A[right];
while(left>n;
for(int i=0;i>A[i];
sum+=A[i];
}
randSelect(A,0,n-1,n/2);
for(int i=0;i