PAT刷题小结(刷题篇)


上一个部分写了七千多字,比较庞大,其实这四个专题的习题与前面的相比相对偏少,值得一说的也不多,但内容确实比较复杂,即便前面介绍了那么多,有些内容还是省略掉了。在这一部分主要分析我近期刷到的较有启发性和代表性的题目,姑且按照知识点分类了。

树状数组

分块思想

  • 题目描述
    食堂有N个打饭窗口,现在正到了午饭时间,每个窗口都排了很多的学生,而且每个窗口排队的人数在不断的变化。
    现在问你第i个窗口到第j个窗口一共有多少人在排队?
  • 输入
    输入的第一行是一个整数T,表示有T组测试数据。
    每组输入的第一行是一个正整数N(N<=30000),表示食堂有N个窗口。
    接下来一行输入N个正整数,第i个正整数ai表示第i个窗口最开始有ai个人排队。(1<=ai<=50)
    接下来每行有一条命令,命令有四种形式:
    (1)Add i j,i和j为正整数,表示第i个窗口增加j个人(j不超过30);
    (2)Sub i j,i和j为正整数,表示第i个窗口减少j个人(j不超过30);
    (3)Query i j,i和j为正整数,i<=j,表示询问第i到第j个窗口的总人数;
    (4)End 表示结束,这条命令在每组数据最后出现;
    每组数据最多有40000条命令。
  • 输出
    对于每组输入,首先输出样例号,占一行。
    然后对于每个Query询问,输出一个整数,占一行,表示询问的段中的总人数,这个数保持在int以内。
  • 样例输入 复制
    1
    10
    1 2 3 4 5 6 7 8 9 10
    Query 1 3
    Add 3 6
    Query 2 7
    Sub 10 2
    Add 6 3
    Query 3 10
    End
  • 样例输出 复制
    Case 1:
    6
    33
    59

这个问题显然是可以用树状数组解决的单点更新、区间查询问题,当然同时也是能够用block分块解决的实时更新数列查询求和问题,故本题有树状数组和分块法两种解法,可以当作对这两块内容的一个牛刀小试,需要注意的是要用scanf和printf处理输入输出,若用cin、cout会提示时间超限。
思路1 树状数组:对N个窗口排队人数建立树状数组c,其中c[i]存储的是包括i窗口向前的lowbit(i)范围内窗口排队总人数;设计getSum函数,可求解1~x窗口排队人数总和,原理上一部分已提,求[i,j],(i>1)范围内的总人数则可以用getSum(j)-getSum(i-1);设计update函数,更新x窗口人数,指令为Add做加法,指令为Sub将增量乘以-1传入函数,相当于做减法。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define lowbit(i) (i&(-i))
typedef long long LL;
const int maxn = 30010;
LL c[maxn];
int N;
void update(int x,int v){
	for(int i=x;i<=N;i+=lowbit(i)){
		c[i] += v;
	}
}
LL getSum(int x){
	LL sum=0;
	for(int i=x;i>0;i-=lowbit(i))
		sum += c[i];
	return sum;
}
int main(){
	int T,i,j,x;
	char cmd[10];
	scanf("%d",&T);
	for(int k=1;k<=T;k++){
		printf("Case %d:\n",k);
		scanf("%d",&N);
		fill(c,c+N+1,0);
		for(int s=1;s<=N;s++){
			scanf("%d",&x);
			update(s,x);
		}
		while(scanf("%s",cmd),strcmp(cmd,"End")!=0){
			scanf("%d %d",&i,&j);
			if(!strcmp(cmd,"Query")){
				LL ans;
				if(i==1)
					ans = getSum(j);
				else
					ans = getSum(j)-getSum(i-1);
				printf("%lld\n",ans);
			}else if(!strcmp(cmd,"Add")){
				update(i,j);
			}else if(!strcmp(cmd,"Sub")){
				j *= -1;
				update(i,j);
			}
		}
	}
} 


思路2 分块思想:对N个窗口分块,由于N不超过30000,每块元素数量可设为173,block数组元素存储每块内人数总和,查询和累加块,设计getSum函数,注意x
窗口元素的块号为(x-1)/blocknum(x从1开始计,blocknum为每块元素数量)按照思路1的方法求区间累加和,更新需要分别更新原序列数据和块数据,复杂度均为常数。代码中块内元素数量为100,与分析的稍有不同。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int maxn = 30050;
const int sqrN = 100;
LL block[310];
int table[maxn];
void update(int x,int v){
	block[(x-1)/sqrN] += v;
	table[x] += v;
}
 
LL getSum(int x){
	LL sum = 0;
	int bnum = (x-1)/sqrN;
	for(int i=0;i<bnum;i++)
		sum += block[i];
	for(int i=sqrN*bnum+1;i<=x;i++)
		sum += table[i];
	return sum;
}
int main(){
	int T,N,I,J;
	char query[10];
	scanf("%d",&T);
	for(int i=1;i<=T;i++){
		printf("Case %d:\n",i);
		scanf("%d",&N);
		fill(block,block+310,0); 
		for(int j=1;j<=N;j++){
			scanf("%d",table+j);
			block[(j-1)/sqrN] += table[j];
		}
		while(scanf("%s",query),strcmp(query,"End")!=0){
			scanf("%d %d",&I,&J);
			if(!strcmp(query,"Add")){
				update(I,J);
			}else if(!strcmp(query,"Sub")){
				update(I,-J);
			}else{
				LL ans=0;
				if(I==1) ans = getSum(J);
				else ans = getSum(J) - getSum(I-1);
				printf("%lld\n",ans);
			}
		}
	}
	return 0;
}

最少的交换

  • 题目描述
    现在给你一个由n个互不相同的整数组成的序列,现在要求你任意交换相邻的两个数字,使序列成为升序序列,请问最少的交换次数是多少?
  • 输入
    输入包含多组测试数据。每组输入第一行是一个正整数n(n<500000),表示序列的长度,当n=0时。
    接下来的n行,每行一个整数a[i](0<=a[i]<=999999999),表示序列中第i个元素。
  • 输出
    对于每组输入,输出使得所给序列升序的最少交换次数。
  • 样例输入 复制
    5
    9
    1
    0
    5
    4
    3
    1
    2
    3
    0
  • 样例输出 复制
    6
    0

树状数组的一个典型应用是求序列的逆序数,因为利用树状数组可以方便地统计一个序列的元素x左边所有大于x的元素个数,方法是用c数组存储序列元素的出现次数,下标为序列元素本身的值,因此这里的c数组很像一个hash表,直接按值查找,查找的时间复杂度为常数,统计时边读数据边更新c数组,读到元素x就将c数组下标为x的左边的元素求累加和,即执行getSum(maxn)-getSum(x),便能得到原序列x左边(出现比x早)且比x值大(c数组中处于x右边)的元素个数(c数组存储下标出现次数)。
思路分析:这道题本质上就是求序列的逆序数,而求逆序数只要统计每个元素左边大于该元素的元素个数,累加即可。此外,也可以通过倒着遍历原序列,同时更新c数组,对每个元素x执行getSum(x-1)便可统计出所有位于x右边小于x的元素个数,对本题来说得到的解是一样的。另外本题对原数组进行了离散化处理,因为只关注数组元素的大小关系,所以不妨对数组元素排个序,然后将元素重新赋值为它的排名,这样我们的c数组的长度就不会超过原数组的长度。需要注意的是大小相同的元素排名相同,但其后的元素排名等于排序后的下标,即排名不一定连续,如:3 3 5 7 8,其排名分别为1 1 3 4 5;若原数组为8 7 5 3 3,离散化操作后就得到5 4 3 1 1。

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define lowbit(i) (i&(-i))
const int maxn = 500010;
int c[maxn];
int A[maxn];
struct node{
	int pos;//原来的序号 
	int val;//值 
	node(){
		
	}
	node(int _p,int _v):pos(_p),val(_v){
	}
};
vector<node> d;
bool cmp(node a,node b){
	return a.val<b.val;
}
void update(int x,int v){
	for(int i=x;i<maxn;i+=lowbit(i))
		c[i] += v;
}
int getSum(int x){
	int sum = 0;
	for(int i=x;i>0;i-=lowbit(i))
		sum += c[i];
	return sum;
}
int main(){
	int n;
	while(scanf("%d",&n),n!=0){
		d.clear();
		int x;
		for(int i=0;i<n;i++){
			scanf("%d",&x);
			node temp(i,x);
			d.push_back(temp);
		}
		sort(d.begin(),d.end(),cmp);
		for(int i=0;i<n;i++){
			if(i==0||d[i].val!=d[i-1].val)
				A[d[i].pos] = i+1;
			else
				A[d[i].pos] = A[d[i-1].pos];
		}
		long long Count = 0;
		fill(c,c+n+1,0);
		for(int i=0;i<n;i++){
			update(A[i],1);
			Count += getSum(n) - getSum(A[i]);
		}
		printf("%lld\n",Count);
	}
}

数列问题

  • 问题描述
    一个简单的数列问题:给定一个长度为n的数列,求这样的三个元素ai, aj, ak的个数,满足ai < aj > ak,且i < j < k。
  • 输入数据
    第一行是一个整数n(n <= 50000)。
    第二行n个整数ai(0 <= ai <= 32767)。
  • 输出数据
    一个数,满足ai < aj > ak (i < j < k)的个数。
  • 样例输入
    5
    1 2 3 4 1
  • 样例输出
    6

思路分析:经过上一题的分析您应该很快清楚该如何处理,事实上只要分别统计x右边小于x的元素个数,和x左边小于x的元素个数,然后累加即可,具体代码不给出,读者可当练手题自行完成。

结构体的应用

本部分包含三道PAT习题,都是较为复杂的阅读理解题,都需要巧妙的设计存储结构,应体会题解是如何筛选有效信息存储,并利用一些特殊的存储结构简化程序的,我自己的问题还是在于对整个过程无脑模拟不会抽象与简化,采用的数据结构也不够高效,最后要么程序崩溃要么部分正确。
分析里我频繁提到的柳神是浙大的一名学子,我偶然在网上看到了她的个人博客,然后便心甘情愿地拜入麾下了,她的许多题解都让我受益匪浅。

队列queue

这一块是PAT上两道涉及队列的题目,其中第一道结构体内包含先进先出队列,第二题涉及优先队列使程序变得相当简洁高效,都是在我看来很值得学习的题目。

1014 Waiting in Line

Suppose a bank has N windows open for service. There is a yellow line in front of the windows which devides the waiting area into two parts. The rules for the customers to wait in line are:
The space inside the yellow line in front of each window is enough to contain a line with M customers. Hence when all the N lines are full, all the customers after (and including) the (NM+1)st one will have to wait in a line behind the yellow line.
Each customer will choose the shortest line to wait in when crossing the yellow line. If there are two or more lines with the same length, the customer will always choose the window with the smallest number.
Customeri​ will take Ti​ minutes to have his/her transaction processed.
The first N customers are assumed to be served at 8:00am.
Now given the processing time of each customer, you are supposed to tell the exact time at which a customer has his/her business done.
For example, suppose that a bank has 2 windows and each window may have 2 customers waiting inside the yellow line. There are 5 customers waiting with transactions taking 1, 2, 6, 4 and 3 minutes, respectively. At 08:00 in the morning, customer1​ is served at window1​ while customer2​ is served at window2​. Customer3​ will wait in front of window1​ and customer4​ will wait in front of window2​. Customer5​ will wait behind the yellow line.
At 08:01, customer1​ is done and customer5​ enters the line in front of window1​ since that line seems shorter now. Customer2​ will leave at 08:02, customer4​ at 08:06, customer3​ at 08:07, and finally customer5​ at 08:10.
Input Specification:
Each input file contains one test case. Each case starts with a line containing 4 positive integers: N (≤20, number of windows), M (≤10, the maximum capacity of each line inside the yellow line), K (≤1000, number of customers), and Q (≤1000, number of customer queries).
The next line contains K positive integers, which are the processing time of the K customers.
The last line contains Q positive integers, which represent the customers who are asking about the time they can have their transactions done. The customers are numbered from 1 to K.
Output Specification:
For each of the Q customers, print in one line the time at which his/her transaction is finished, in the format HH:MM where HH is in [08, 17] and MM is in [00, 59]. Note that since the bank is closed everyday after 17:00, for those customers who cannot be served before 17:00, you must output Sorry instead.
Sample Input:
2 2 7 5
1 2 6 4 3 534 2
3 4 5 6 7
Sample Output:
08:07
08:06
08:10
17:00
Sorry

题目大意:银行有N个窗口开放业务办理,每个窗口前的办理者需要在黄线内排队等候,黄线内每条队列最多有M个人,剩下的人要在黄线外等候(也是先到先得,相当于一个等候队列)。排队按这样的规则进行:如果某个窗口有人办理好业务离开,则黄线外等候的人(若有)选择该队列排,因为这个时候必然黄线内其他队伍都是满的,不然黄线外不会有人,只有这个队伍人数少1,但是一旦选择一条队伍便不可更换。按到达顺序给出每个客户办理业务需要的时间,银行从早上8:00经营到下午17:00,前N个客户将在8:00受理,若客户17:00前还未开始办理业务则不再受理,只能离开。现在有Q个客户要查询自己办理完成业务的时间,若能够办理则给出,否则,输出sorry。
思路分析:本题参考柳神的解答(姐姐真的是我的神),对每个窗口维护一个队列q,同时记录它队首元素业务办理完成时间,亦即出队时间poptime,和队尾元素办理完成时间endtime(每次元素入队时累加它办理业务时长即可实现),优先选择出队时间最早的队排。元素出队时,黄线外若有元素则先入队,然后计算此时队首元素业务办理完成的时间,只要用出队时间加上业务办理时长即可,接着用这个时间更新该窗口出队时间。

#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>
using namespace std;
struct node{
	int poptime,endtime;
	queue<int> q;//存储人员办理时间 
};
int main(){
	int n,m,k,q;
	cin>>n>>m>>k>>q;
	vector<node> window(n);
	vector<int> time(k+1);
	vector<int> result(k+1);
	vector<bool> sorry(k+1,false);
	for(int i=1;i<=k;i++)
		scanf("%d",&(time[i]));
	int index = 1;
	for(int i=0;i<m;i++){
		for(int j=0;j<n;j++){
			if(index<=k){
				window[j].q.push(time[index]);
				if(window[j].endtime>=540)
					sorry[index] = true;
				if(i==0){
					window[j].poptime = time[index];
					window[j].endtime = 0;
				}
				window[j].endtime += time[index];
				result[index] = window[j].endtime;
				index++;
			}
		}
	}
	while(index<=k){
		int minwindow = 0,minpoptime = window[0].poptime;
		for(int i=1;i<n;i++){
			if(window[i].poptime<minpoptime){
				minwindow = i;
				minpoptime = window[i].poptime;
			}
		}
		window[minwindow].q.pop();
		window[minwindow].q.push(time[index]);
		if(window[minwindow].endtime>=540)
			sorry[index] = true;
		window[minwindow].endtime += time[index];
		result[index++] = window[minwindow].endtime;
		window[minwindow].poptime += window[minwindow].q.front();
	}
	while(q--){
		int qnum,H,M;
		scanf("%d",&qnum);
		H = 8 + result[qnum] / 60;
		M = result[qnum] % 60;
		if(!sorry[qnum]){
			printf("%02d:%02d\n",H,M);
		}else printf("Sorry\n");
	}
	return 0;
} 

1017 Queueing at Bank

Suppose a bank has K windows open for service. There is a yellow line in front of the windows which devides the waiting area into two parts. All the customers have to wait in line behind the yellow line, until it is his/her turn to be served and there is a window available. It is assumed that no window can be occupied by a single customer for more than 1 hour.
Now given the arriving time T and the processing time P of each customer, you are supposed to tell the average waiting time of all the customers.
Input Specification:
Each input file contains one test case. For each case, the first line contains 2 numbers: N (≤104) - the total number of customers, and K (≤100) - the number of windows. Then N lines follow, each contains 2 times: HH:MM:SS - the arriving time, and P - the processing time in minutes of a customer. Here HH is in the range [00, 23], MM and SS are both in [00, 59]. It is assumed that no two customers arrives at the same time.
Notice that the bank opens from 08:00 to 17:00. Anyone arrives early will have to wait in line till 08:00, and anyone comes too late (at or after 17:00:01) will not be served nor counted into the average.
Output Specification:
For each test case, print in one line the average waiting time of all the customers, in minutes and accurate up to 1 decimal place.
Sample Input:
7 3
07:55:00 16
17:00:01 2
07:59:59 15
08:01:00 60
08:00:00 30
08:00:02 2
08:03:00 10
Sample Output:
8.2

题目大意:银行有K个窗口开放业务办理,黄线内的人办理业务,因窗口满了不能办理业务的人在黄线外等待,按照先到先得的顺序,若有窗口空闲,黄线外最先到的人就去该窗口办理。按时分秒的形式给出所有客户的到达时间,并给出客户业务办理需要的分钟数,计算每位客户等待的平均时长,单位为分钟,精确到小数点后一位。注意每个窗口不能被占用超过一小时,银行从早八营业到晚五,八点之前来的客户要等到银行开门,17点之后来的客户不予受理。
思路分析:这题也参考了柳神的代码,但个人认为代码里没有处理”窗口不能被占用1小时这个要求“,事实上只要输入办理时长时检查一下,超过一小时的统统存为一小时即可,此外在数据结构上也做了一点点小改动,将客户信息以结构体数组存储。代码用优先队列按上一题提到的poptime建立小顶堆,并利用这个数据和客户到达时间计算客户等待时间,累加后求平均即可。注意到达时间存为秒数:

#include <cstdio>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
const int maxn = 10010;
struct node{
	int arr;
	int lap;
};
vector<node> d;
bool cmp(node a,node b){
	return a.arr < b.arr;
}
int main(){
	int K,N,h,m,s,l;
	int limi = 3600 * 17,total = 0;
	scanf("%d%d",&N,&K);
	for(int i=0;i<N;i++){
		scanf("%d:%d:%d %d",&h,&m,&s,&l);
		node temp;
		temp.arr = h * 3600 + m * 60 + s;
		if(l <= 60) temp.lap = l * 60;
		else temp.lap = 3600;
		if(temp.arr>limi) continue;
		d.push_back(temp);
	}
	sort(d.begin(),d.end(),cmp);
	N = d.size();//更新一下N值 
	priority_queue<int,vector<int>,greater<int> > q;
	for(int i=0;i<K;i++)
		q.push(8*3600);
	for(int i=0;i<N;i++){
		if(d[i].arr >= q.top()){
			d[i].lap += d[i].arr;
			q.pop();
			q.push(d[i].lap);
		}else{
			total += q.top() - d[i].arr;
			d[i].lap += q.top();
			q.pop();
			q.push(d[i].lap);
		}
	}
	(!N)?printf("0.0\n"):printf("%.1f\n",((double)total/60.0)/(double)N);
} 

1016 Phone Bills

A long-distance telephone company charges its customers by the following rules:
Making a long-distance call costs a certain amount per minute, depending on the time of day when the call is made. When a customer starts connecting a long-distance call, the time will be recorded, and so will be the time when the customer hangs up the phone. Every calendar month, a bill is sent to the customer for each minute called (at a rate determined by the time of day). Your job is to prepare the bills for each month, given a set of phone call records.
Input Specification:
Each input file contains one test case. Each case has two parts: the rate structure, and the phone call records.
The rate structure consists of a line with 24 non-negative integers denoting the toll (cents/minute) from 00:00 - 01:00, the toll from 01:00 - 02:00, and so on for each hour in the day.
The next line contains a positive number N (≤1000), followed by N lines of records. Each phone call record consists of the name of the customer (string of up to 20 characters without space), the time and date (MM:dd:HH:mm), and the word on-line or off-line.
For each test case, all dates will be within a single month. Each on-line record is paired with the chronologically next record for the same customer provided it is an off-line record. Any on-line records that are not paired with an off-line record are ignored, as are off-line records not paired with an on-line record. It is guaranteed that at least one call is well paired in the input. You may assume that no two records for the same customer have the same time. Times are recorded using a 24-hour clock.
Output Specification:
For each test case, you must print a phone bill for each customer.
Bills must be printed in alphabetical order of customers’ names. For each customer, first print in a line the name of the customer and the month of the bill in the format shown by the sample. Then for each time period of a call, print in one line the beginning and ending time and date (dd:HH:mm), the lasting time (in minute) and the charge of the call. The calls must be listed in chronological order. Finally, print the total charge for the month in the format shown by the sample.
Sample Input:
10 10 10 10 10 10 20 20 20 15 15 15 15 15 15 15 20 30 20 15 15 10 10 10
10
CYLL 01:01:06:01 on-line
CYLL 01:28:16:05 off-line
CYJJ 01:01:07:00 off-line
CYLL 01:01:08:03 off-line
CYJJ 01:01:05:59 on-line
aaa 01:01:01:03 on-line
aaa 01:02:00:01 on-line
CYLL 01:28:15:41 on-line
aaa 01:05:02:24 on-line
aaa 01:04:23:59 off-line
Sample Output:
CYJJ 01
01:05:59 01:07:00 61 $12.10
Total amount: $12.10
CYLL 01
01:06:01 01:08:03 122 $24.40
28:15:41 28:16:05 24 $3.85
Total amount: $28.25
aaa 01
02:00:01 04:23:59 4318 $638.80
Total amount: $638.80

题目大意:每天24小时各小时段的话费有各自的标准,给出24个正整数代表每个小时段每分钟收取的美分数,其中第一个小时段为00:00:00–01:00:00,其他依此类推。现给出一段通话记录,让你以特定格式输出每个人该月(一个测试文件的月份统一)每天的通话起始时间即当天花费,并最后输出此人该月总话费。通话记录有若干条,各占一行,以人名字符串打头(为一段不超过20字符英文字符串),接以天时分秒表示的时间和状态,其中状态有on-line、off-line两种,若一人同一天状态为on-line的记录没有状态为off-line的记录匹配,或off-line没有on-line匹配,都视为无效数据,直接舍弃。注意通话记录是乱序给出,但输出要按人名字典序升序输出,且同一个人的通话记录要按时间顺序输出。
思路分析:我依然参考了柳神的思路分析,在其启发下自行写出了代码,但逻辑完全一致,并无任何值得一说的创新之处。本题用结构体存储通话记录,结构体成员包括人名、时间、状态,然后对该结构体先按照人名排序,再按照时间排序,然后遍历检查是否合法,具体来说就是是否为on-line接off-line,否则舍弃。注意要处理跨天通话,所以以天时分秒的形式计算话费:

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
struct record{
	char name[30];
	char stat[10];
	int dd,HH,mm;
};
vector<record> d;
bool cmp(record a,record b){
	if(strcmp(a.name,b.name)==0){
		if(a.dd==b.dd){
			if(a.HH==b.HH)
				return a.mm < b.mm;
			return a.HH < b.HH;
		}
		return a.dd < b.dd;
	}else
	return strcmp(a.name,b.name) < 0;
}
double paystruct[24];
int main(){
	int N,MM,dd,HH,mm;
	int x;
	char name[30];
	char stat[10];
	for(int i=0;i<24;i++){
		scanf("%d",&x);
		paystruct[i] = double(x*60)/100;
	}
	scanf("%d",&N);
	for(int i=0;i<N;i++){
		record temp;
		scanf("%s %d:%d:%d:%d %s",name,&MM,&dd,&HH,&mm,stat);
		strcpy(temp.name,name);
		strcpy(temp.stat,stat);
		temp.dd = dd,temp.HH = HH,temp.mm = mm;
		d.push_back(temp);
	}
	sort(d.begin(),d.end(),cmp);
	strcpy(name,d[0].name);
	strcpy(stat,d[0].stat);
	dd = d[0].dd,HH = d[0].HH,mm = d[0].mm;
	int td = dd,tH = HH,tm = mm;//终点有必要存储一下 
	double total = 0;
	int Count = 0;
	for(int i=1;i<N;i++){
		if(strcmp(name,d[i].name)==0){
			if(strcmp(d[i].stat,"off-line")==0&&strcmp(stat,"on-line")==0){
				//先计算状态
				double pay = 0;
				if(Count==0) printf("%s %02d\n",name,MM);
				Count++;
				int lap = (d[i].dd - td) * 24 * 60 + (d[i].HH - tH) * 60 + (d[i].mm - tm);
				for(int j=tH;j<=(d[i].dd - td)*24 + d[i].HH;j++){
					pay += paystruct[j%24];
				}
				pay -= ((1.0*tm)/60)*paystruct[tH];
				pay -= ((60-d[i].mm)*1.0) / 60 * paystruct[d[i].HH];
				printf("%02d:%02d:%02d %02d:%02d:%02d %d $%.2f\n",\
				td,tH,tm,d[i].dd,d[i].HH,d[i].mm,lap,pay);
				total += pay;
				//再转移状态
				strcpy(stat,"off-line");
			}else if(strcmp(d[i].stat,"on-line")==0){
				td = d[i].dd,tH = d[i].HH,tm = d[i].mm;
				strcpy(stat,"on-line");
			}
		}else{
			//先把上一个信息打印了
			if(total!=0) printf("Total amount: $%.2f\n",total);
			//然后更新状态 
			total = 0;
			Count = 0;
			strcpy(name,d[i].name);
			strcpy(stat,d[i].stat);
			td = d[i].dd,tH = d[i].HH,tm = d[i].mm;
		}
	}
	if(total!=0) printf("Total amount: $%.2f\n",total);
}

DFS

1179 chemical reaction

A chemical equation is the symbolic representation of a chemical reaction in the form of symbols and formulae, wherein the reactant entities are given on the left-hand side and the product entities on the right-hand side. For example, CH4​+2O2​=CO2​+2H2​O means that the reactants in this chemical reaction are methane and oxygen: CH4​ and O2​, and the products of this reaction are carbon dioxide and water: CO2​ and H2​O.
Given a set of reactants and products, you are supposed to tell that in which way we can obtain these products, provided that each reactant can be used only once. For the sake of simplicity, we will consider all the entities on the right-hand side of the equation as one single product.
Input Specification:
Each input file contains one test case. For each case, the first line gives an integer N (2≤N≤20), followed by N distinct indices of reactants. The second line gives an integer M (1≤M≤10), followed by M distinct indices of products. The index of an entity is a 2-digit number.
Then a positive integer K (≤50) is given, followed by K lines of equations, in the format:
reactant_1 + reactant_2 + … + reactant_n -> product
where all the reactants are distinct and are in increasing order of their indices.
Note: It is guaranteed that
one set of reactants will not produce two or more different products, i.e. situation like 01 + 02 -> 03 and 01 + 02 -> 04 is impossible;
a reactant cannot be its product unless it is the only one on the left-hand side, i.e. 01 -> 01 is always true (no matter the equation is given or not), but 01 + 02 -> 01 is impossible; and
there are never more than 5 different ways of obtaining a product given in the equations list.
Output Specification:
For each case, print the equations that use the given reactants to obtain all the given products. Note that each reactant can be used only once.
Each equation occupies a line, in the same format as we see in the inputs. The equations must be print in the same order as the products given in the input. For each product in order, if the solution is not unique, always print the one with the smallest sequence of reactants – A sequence { a1​,⋯,am​ } is said to be smaller than another sequence { b1​,⋯,bn​ } if there exists 1≤i≤min(m,n) so that aj​=bj​ for all j<i, and ai​<bi​.
It is guaranteed that at least one solution exists.
Sample Input:
8 09 05 03 04 02 01 16 10
3 08 03 04
6
03 + 09 -> 08
02 + 08 -> 04
02 + 04 -> 03
01 + 05 -> 03
01 + 09 + 16 -> 03
02 + 03 + 05 -> 08
Sample Output:
02 + 03 + 05 -> 08
01 + 09 + 16 -> 03
04 -> 04

题目大意:化学反应方程式是这样的一个等式->左边都是反应物,右边都是生成物,为了使问题变得简单,我们假设右边只有一个生成物。输入三段数据,分别为反应物、生成物和方程式。如果反应物相同,则只能得到一个生成物,而不会得到另一个不同得生成物;如果生成物本身出现在->左边,则只有恒等式一种情况,即一物质不能与其他反应物结合得到自己,且恒等式无论给出的方程式里包不包含总是成立的;给出的反应方程式中对于同一个物质不会有超过5种合成方法。现要求你对输入的生成物依次输出其合成反应方程式,注意反应物只在输入的反应物中选择,且如果有多种方案,输出字典序最小的一个。每各反应物仅能使用一次。

思路分析:本题要求按照输入的生成物的顺序依次给出其合成方案,由于前一个生成物合成方法的选择会影响后面的生成物生成,并可能会造成后面生成物的反应物全部被用过而无法合成的情况,因此势必要用到DFS算法,当走到死胡同时逐层回溯,重新选择。本题可以直接读入方程式的字符串并存到数组里,生成物结构体内就可以存储对应合成方法的方程式编号,如果既是反应物又是生成物,需要为其添加恒等式并存入方程式数组,可以用到sprintf建立格式化字符串。由于最后要求方案字典序最小可以对方程式数组按其成员字符串大小升序排序,然后传入分析函数solve,分析出方程式的反应物、生成物并把信息存到相应的结构体中,若反应物没有给出,记得舍弃。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;
const int maxn = 30;
const int maxm = 20;
const int maxk = 60;
bool used[maxn] = {false};
struct node{
	int id;//索引值 
	vector<int> method;//存储方程式编号
	int choice;//最终选择打印的方程式 
};
map<int,int> prodMap;//生成物索引与下标的映射
struct eq{
	vector<int> reaction;
	string equation;
};
map<int,int> reacMap;//反应物索引与下标的映射 
//方程式和生成物结构体用动态数组存储
vector<eq> equa;
bool cmp(eq a,eq b){
	return a.equation < b.equation;
}
void solve(vector<node> &a,string &s,int index){
	istringstream ss(s);
	string now,pre;
	ss>>now,pre = now;
	int x;
	vector<int> b;
	while(ss>>now){
		if(pre=="->"){
			sscanf(now.c_str(),"%d",&x);
			if(prodMap.find(x)!=prodMap.end()){
				a[prodMap[x]].method.push_back(index);//方程式的编号 
			}
		}else if(now=="+"||now=="->"){
			sscanf(pre.c_str(),"%d",&x);
			if(reacMap.find(x)!=reacMap.end()){
				b.push_back(reacMap[x]);//反应物下标 
			}else return;//有不存在的反应物,舍弃 
		}
		pre = now;
	}
	equa[index].reaction = b;
}
bool DFS(vector<node> &a,int v,const int M){
	if(v==M){
		//到达递归终点,直接打印,返回
		for(int i=0;i<M;i++){
			int cid = a[i].choice;
			cout<<equa[cid].equation<<endl;
		}
		return true; 
	}
	for(int i=0;i<a[v].method.size();i++){
		int cid = a[v].method[i];
		int j;
		for(j=0;j<equa[cid].reaction.size()&&!used[equa[cid].reaction[j]];j++);
		if(j==equa[cid].reaction.size()){
			for(j=0;j<equa[cid].reaction.size();j++)
				used[equa[cid].reaction[j]] = true;//用掉反应物 
			a[v].choice = cid;
			if(DFS(a,v+1,M)) return true;//直接return,就只会输出一条路径,循环没有走完 
			else{
				for(j=0;j<equa[cid].reaction.size();j++)
					used[equa[cid].reaction[j]] = false;//还原反应物 
				continue;
			}
		}
	}
	return false;
}
int main(){
	int N,M,K,x;
	scanf("%d",&N);
	for(int i=0;i<N;i++){
		scanf("%d",&x);
		reacMap.insert(make_pair(x,i));
	}
	getchar();
	scanf("%d",&M);
	vector<node> prod(M);
	for(int i=0;i<M;i++){
		scanf("%d",&x);
		prod[i].id = x;
		prodMap.insert(make_pair(x,i));
	}
	getchar();
	scanf("%d",&K);
	getchar();
	for(int i=0;i<K;i++){
		eq temp;
		getline(cin,temp.equation);
		equa.push_back(temp);
	}
	for(int i=0;i<M;i++){
		if(reacMap.find(prod[i].id)!=reacMap.end()){
			eq temp;
			char cst[20];
			sprintf(cst,"%02d -> %02d",prod[i].id,prod[i].id);
			temp.equation = cst;
			equa.push_back(temp);
		}
	} 
	K = equa.size();//更新一下K 
	sort(equa.begin(),equa.end(),cmp);
	for(int i=0;i<K;i++){
		solve(prod,equa[i].equation,i); 
	}
	//如何打印方案
	DFS(prod,0,M);//从0开始DFS 
} 

反思总结

我想,可能是因为自己水平尚浅,或是没有找到合适的做题节奏,才导致上面每一道题所耗的平均时长都在2个小时左右。但是即便又理解错了题目的意思、又忽视了边界数据的处理而导致部分正确,或直接毫无头绪,我也难以跳过去转战下一题,而是不停地磨,直到成功解出。现在想来,这么做并不理智,因为我自己的思考方式就很像DFS,不停碰壁、碰壁,偶然找到了出口,高呼万岁!但最后脑子里印痕最深的,还是走过的那些不通的路,对于如何找到正解,还要一步回溯才能得到其路径,而通常情况下,我是懒于理会的,所以成功解题对我来说作用不大,反而参考别人的题解学习会受到更多启发,同时也不用着了魔一样疯狂试错。我想一种是做题版训练方式、一种是背题版训练方式,应该等量分配到刷题日常中,不能一方的侧重远超另一方,两者是相辅相成、相互调剂的。收获多少因人而异,但找到合适的学习与训练方法才是真本事,从长远角度来看这样的人才能赢下自己的人生游戏。
在这里插入图片描述

  • 14
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值