《算法笔记上机实验指南》第4章 入门篇(2)第13章 专题扩展

it2025-02-09  12

A1057

//分块数组 #include<cstdio> #include<cstring> #include<stack> #include<iostream> #include<string> using namespace std; /* 记住第k大和第k小是一样的,没有区别 */ const int maxn=100010; const int sqrN=316; //sqrN表示每块中的最大数值,其实就是根号n,n表示数组大小 stack<int> st; int block[sqrN]; //block表示每一块的数量 比如block[0]的元素值表示第0块中的元素数量 block[1]表示第1块中的元素数量 int table[maxn]; //table表示某个元素的数量 比如 table[1]=2表示1这个元素有2个 //下面这个函数是先找块,看第k大的数在那一个块,然后在某一个块中,去寻找是某个元素,使统计的数sum>=k,表示已经找到第k个大数,而且当前的num即为所找到的第k大数 void peekMedian(int K) //分块的函数,虽然是找到第k大数,但是根不不需要进行排序,就按照这道题中,所有的元素都在第0块,在table数组中,注意num表示当前块的第1个元素,对于第0块而言就是从0---315,而table[num]表示num有几个,所以已经是经过排序的 { int sum=0; //sum表示统计到的个数 int idx=0; //idx表示访问的第几个块 while(sum+block[idx]<K) { sum+=block[idx++]; } int num=idx*sqrN; //num表示当前块的第1个元素 while(sum+table[num]<K) { sum+=table[num++]; } printf("%d\n",num); } void Push(int x) { st.push(x); block[x/sqrN]++; //通过push,统计block和table的值 table[x]++; } void Pop() { int x=st.top(); st.pop(); block[x/sqrN]--; //通过pop,也是更新block和table的值 table[x]--; printf("%d\n",x); } int main() { int x,query; memset(block,0,sizeof(block)); memset(table,0,sizeof(table)); string cmd; cin >> query; for(int i=0; i<query; i++) { cin >> cmd; if(cmd=="Push") { scanf("%d",&x); Push(x); } else if(cmd=="Pop") { if(st.empty()==true) { printf("Invalid\n"); } else { Pop(); } } else { if(st.empty()==true) { printf("Invalid\n"); } else { int K=st.size(); //统计第k个大数 if(K%2==1) K=(K+1)/2; else K=K/2; peekMedian(K); } } } return 0; } //树状数组 #include<iostream> #include<stack> #include<cstring> using namespace std; #define lowbit(i) ((i)&(-i)) const int maxn=100004; stack<int> s; int n; int c[maxn]; //树状数组就是统计左边比该数小 void update(int x, int v) //该函数将第x个数加上v { for(int i=x; i<maxn; i+=lowbit(i)) { c[i]+=v; } } int getNum(int x) //返回前x个整数和 { int sum=0; for(int i=x; i>0; i-=lowbit(i)) { sum+=c[i]; } return sum; } //上面两个代码共同表示统计左边比x小的数字的个数 int PeekMedian() //通过二分查找法寻找第k大数 { int l=1,r=maxn,k=(s.size()+1)/2,mid; //明显就是l这个数字,l从1开始,r从maxn开始表示的就是数字,极可能为第k大数 while(l<r) { mid=(l+r)/2; if(getNum(mid)>=k) //getNum(mid)表示返回从左边开始比mid小的数字的个数,如果大于等于k,则表示所找的数字在左半边,否则在右半边 r=mid; else l=mid+1; } cout << l << endl; //最终找到合适的数字并输出 } int main() { cin >> n; string temp; int temp1; memset(c,0,sizeof(c)); for(int i=0; i<n; i++) { cin >> temp; if(temp=="Push") { cin >> temp1; s.push(temp1); update(temp1,1); //将每个输入的数字,都在c中标记为1 } else if(temp=="Pop") { if(s.empty()) { cout << "Invalid" << endl; } else { int x=s.top(); cout << x << endl; s.pop(); update(x,-1); } } else { if(s.empty()) { cout << "Invalid" << endl; } else { PeekMedian(); } } } }

A1105

#include<iostream> #include<cmath> #include<algorithm> using namespace std; const int maxn=10004; int mar[maxn][maxn]; int n; int a[maxn]; bool cmp(int a,int b) { return a>b; } int main() { cin >> n; for(int i=0; i<n; i++) cin >> a[i]; if(n==1) //如果n为1的话,需要特判 { cout << a[0] << endl; return 0; } int m=(int)ceil(sqrt(1.0*n)); //ceil表示向上取整函数 while(n%m!=0) //上面那个是对n进行开根号,然后取整数,不一定能被n整除,下面这个while就是为了寻找能整除n的值 m++; sort(a,a+n,cmp); int N=n/m; int nai=1,zi=m,pi=1,gu=N,i=1,j=1; int num=0; while(num<n) { while(j<gu && num<n) { mar[i][j]=a[num++]; j++; } while(i<zi && num<n) { mar[i][j]=a[num++]; i++; } while(j>pi && num<n) { mar[i][j]=a[num++]; j--; } while(i>nai && num<n) { mar[i][j]=a[num++]; i--; } nai+=1; zi-=1; pi+=1; gu-=1; i++; j++; if(num==n-1) //对于平方的矩阵而言,螺旋矩阵到最后就是中间那个值需要特殊处理 mar[i][j]=a[num++]; } for(int i=1; i<=m; i++) { for(int j=1; j<=N; j++) { if(j!=1) cout << " "; cout << mar[i][j]; } cout << endl; } return 0; }

A1017

#include<iostream> #include<vector> #include<algorithm> using namespace std; struct node { int begintime; int servetime; }; const int maxn=10003; const int INF=10000000; vector<node> Node; int endTime[maxn]; int n,k; int vert(int h, int m, int s) { return h*3600+m*60+s; } bool cmp(node a, node b) { return a.begintime<b.begintime; } int main() { cin >> n >> k; int temp1,temp2,temp3,temp4,temp5,temp6; int bankbegin=vert(8,0,0); int bankend=vert(17,0,0); for(int i=0; i<k; i++) endTime[i]=bankbegin; for(int i=0; i<n; i++) { scanf("%d:%d:%d %d",&temp1,&temp2,&temp3,&temp4); temp5=vert(temp1,temp2,temp3); if(temp5>bankend) continue; temp6=temp4<=60?temp4*60:3600; node temp7; temp7.begintime=temp5; temp7.servetime=temp6; Node.push_back(temp7); } sort(Node.begin(),Node.end(),cmp); int all=0; for(int i=0; i<Node.size(); i++) { int idN=0,MIN=INF; for(int j=0; j<k; j++) { if(endTime[j]<MIN) { MIN=endTime[j]; idN=j; } } if(endTime[idN]<=Node[i].begintime) //表示人的开始时间晚于当前窗口最早结束的时间 { endTime[idN]=Node[i].begintime+Node[i].servetime; //则当前最早结束时间为人的到达时间+人的服务时间 } else //表示人的开始时间早于当前窗口的最早结束时间,也就是人需要等待 { all+=(endTime[idN]-Node[i].begintime); //等待时间为窗口最早结束时间减去人的到达时间 endTime[idN]+=Node[i].servetime; //则修改当前窗口最早结束时间,则当前窗口最早结束时间在原有的基础上直接增加人的服务时间 } } printf("%.1f",all/60.0/Node.size()); //直接输出平均等待时间,以分钟为单位,保留一位小数 return 0; }

A1014

#include<cstdio> #include<queue> #include<algorithm> #include<iostream> using namespace std; const int maxn=1111; int n,m,k,query,q; int convertTime(int h,int m) //将所有时间转化为分钟 { return h*60+m; } struct window //窗口设置为结构数组 { int endtime,poptime; //窗口当前队伍的最后服务时间,队首客户的服务结束时间 queue<int> q; //队列,这个队列下面放着他的所有用户 }window[20]; int ans[maxn],needTime[maxn]; //每个人所对应的服务结束时间和服务需要时间 int main() { int index=0; //当前第一个未入队的客户编号 //窗口数,窗口人数上限,客户数,查询数 scanf("%d%d%d%d",&n,&m,&k,&query); for(int i=0; i<k; i++) //从0开始依次输入每个客户的服务时间 { cin >> needTime[i]; //服务需要的时间 } for(int i=0; i<n; i++) //对窗口进行初始化, 将所有窗口对应的当前队伍的结束时间和当前队首元素的结束时间均设置为8:00 { window[i].poptime=window[i].endtime=convertTime(8,0); //将值都初始化为8点 } for(int i=0; i<min(n*m,k); i++) //将黄线内的人数先输入进去 { //循环入队 window[index%n].q.push(index); //index从0开始,index%n就是把人放到对应的窗口下,将人的编号放到对应窗口下的队列中 window[index%n].endtime+=needTime[index]; //每个窗口的最晚结束时间,就是一直列加当前队列中每个人的服务时间 if(index<n) //当n为2时,index为人数编号,此时窗口数为n,所以就是设置第1批当前队首元的结束时间 window[index].poptime==needTime[index]; //设置第1批人的窗口结束服务的时间 ans[index]=window[index%n].endtime; //每个人的窗口结束时间为当前窗口对应的最晚结束时间 index++; } //上面和下面的代码思路是一样的,只不过上面主要处理黄线前的人,而后面主要处理黄线外等待的人 for(; index<k; index++) //index是当前的编号,k是人数,上面这是把在黄线内的人设置完,现在处理黄线外的, { int idx=-1,minpoptime=1<<30; //idx表示当前窗口的编号 for(int i=0; i<n; i++) //for循环寻找当前窗口下的队列的人的最早结束时间 { if(window[i].poptime<minpoptime) { idx=i; minpoptime=window[i].poptime; } } window[idx].q.pop(); window[idx].q.push(index); window[idx].endtime+=needTime[index]; window[idx].poptime+=needTime[window[idx].q.front()]; //此处的队首元素的第1个结束时间必须是在原有的基础上向后推进 ans[index]=window[idx].endtime; //更新每个人的结束是按 } for(int i=0; i<query; i++) { cin >> q; if(ans[q-1]-needTime[q-1]>=convertTime(17,0)) //如果一个人最晚结束时间减去他的最早结束时间是超过17.00则输出sorry { cout << "Sorry" << endl; } else { printf("%02d:%02d\n",ans[q-1]/60,ans[q-1]%60); } } return 0; }

A1026

#include<iostream> #include<cmath> #include<vector> #include<algorithm> using namespace std; const int K=111; //窗口数 const int INF=1000000000; //无穷大 struct Player //给球员设置PLayer { int arriveTime,startTime,trainTime; //到达时间,训练开始时间以及训练时长 bool isVIP; //是否是vip球员 }newPlayer; struct Table //给桌子也设置一个VIP判断 { int endTime,numServe; //当前占用该桌子的球员结束时间,以及 已训练的人数 bool isVIP; //是否为vip桌子 }table[K]; vector<Player> player; //给player设置结构类型的vector int convertTime(int h, int m, int s) //将时间转化为秒方便计算 { return h*3600+m*60+s; } bool cmpArriveTime(Player a, Player b) //按照到达时间的升序排列 { return a.arriveTime<b.arriveTime; } bool cmpStartTime(Player a, Player b) //按照开始时间排序 { return a.startTime<b.startTime; } int nextVIPPlayer(int VIPi) //当前VIPi的球员 { VIPi++; while(VIPi<player.size() && player[VIPi].isVIP==0) //只要不是vip球员则直接走下一个 { VIPi++; } return VIPi; } void allotTable(int pID, int tID) { if(player[pID].arriveTime<=table[tID].endTime) //如果球员的到达时间早于桌子的结束时间,说明球员需要等待 { player[pID].startTime=table[tID].endTime; //球员开始训练的时间为桌子结束的时间 } else //否则,说明桌子等着球员 { player[pID].startTime=player[pID].arriveTime; //则球员的开始训练时间即为到达时间,也就是一来就开始训练 } table[tID].endTime=player[pID].startTime+player[pID].trainTime; //桌子结束的时间即为球员开始训练的时间+训练时长 table[tID].numServe++; //桌子的服务人数自增1 } int main() { int n,k,m,VIPtable; //n表示人数 cin >> n; int stTime=convertTime(8,0,0); //将时间转化为秒 int edTime=convertTime(21,0,0); for(int i=0; i<n; i++) { int h,m,s,trainTime,isVIP; scanf("%d:%d:%d %d %d",&h,&m,&s,&trainTime,&isVIP); newPlayer.arriveTime=convertTime(h,m,s); //将时间转化为秒 if(newPlayer.arriveTime>=edTime) //如果到达时间在21:00之后直接退出 continue; newPlayer.startTime=edTime; //!!!注意这里初始化训练开始时间必须为edTime,因为在本题中,如果设置为stTime,则在后面的while语句中 newPlayer.trainTime=trainTime<=120?trainTime*60:7200; //如果训练时间小于120min直接转化为秒,否则压缩为7200 newPlayer.isVIP=isVIP; player.push_back(newPlayer); } cin >> k >> m; //输入总的桌子数,vip桌子数 for(int i=1; i<=k; i++) { table[i].endTime=stTime; //将桌子的初始值设置为开始时间 table[i].numServe=table[i].isVIP=0; } for(int i=0; i<m; i++) //将vip桌子的isVIP标记为1 { cin >> VIPtable; table[VIPtable].isVIP=1; } sort(player.begin(),player.end(),cmpArriveTime); //按到达时间排序 int i=0,VIPi=-1; VIPi=nextVIPPlayer(VIPi); //编号VIPi从当前VIP球员移到下一个VIP球员 找到第一个VIP //上面代码就是在输入信息,将人的结构和桌子的结构设置起来,目前VIPi就是当前第一个VIP while(i<player.size()) //遍历n个球员 { int idx=-1,minEndTime=INF; for(int j=1; j<=k; j++) //寻找当前最早结束的桌子 { if(table[j].endTime<minEndTime) { minEndTime=table[j].endTime; idx=j; } } if(table[idx].endTime>=edTime) //如果当前最早结束的桌子的时间在晚上关门时间之后,则后面的人不用来了,所以直接退出循环,就如同例子中,后面,最早结束的桌子是21:00但是8号来了,但是21:00关门所以直接退出循环 break; //对于20.53来的那个货,由于当前桌子的最早结束时间已经超过了21.00所以直接退出循环 if(player[i].isVIP==1 && i<VIPi) //VIPi表示当前所指向的vip球员,i<VIPi表示当前的vip球员已经在训练 { i++; // continue; //continue是用在循环中的,跳过后面的语句 } //以下是按照球桌是否是vip,球员是否是vip所进行的4中情况 if(table[idx].isVIP==1) //如果是vip桌子 { if(player[i].isVIP==1) //如果球员是vip球员 { allotTable(i,idx); //如果桌子是vip桌子,并且球员是vip球员,则直接分配桌子 //如果VIPi是i,这vip球员就是当前访问的球员 VIPi=nextVIPPlayer(VIPi); //寻找下一个vip球员 i++; //开始下一个人 } else //如果桌子是vip桌子,并且球员是普通球员 { //如果后面的vip球员的到达时间是在桌子的结束时间之前,则把桌子给vip,然后vip需要走到下一个,然后队首成员不变 if(VIPi<player.size() && player[VIPi].arriveTime<=table[idx].endTime) //如果该vip球员的到达时间是早于vip桌子的结束时间,则将该桌子分配给vip球员 { //因为在上面的代码中,已经让寻找下一个vip了,所以不用i++ allotTable(VIPi,idx); //将该桌子分配给vip球员 VIPi=nextVIPPlayer(VIPi); //寻找下一个vip } else //如果vip球员的到达时间晚于桌子的结束时间,则将该vip桌子直接分配给队首成员 { allotTable(i,idx); i++; } } } else //桌子不是vip球桌 { if(player[i].isVIP==0) //球桌不是vip,球员不是vip,直接将桌子分配 { allotTable(i,idx); //i号球员开始训练,递推下一个 i++; //i往后继续循环 } else //球桌不是vip,但是球员是vip { //找到最早空闲的球桌 int VIPidx=-1,minVIPEndTime=INF; for(int j=1; j<=k; j++) //找到当前最早结束的vip桌子 { if(table[j].isVIP==1 && table[j].endTime<minVIPEndTime) { minVIPEndTime=table[j].endTime; VIPidx=j; } } //如果能找到最早空闲的vip桌子,并且球员的到达时间是晚于桌子的结束时间 if(VIPidx!=-1 && player[i].arriveTime>=table[VIPidx].endTime) { allotTable(i,VIPidx); //将该vip桌子分配给该vip球员 //找到下一个vip球员 VIPi=nextVIPPlayer(VIPi); //找到下一个vip球员 i++; //i号球员开始训练 } else //否则的话,将桌子分配给当前队列的第一个人,说明vip到达的时间是早于vip桌子的结束时间,则直接把vip桌子分配给当前队列的第一个人 { allotTable(i,idx); //找到一下个vip球员 VIPi=nextVIPPlayer(VIPi); i++; } } } } sort(player.begin(),player.end(),cmpStartTime); //按照球员开始训练时间的升序进行排列 for(i=0; i<player.size() && player[i].startTime<edTime; i++) //输出球员的到达时间,开始训练时间,等待时间 { //如果设置每个人的开始时间没stTime,则就会把最后一个人算上,因为此时他的stTime为上面初始化的8.00 int t1=player[i].arriveTime; int t2=player[i].startTime; printf("%02d:%02d:%02d ",t1/3600,t1%3600/60,t1%60); printf("%02d:%02d:%02d ", t2/3600,t2%3600/60,t2%60); printf("%.0f\n",round((t2-t1)/60.0)); } for(int i=1; i<=k; i++) { printf("%d",table[i].numServe); if(i<k) cout << " "; } return 0; }
最新回复(0)