BUAA19级算法C1
A 求逆序对个数
题目描述
给定一个整数序列,求逆序对的个数
输入
第一个数为序列长度n
接下来一行,n个整数,保证在int范围内
输出
输出一行,逆序对的个数
1≤n≤1e5
其实如果有做过往年的题(毕竟14级的题基本都是公开了的) 应付这个分治的模板题应该是有手就行 到19级已经作为签到题了 作为一个老实人 我提前照着算法导论的伪代码敲了一个C代码出来 于是有备而来不到10s就a了(危)
#include <stdio.h> int n,a[1001000]; long long int sum; void Merge(int x[],int l,int p,int r){ int i; int msy1=p-l+1; int msy2=r-p; int b[msy1+2]; int c[msy2+2]; for(i=1;i<=msy1;i++) b[i]=x[l-1+i]; for(i=1;i<=msy2;i++) c[i]=x[p+i]; b[msy1+1]=2100000000; c[msy2+1]=2100000000; int bi=1; int ci=1; for(i=0;i<r-l+1;i++){ if(b[bi]<=c[ci]) x[l+i]=b[bi++]; else{ x[l+i]=c[ci++]; sum+=(msy1-bi+1); } } } void Merge_Sort(int x[],int l,int r){ if(l==r) return; int p=(l+r)/2; Merge_Sort(x,l,p); Merge_Sort(x,p+1,r); Merge(x,l,p,r); } int main(){ while(~scanf("%d",&n)){ sum=0; for(int i=0;i<n;i++) scanf("%d",&a[i]); Merge_Sort(a,0,n-1); printf("%lld\n",sum); } return 0; }
B Rotate
题目描述
H老师面向北方站在一个二维平面上。现在他站在出发点,想做若干次这样的操作:向自己面向的方向走 1米 ,然后逆时针旋转 X 度(单位是角度)。请问他最少需要做多少次这样的操作,才能重新回到出发点?
输入
多组输入数据每组数据输入一行,一个整数 X,含义见题目描述。
输出
每组数据输出一行,一个整数,表示最少操作次数。
1≤X≤179
这个题你和我说是算法题我是不信的 更应该分到程设板块里面 应该是第一次上机没啥可出的才搞了这个签到题
画画图可以知道无论你怎么转转出来的都是一个正多边形,显然可以通过外角和为360来看整除性 能整除就能到然后就a了
#include <bits/stdc++.h> using namespace std; typedef long long ll; ll n,k,a[1001000],b[1001000],cnt,ans,xx,yy; int main(){ while(~scanf("%lld",&n)){ ll aaa; for(int i=1;i<=360;i++){ if((n*i)%360==0){ aaa=i; break; } } printf("%lld\n",aaa); } return 0; }
C 小田田浇花
题目描述
有n朵花,编号0∼n−1,一开始都是干旱状态。每次操作会都使[L,R]区间内的花朵状态发生改变,也就是说,对于花i,L≤i≤R,如果i是干旱状态,则小田田会浇水让花变成正常状态,而如果i是正常状态,会由于天气原因变为干旱状态。请问k次操作后有多少花是正常状态。
输入
多组输入数据每组数据第一行两个数n,k(1≤n≤1e9,1≤k≤1e5)接下来k行,每行两个数l,r(0≤l≤r≤n−1)
输出
每组数据一行一个数,表示最后正常状态的花的个数
Hint
排序+思维
18级的原题 爽歪歪 还是一个画图题 如果给出的所有线段都不相交这个问题就很显然了 但如果存在两个区间是相交的 无非就是一下两种情况 [1,4]和[2,3]以及[1,3]和[2,4] 也就是说有可能一个线段完全包含了另一条线段 或者没完全包含 但这两种情况都可以变成[1,1]和[4,4]其实就是把这些数无论是左端点还是右端点都排一个序 然后相邻做差加和就可以了 但是有一个小坑 我们需要把右端点都+1 不然做差的值并不是真正的数量
#include <bits/stdc++.h> using namespace std; typedef long long ll; ll n,k,a[1001000],cnt,ans,xx,yy; int main(){ while(~scanf("%lld%lld",&n,&k)){ ans=0; cnt=0; for(int i=1;i<=k;i++){ scanf("%lld %lld",&xx,&yy); a[cnt++]=xx; a[cnt++]=yy+1; } sort(a,a+2*k); for(int i=1;i<cnt;i+=2) ans+=a[i]-a[i-1]; printf("%lld\n",ans); } return 0; }
D HolmiumTS的羊羊牧场
题目描述
HolmiumTS在Minecraft中非常缺少羊毛(问就是GTNH),所以他准备建立一个很大很大的羊羊牧场来提供足够的羊毛。在Minecraft中,一只在第i天出生的小羊在第i+1天就可以长大,成为成年羊。每一对成年羊每天可以产下一只小羊,但单独的一只成年羊是不可以产小羊的。HolmiumTS在第0天带来了m只小羊,那么第n天结束之后,HolmiumTS一共会有多少只羊(包括小羊和成年羊)呢?
输入
第一行包含一个正整数,为数据组数t接下来t行,每行2个整数,分别为n,m
输出
对于每组数据,输出一行,包含一个整数,为所求的结果
数据范围与约定
1<=t<=100
0<=n,m<=1e8
保证结果小于1e6
提示
如果TLE了,注意输入可以有0,想一想数据实际可能的范围,如何判断最坏情况。
作为一道水题 当时竟然被提示吓破了胆 下课之后做了一下气的自己扇自己耳光... 借助C语言的除法舍入+特判可以迅速a掉...
#include <bits/stdc++.h> int n,m; int main(){ int t; scanf("%d",&t); for(int i=0;i<t;i++){ scanf("%d %d",&n,&m); if(m==0){ printf("0\n"); continue; } if(m==1){ printf("1\n"); continue; } for(int j=0;j<n;j++) m=m*3/2; printf("%d\n",m); } return 0; }
E 最大子数组问题
题目描述
假定你获得了投资挥发性化学公司的机会。与其他公司一样,该公司的股票价格是不稳定的。你被准许可以在某个时刻买进一股该公司的股票,并在之后的某个时期将其卖出。你可以了解股票将来的价格,使得自己的利益最大化。你的任务是根据每天的股票价格,求得最大的收益。
输入
一个整数n,表示天数接下来一行,n个整数,用空格隔开,表示每天的股价。
输出
一个整数,表示最大收益。
数据范围与约定
n < 1e6
这个题的做法已经很明显了 题目都写出来了... 我们可以将整个数组差分 再求一个最大子段和 子段和显然就是某一个时段的价格-之前时段的价格啦~这样就可以时间O(n)搞这个题 赛后我觉得这个题还可以出得更搞人 比如我把内存限制搞到4096kb就又可以搞死一批人 于是我们需要一个空间O(1)的做法 这样我的代码就可以派上用场了(√)跑得飞快而且内存超小~
/* Author: lnx的小迷弟 Result: AC Submission_id: 3196942 Created at: Sun Dec 27 2020 16:50:34 GMT+0800 (China Standard Time) Problem_id: 3544 Time: 325 Memory: 1688 */ #include <stdio.h> typedef long long ll; ll n,ans=-99999ll,tmp,tmp1,tmp2,tmpans; int main(){ scanf("%lld",&n); scanf("%lld",&tmp1);//先输入一个数方便后边差分 for(ll i=2;i<=n;i++){ scanf("%lld",&tmp); tmp2=tmp;//存住这个值 方便之后赋值给tmp1 tmp-=tmp1;//边输入边差分 if(tmpans>0) tmpans+=tmp;//tans是包括这个数的最大子段和 else tmpans=tmp;//小于0还不如就算他自己 if(tmpans>ans) ans=tmpans;//大于就更新答案 tmp1=tmp2;//方便之后差分 } printf("%lld",ans); return 0; }
F 超体 - 简单版
题目描述
要么永生,要么繁殖。在环境舒适的 X星球,细胞们选择了繁殖,并且把知识一代一代传承下去。在 t=1秒时,有一个新生的细胞在混沌中诞生了。对于每个细胞,当它诞生 a秒后就会变得成熟。每个成熟的细胞每秒都会分裂一次(包括它刚刚成熟的那一时刻)。每次分裂会产生一个新生细胞。每个细胞在它的成熟期只能分裂 b次。在最后一次分裂之后,细胞会立刻进入衰老期。进入衰老期再过 c秒后,细胞会立刻死亡。在 t=T秒时,你能计算出 X星球一共有多少活着的细胞吗?
输入
一行,仅四个整数,相邻整数间由一个空格隔开,四个整数分别是上文中的 a,b,c,T.
输出
一行,仅一个整数,在 t=T 秒时活着的细胞的总数。保证答案在 long long 范围内。
数据范围与约定
a,b,c,T∈[1,10]
程设原题不解释 没啥技术含量期末也必不会考
#include<stdio.h> #include<string.h> #include<math.h> #include<stdlib.h> #define maxn 10005 long long int x,y,z,q; long long int luan[maxn]; long long int zhuang[maxn]; long long int cha[maxn]; long long int lao[maxn]; int main(){ int i; scanf("%lld%lld%lld%lld",&x,&y,&z,&q); if(q==1){ printf("1\n"); return 0; } luan[1]++; luan[x+1]--; for(i=2;i<=q;i++){ luan[i]+=luan[i-1]; lao[i]+=lao[i-1]; zhuang[i]+=zhuang[i-1]; if(zhuang[i]!=zhuang[i-1]){ long long int temp=zhuang[i-1]-zhuang[i]; lao[i]+=temp; lao[i+z]-=temp; } if(luan[i]!=luan[i-1]){ long long int temp=luan[i-1]-luan[i]; luan[i]+=temp; luan[i]+=zhuang[i]; luan[i+x]-=temp; luan[i+x]-=zhuang[i]; zhuang[i]+=temp; zhuang[i+y]-=temp; } if(i==q) printf("%llu\n",(unsigned long long)lao[i]+(unsigned long long)zhuang[i]+(unsigned long long)luan[i]); } return 0; }
G 绕来绕去曲线
题目描述
这条曲线的形式定义是递归的。第i个曲线可以通过以下方式连接四个(i-1)个曲线来形成:将网格划分为四个象限;在主对角线上(从左上方到右下方)反转第(i-1)个曲线,并将其放置在左上象限中;将第(i-1)个曲线的两个副本分别放在左下象限和右下象限中;跨次对角线(从右上到左下)反转第(i-1)个曲线,并将其放置在右上象限中。前三个曲线图象如下所示。
现在沿该曲线对点进行编号,第i个曲线上的点依次编号为1∼22i那么,请问第n个曲线上的两点a,b之间的距离的平方。(所有点都在方格中心,方格边长为1)
输入
多组输入数据。每组一行,三个数字n,a,b,分别表示第n个曲线上的两点a,b的编号(1≤n≤31,1≤a,b≤2^2n
)
输出
每行一个整数,表示ab两点之间的距离。
18级原题美滋滋 通过看前后的曲线发现很明显是递归问题(不要嫌我废话多...) 难点在于怎么去写递归 (废话怎么这么多...) 注意到后一个曲线可以分为左上左下右下和右上四个区域我们只需要分别看这四个部分和前一个曲线的关系即可第一部分是将前一个曲线的横纵坐标颠倒(前曲线向右==后曲线向下)所以只需要寻找到前一个曲线同样编号下的坐标再对坐标xy进行交换即可 第二部分和前一个曲线的关系是向下平移了半个现在的边长所以只需要寻找前一个曲线中的坐标加上半个边长即可 第三部分同理 需要横纵坐标都加半个边长 第四部分和前一个曲线的关系比较复杂 但是通过找规律可以发现前曲线向右=后曲线向上,即前一个的横坐标与后一个的纵坐标相关 故先递归找到前面曲线中该点坐标,再交换横纵坐标值 此时相当于前曲线向下==后曲线向上,可知两者纵坐标相加之和为前一个图形边长+1 而横坐标之和为2倍前曲线边长+1(具体可以参照题干中的第二个图和第三个图的第四部分)
#include <bits/stdc++.h> typedef long long ll; using namespace std; long long int n,a,b; long long int sum; typedef pair<ll,ll> point; ll dis(point a,point b){ return (a.first-b.first)*(a.first-b.first)+(a.second-b.second)*(a.second-b.second); } point findpos(ll n,ll x){ long long int bc=1<<(n-1); if(n==1){ if(x==1) return {1,1}; else if(x==2) return {1,2}; else if(x==3) return {2,2}; else if(x==4) return {2,1}; } point ans; if(x<=bc*bc){ ans=findpos(n-1,x); ll fff=ans.first; ll sss=ans.second; ans.first=sss; ans.second=fff; return ans; } else if(x<=2*bc*bc){ ans=findpos(n-1,x-bc*bc); ans.second+=bc; return ans; } else if(x<=3*bc*bc){ ans=findpos(n-1,x-2*bc*bc); ans.second+=bc;ans.first+=bc; return ans; } else if(x<=4*bc*bc){ ans=findpos(n-1,x-bc*bc*3); ll fff=ans.first; ll sss=ans.second; ans.first=sss; ans.second=fff; ans.second=bc-ans.second+1; ans.first=2*bc-ans.first+1; return ans; } } int main(){ while(~scanf("%lld %lld %lld",&n,&a,&b)){ point aaaa=findpos(n,a); point bbbb=findpos(n,b); printf("%lld\n",dis(aaaa,bbbb)); } return 0; }
H pair
题目描述
c++中有一个非常方便的结构体pair<type,type>现在我们定义一个合法的,只有pair和int构成的pair如下:当且仅当type为int或者pair<type,type>时,pair<type,type> 合法例如pair<int,int>和pair<pair<int,int>,int>是一个合法的pair现在给你多个pair和int字符串,按照给出pair和int的顺序,添加'<' , '>' , ','这三个符号,使得给出的串成为一个合法的pair<type,type>。(如果没有pair,只有一个int,是不合法的)如果不行,输出Error occurred
输入
第一行一个数字 t表示数据组数。每组数据两行,第一行一个数字 n表示字符串个数,第二行 n 个字符串,只会是pair或者int。
输出
一行一个字符串
在程设考递归还好 在算法课再看这个真的想吐.jpg
#include <bits/stdc++.h> typedef long long ll; using namespace std; long long int n,a,b,num,nmb; long long int sum; string s[10010]; string fu[10010]; bool ren(string *s); bool work(string *s); bool ren(string *s){ if(s[num]=="int"){ fu[nmb++]="int"; return true; } if(s[num]=="pair"&&work(s)) return true; return false; } bool work(string *s){ if(s[num]=="pair"){ num++; fu[nmb]="pair"; nmb++; fu[nmb]="<"; nmb++; if(ren(s)){ num++; fu[nmb]=","; nmb++; if(ren(s)){ fu[nmb]=">"; nmb++; return true; } } } return false; } void print(){ for(int i=0;i<nmb;i++) cout<<fu[i]; printf("\n"); } int main(){ scanf("%lld",&n); ll m; for(int i=0;i<n;i++){ num=0;nmb=0; scanf("%lld",&m); for(int j=0;j<m;j++) cin>>s[j]; if(work(s)&&num==m-1) print(); else printf("Error occurred\n"); } return 0; }
I 动态逆序对
题目描述
对于序列 a,它的逆序对数定义为集合中 i<j 且 ai>aj 的元素对数。现在给出一个长度为n,元素为1到n的排列的序列a,按照某种顺序依次删除 m 个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。
输入
第一行包含两个整数 n 和 m,即初始元素的个数和删除的元素个数。 以下 n 行,每行包含一个 1∼n 之间的正整数,即初始排列。 接下来 m 行,每行一个正整数,依次为每次删除的元素。
输出
输出包含m行,依次为删除每个元素之前,逆序对的个数。
这边建议直接移步到https://www.luogu.com.cn/problem/P3157
C1就cdq分治无疑是搞人心态的
J 寻找周思明
题目描述
此题只能使用C语言提交
前苏联时期生产出的最强作品,失踪已久的实验体AD3,美国军方最近突然发现其与一个名叫周思明的普通高中生高度相似,因此决定派你前往调查。你手上有一台机器,可以显示一个人的战斗力,通过前期调查,你得到了包含目标在内的n个人的战斗力。此时你得到了一个重要信息,周思明的战斗力在这n个的中排名第k大。现在你需要向上级汇报,周思明的战斗力是多少。
输入
第一行,包含一个正整数,为数据的组数T。接下来包含T组数据,每组数据包含两行:第一行有两个正整数n,k,分别对应人数和周思明的战斗力排名。(保证n>=k)第二行包含n个正整数a1,a2,...,an,分别为这n个人的战斗力。
输出
对于每组数据,输出一行,包含一个正整数,为周思明的战斗力。
对于所有测试点,都有0<ai<=1e8,每个测试点的分值都为10分。
测试点1~5,每个测试点内满足∑n<=1e5
测试点6~8,每个测试点内满足∑n<=1e7,且数据内的所有ai为随机生成,即你可以认为你的算法不会遇到最坏情况
测试点9,10,每个测试点内满足∑n<=1e7,数据为助教精心构造,你的算法应当能够处理可能的极端数据
我大意了 没想到算法课还带这样的 之前特意准备了nth_element没办法 硬着头皮搞 结果忘了C语言语法疯狂ce 下课回寝室疯狂提交终于ac结果最后告诉我qsort可以水过???wrml...
个人的解答有两种 一种是照着算法导论的另一种O(n)神仙做法的伪代码敲了一个 发现还可以 另一种就是快排思想了 具体看代码 如果没有真正理解快排的思想建议回去好好看看
#include <stdio.h> typedef long long ll; long long int n,a[1001000],b,num,nmb,m; long long int sum; ll t; ll quicksort(ll l,ll r){ if(l==r) return a[l]; ll temp=a[l]; ll num=l+rand()%(r-l+1); a[l]=a[num]; a[num]=temp; ll i,j,x=a[l]; if(l<r){ i=l; j=r; while(i<j){ while(i<j&&a[j]<x) j--; if(i<j) a[i++]=a[j]; while(i<j&&a[i]>x) ++i; if(i<j) a[j--]=a[i]; } a[i]=x; if(i==(m-1)) return a[i]; if(i<(m-1)) return quicksort(i+1,r); else return quicksort(l,i-1); } } int main(){ srand((int)time(NULL)); scanf("%lld",&t); int i,j; for(i=0;i<t;i++){ scanf("%lld %lld",&n,&m); for(j=0;j<n;j++) scanf("%lld",&a[j]); printf("%lld\n",quicksort(0,n-1)); } return 0; }
另一种做法
#include <stdio.h> int a[10010000]; int n,k; void wr(int x) { if (x < 0)putchar('-'), x = -x; if (x > 9)wr(x / 10); putchar(x % 10 + 48); } int rd() { int k = 0, f = 1; char c = getchar(); while (c < '0' || c>'9') { if (c == '-')f = 0; c = getchar(); } while (c >= '0' && c <= '9') { k = (k << 1) + (k << 3) + c - 48; c = getchar(); } return k * f; } int select(int a[], int l, int r, int k){ int group; int i; int left,right,mid; int pivot; int p,left_num; if (r-l+1 <= 5) { insertsort(a,l,r); return a[l+k-1]; } group = (r-l+1+5)/5; for(i=0; i<group; i++) { left = l+5*i; right = (l+5*i+4) > r?r:l+5*i+4; //超出右便捷就使用右边界赋值 mid = (left+right)/2; insertsort(a,left,right); //将各组中位数与前i个元素互换位置,方便递归select寻找中位数的中位数 swap(a,l+i,mid); } pivot = select(a,l,l+group-1,(group+1)/2); // 找出中位数的中位数 // 用中位数的中位数作为基准的位置 p = partition(a,l,r,pivot); left_num = p-l; if(k == left_num+1) return a[p]; else if(k<=left_num) //k在低区 return select(a, l, p-1, k); else //k在高区 return select(a, p+1, r, k-left_num-1); } int partition(int a[],int p,int r,int pivot){ int i,pos; for(i=p;i<=r;i++){ if(a[i]==pivot){ pos=i; break; } } int temp=a[p]; a[p]=a[pos]; a[pos]=temp; while(p<r){ while(p<r){ if(a[r]>pivot) r--; else{ a[p++]=a[r]; break; } } while(p<r){ if(a[p]<pivot) p++; else{ a[r--]=a[p]; break; } } } a[p]=pivot; return p; } // 插入排序 void insertsort(int a[], int low, int high){ int i,j; int key; for(i=low+1; i<=high; i++) { key = a[i]; for (j=i-1;j>=low&&key<a[j];j--) { a[j+1] = a[j]; } a[j+1] = key; } } void swap(int a[], int i, int j){ int tmp=a[i]; a[i] = a[j]; a[j] = tmp; } int main(){ srand((int)time(NULL)); int t,i,j; t=rd(); for(i=1;i<=t;i++){ n=rd(); k=rd(); for(j=0;j<n;j++) a[j]=rd(); wr(select(a,0,n-1,n+1-k)); printf("\n"); } return 0; }