团体程序设计天梯赛真题(部分题解,持续更新)

文章目录

天梯赛真题

在本篇文章中,我会记录一些PTA刷题过程中遇到的问题,以及一些比较巧妙的题目,用于自己补盲。可能一些题目的解法不是最优的,但是每道题都是拿满分数的。

目前记录了L1和L2中全部我认为有价值、触及到我盲点的题目,明天就要开始天梯赛了,希望能有个好结果。

解题框架:

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
int main(){
	ios::sync_with_stdio(false);
	
	return 0;
} 

L1-008 求整数段和(10分)

给定两个整数AB,输出从AB的所有整数以及这些数的和。

输入格式:

输入在一行中给出2个整数AB,其中−100≤AB≤100,其间以空格分隔。

输出格式:

首先顺序输出从AB的所有整数,每5个数字占一行,每个数字占5个字符宽度,向右对齐。最后在一行中按Sum = X的格式输出全部数字的和X

输入样例:

-3 8

输出样例:

   -3   -2   -1    0    1
    2    3    4    5    6
    7    8
Sum = 30

解题过程:

本题需要注意的是输出格式的控制!

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
int main(){
	ios::sync_with_stdio(false);
    int A,B;
    int sum=0;
    cin>>A>>B;
    int count=1;
	for(int i=A;i<=B;++i){
		cout<<setw(5);
		cout<<i;
		if(count%5==0) cout<<endl;
		count++;
		sum+=i;
	} 
	if((count-1)%5!=0) cout<<endl;
	cout<<"Sum = "<<sum;
	return 0;
} 

C++的输出格式控制:

  • 设置输出宽度:

    //设置宽度为w,默认为右对齐
    cout.width(w);
    cout<<setw(w);
    
  • 设置对齐方式:

    cout<<setiosflags(ios_base::left);//设置左对齐
    
  • 设置输出精度:

    cout<<setiosflags(ios_base::fixed);//以定点格式显示浮点数据
    cout<<setiosflags(ios_base::scientific);//以科学计数法显示浮点数据
    cout<<setprecision(p);//设置精度为p
    
  • 设置进制:

    cout<<dec<<a;//以十进制格式输出a
    cout<<oct<<a;//以八进制格式输出a
    cout<<hex<<a;//以十六进制格式输出a
    cout<<setiosflags(ios_base::uppercase);//十六进制字母大写输出
    cout<<setiosflags(ios_base::lowercase);//十六进制字母小写输出
    
  • 设置填充:

    cout<<setfill(c);
    //配合setw使用,对于多出的宽度填充字符c
    

L1-018 大笨钟(10分)

微博上有个自称“大笨钟V”的家伙,每天敲钟催促码农们爱惜身体早点睡觉。不过由于笨钟自己作息也不是很规律,所以敲钟并不定时。一般敲钟的点数是根据敲钟时间而定的,如果正好在某个整点敲,那么“当”数就等于那个整点数;如果过了整点,就敲下一个整点数。另外,虽然一天有24小时,钟却是只在后半天敲1~12下。例如在23:00敲钟,就是“当当当当当当当当当当当”,而到了23:01就会是“当当当当当当当当当当当当”。在午夜00:00到中午12:00期间(端点时间包括在内),笨钟是不敲的。

下面就请你写个程序,根据当前时间替大笨钟敲钟。

输入格式:

输入第一行按照hh:mm的格式给出当前时间。其中hh是小时,在00到23之间;mm是分钟,在00到59之间。

输出格式:

根据当前时间替大笨钟敲钟,即在一行中输出相应数量个Dang。如果不是敲钟期,则输出:

Only hh:mm.  Too early to Dang.

其中hh:mm是输入的时间。

输入样例1:

19:05

输出样例1:

DangDangDangDangDangDangDangDang

输入样例2:

07:05

输出样例2:

Only 07:05.  Too early to Dang.

解题过程:

本题需要注意的是字符串和数字之间的转换过程。

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
int main(){
	ios::sync_with_stdio(false);
	string time;
	cin>>time;
	stringstream sstr;
	int hour,minute;
	sstr<<time.substr(0,2);
	sstr>>hour;
    sstr.clear();
	sstr<<time.substr(3,2);
	sstr>>minute;
	if(hour>=0&&hour<=12){
		if(hour==12&&minute!=0) cout<<"Dang";
		else cout<<"Only "<<time<<".  Too early to Dang.";
	}
	else{
		for(int i=0;i<hour-12;++i) cout<<"Dang";
		if(minute!=0) cout<<"Dang";
	}
	return 0;
} 

字符串与数字之间的相互转换:如果字符串中存在非数字字符,那么sstr会将由起始字符到非数字字符的部分转为数字

//字符串转数字
int num;
string s("07");
stringstream sstr;
sstr<<s;
sstr>>num;

sstr.clear();//重复使用前需要进行清空

//数字转字符串
int num=7;
string s;
stringstream str;
str<<num;
str>>s;

//更简便得可以使用
string s("07");
int num=stod(s);

L1-041 寻找250(10分)

对方不想和你说话,并向你扔了一串数…… 而你必须从这一串数字中找到“250”这个高大上的感人数字。

输入格式:

输入在一行中给出不知道多少个绝对值不超过1000的整数,其中保证至少存在一个“250”。

输出格式:

在一行中输出第一次出现的“250”是对方扔过来的第几个数字(计数从1开始)。题目保证输出的数字在整型范围内。

输入样例:

888 666 123 -233 250 13 250 -222

输出样例:

5

解题过程:

当输入个数不确定时,我们该如何确定输入过程何时结束?

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
int main(){
	ios::sync_with_stdio(false);
	int num,count=1;
	int ans=0;
	while(cin>>num){
		if(num==250) {
			if(ans==0) ans=count;
		}
		count++;
	}
	cout<<ans;
	return 0;
} 

当输入个数不确定时,可以直接通过 while(cin>>n) 进行判断与控制。

L1-015 跟奥巴马一起画方块(15分)

美国总统奥巴马不仅呼吁所有人都学习编程,甚至以身作则编写代码,成为美国历史上首位编写计算机代码的总统。2014年底,为庆祝“计算机科学教育周”正式启动,奥巴马编写了很简单的计算机代码:在屏幕上画一个正方形。现在你也跟他一起画吧!

输入格式:

输入在一行中给出正方形边长N(3≤N≤21)和组成正方形边的某种字符C,间隔一个空格。

输出格式:

输出由给定字符C画出的正方形。但是注意到行间距比列间距大,所以为了让结果看上去更像正方形,我们输出的行数实际上是列数的50%(四舍五入取整)。

输入样例:

10 a

输出样例:

aaaaaaaaaa
aaaaaaaaaa
aaaaaaaaaa
aaaaaaaaaa
aaaaaaaaaa

解题过程:

如何进行四舍五入?

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
int main(){
	ios::sync_with_stdio(false);
	float N;
	char c;
	cin>>N>>c;
	for(int i=0;i<round(N/2);++i){
		for(int j=0;j<N;++j){
			cout<<c;
		}
		cout<<endl;
	}
	return 0;
} 

c++中的math.h中有向上取整、向下取整、四舍五入取整和去小数的函数:

cout<<floor(2.7)<<endl;	//2
cout<<ceil(2.1)<<endl;	//3
cout<<round(2.5)<<endl;	//3
cout<<trunc(-2.5)<<endl;//-2

L1-025 正整数A+B(15分)

题的目标很简单,就是求两个正整数AB的和,其中AB都在区间[1,1000]。稍微有点麻烦的是,输入并不保证是两个正整数。

输入格式:

输入在一行给出AB,其间以空格分开。问题是AB不一定是满足要求的正整数,有时候可能是超出范围的数字、负数、带小数点的实数、甚至是一堆乱码。

注意:我们把输入中出现的第1个空格认为是AB的分隔。题目保证至少存在一个空格,并且B不是一个空字符串。

输出格式:

如果输入的确是两个正整数,则按格式A + B = 和输出。如果某个输入不合要求,则在相应位置输出?,显然此时和也是?

输入样例1:

123 456

输出样例1:

123 + 456 = 579

输入样例2:

22. 18

输出样例2:

? + 18 = ?

输入样例3:

-100 blabla bla...33

输出样例3:

? + ? = ?

解题过程:

通过字符串与数字之间反复转换判断输入的是否是一个数字,不需要使用正则表达式

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
int main(){
	ios::sync_with_stdio(false);
	vector<string> str;
	string s;
	string num1,num2,sum;
	int flag1=1;
	while(cin>>s){
		str.push_back(s);
	}
	//首先处理第一个数
	stringstream sstr1;
	string s1;
	int A;
    sstr1<<str[0];sstr1>>A;
    sstr1.clear();
    sstr1<<A;sstr1>>s1;
    if(s1.compare(str[0])==0&&A>=1&&A<=1000){
        num1=s1;
    }
    else{
        num1="?";sum="?";
        flag1=0;//标记str[0]是不合法的
    }
    //处理第二个数
    if(str.size()==2){//只输入了两个字符串,可能是合理的输入
        stringstream sstr2;
        string s2;
        int B;
        sstr2<<str[1];sstr2>>B;
        sstr2.clear();
        sstr2<<B;sstr2>>s2;
        if(s2.compare(str[1])==0&&B>=1&&B<=1000){
            num2=s2;
            if(flag1==1){//str[0]和str[1]都合法,且没有冗余的输入
                sstr2.clear();
                sstr2<<A+B;sstr2>>sum;
            }
        }
        else{
            num2="?";sum="?";
        }
    }
    else{
        num2="?";sum="?";
    }
    cout<<num1<<" + "<<num2<<" = "<<sum;
	return 0;
} 

附上正则表达式的版本:

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
int main(){
	ios::sync_with_stdio(false);
	regex pattern("[0-9]+");
	string A;cin>>A;
	cin.ignore();
	string B;getline(cin,B);
	int flagA=1;
	int flagB=1;
	if(regex_match(A,pattern)&&stoi(A)<=1000&&stoi(A)>=1) cout<<A<<" + ";
	else{
		cout<<"? + ";
		flagA=0;
	}
	if(regex_match(B,pattern)&&stoi(B)<=1000&&stoi(B)>=1) cout<<B<<" = ";
	else{
		cout<<"? = ";
		flagB=0;
	}
	if(flagA&&flagB) cout<<stoi(A)+stoi(B)<<endl;
	else cout<<"?"<<endl;
	return 0;
}

L1-050 倒数第N个字符串(15分)

给定一个完全由小写英文字母组成的字符串等差递增序列,该序列中的每个字符串的长度固定为 L,从 L 个 a 开始,以 1 为步长递增。例如当 L 为 3 时,序列为 { aaa, aab, aac, …, aaz, aba, abb, …, abz, …, zzz }。这个序列的倒数第27个字符串就是 zyz。对于任意给定的 L,本题要求你给出对应序列倒数第 N 个字符串。

输入格式:

输入在一行中给出两个正整数 L(2 ≤ L ≤ 6)和 N(≤10^5)。

输出格式:

在一行中输出对应序列倒数第 N 个字符串。题目保证这个字符串是存在的。

输入样例:

3 7417

输出样例:

pat

解题过程:

我认为这是一道比较巧妙的题目,我们可以将其转换为26进制的减法!

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
int main(){
	ios::sync_with_stdio(false);
	int L,N;
	cin>>L>>N;
	ll num=0;
	ll mult=1;
	for(int i=0;i<L;++i){
		num+=mult*25;
		mult*=26;
	}
	num-=N-1;
	// num转26进制
	vector<char> v;
	while(num>0){
		int a=num%26;
		v.push_back('a'+a);
		num/=26;
	}
	for(int i=0;i<L-v.size();++i) cout<<"a";
	for(int i=v.size()-1;i>=0;--i){
		cout<<v[i];
	}
	return 0;
}

L1-054 福到了(15分)

“福”字倒着贴,寓意“福到”。不论到底算不算民俗,本题且请你编写程序,把各种汉字倒过来输出。这里要处理的每个汉字是由一个 N × N 的网格组成的,网格中的元素或者为字符 @ 或者为空格。而倒过来的汉字所用的字符由裁判指定。

输入格式:

输入在第一行中给出倒过来的汉字所用的字符、以及网格的规模 N (不超过100的正整数),其间以 1 个空格分隔;随后 N 行,每行给出 N 个字符,或者为 @ 或者为空格。

输出格式:

输出倒置的网格,如样例所示。但是,如果这个字正过来倒过去是一样的,就先输出bu yong dao le,然后再用输入指定的字符将其输出。

输入样例 1:

$ 9
 @  @@@@@
@@@  @@@ 
 @   @ @ 
@@@  @@@ 
@@@ @@@@@
@@@ @ @ @
@@@ @@@@@
 @  @ @ @
 @  @@@@@

输出样例 1:

$$$$$  $ 
$ $ $  $ 
$$$$$ $$$
$ $ $ $$$
$$$$$ $$$
 $$$  $$$
 $ $   $ 
 $$$  $$$
$$$$$  $ 

输入样例 2:

& 3
@@@
 @ 
@@@

输出样例 2:

bu yong dao le
&&&
 & 
&&&

解题过程:

本题的关键在于如何读取多个带空格的字符串。

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
bool needDao(vector<string> v){
	int len=v.size();
	for(int i=0;i<=len/2;++i){
		if(v[i].compare(v[len-1-i])) return true;
	}
	return false;
}
int main(){
	ios::sync_with_stdio(false);
	char c;
	int N;
	cin>>c>>N;
	vector<string> v;
	cin.ignore();
	for(int i=0;i<N;++i){
		string s;
		getline(cin,s);
		v.push_back(s);
	}
	if(!needDao(v)){
		cout<<"bu yong dao le"<<endl;
	}
	for(int i=v.size()-1;i>=0;--i){
		replace(v[i].begin(),v[i].end(),'@',c);
		reverse(v[i].begin(),v[i].end());
		cout<<v[i]<<endl;
	}
	return 0;
} 

读取多个带空格的字符串的方法如下:

//如果在getline前存在cin,则需要利用
//cin.ignore();
//忽略掉getline可能读到的换行符
string s;
getline(cin,s);

L1-058 6翻了(15分)

“666”是一种网络用语,大概是表示某人很厉害、我们很佩服的意思。最近又衍生出另一个数字“9”,意思是“6翻了”,实在太厉害的意思。如果你以为这就是厉害的最高境界,那就错啦 —— 目前的最高境界是数字“27”,因为这是 3 个 “9”!

本题就请你编写程序,将那些过时的、只会用一连串“6666……6”表达仰慕的句子,翻译成最新的高级表达。

输入格式:

输入在一行中给出一句话,即一个非空字符串,由不超过 1000 个英文字母、数字和空格组成,以回车结束。

输出格式:

从左到右扫描输入的句子:如果句子中有超过 3 个连续的 6,则将这串连续的 6 替换成 9;但如果有超过 9 个连续的 6,则将这串连续的 6 替换成 27。其他内容不受影响,原样输出。

输入样例:

it is so 666 really 6666 what else can I say 6666666666

输出样例:

it is so 666 really 9 what else can I say 27

解题过程:

利用正则表达式匹配字符串:

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
int main(){
	ios::sync_with_stdio(false);
	string s;
	getline(cin,s);
	regex pattern1("6{4,}");
	regex pattern2("6{10,}");
    s=regex_replace(s,pattern2,"27");//注意一定要先匹配长的那串
	s=regex_replace(s,pattern1,"9");
	cout<<s;
	return 0;
} 

我认为本题目利用正则表达式会更加简便,因此此处系统得了解一下正则表达式的相关知识点:

  • 转义字符:即“\”
  • +:表示前面的字符至少出现一次
  • *:表示前面的字符串可以不出现或出现多次
  • ?:表示前面的字符至多出现一次
  • [ABC]:匹配[ ]中的全部字符
  • [^ABC]:匹配除[ ]中的全部字符
  • [A-Z]:匹配所有大写字母,[a-z]匹配所有小写字母
  • .:匹配除换行符(\n\r)之外的任何单个字符
  • [\s\S]:匹配所有字符串,“\s”代表空白符,“\S”代表非空白符
  • \w:匹配数字、字母、下划线等,等价于[A-Za-Z0-9_]
  • \d:匹配数字,等价于[0-9]
  • {n}:表示前面的字符出现n次
  • ():用于划分组别

**(1)regex_match:**匹配上才返回1

string s("Hello_2018");
regex pattern("(.{5})_(\\d{4})");
if(regex_match(s,pattern))
    cout<<"True"<<endl;

记录各个分组(即用括号划分的若干分组)的匹配情况:

string s("Hello_2018");
regex pattern("(.{5})_(\\d{4})");
smatch result;
if(regex_match(s,result,pattern)){
    cout<<result[0]<<endl;//完整的匹配:Hello_2018
    cout<<result[1]<<endl;//Hello
    cout<<result[2]<<endl;//2018
}

**(2)regex_search:**子串匹配上即返回1

string s("Hello 2018,Bye 2017");
regex pattern("\\d{4}");
if(regex_search(s,pattern))
    cout<<"True"<<endl;

输出所有匹配上的子串:

string s("Hello 2018,Bye 2017");
regex pattern("\\d{4}");
smatch result;
string::const_iterator start=s.begin();
string::const_iterator end=s.end();
while(regex_search(strat,end,result,pattern)){
    string temp=result[0];
    cout<<temp<<endl;
    start=result[0].second;
}

**(3)regex_replace:**将匹配上的子串进行替换

string s("Hello 2018");
regex pattern("Hello");
cout<<regex_replace(s,pattern,"Hi")<<endl;//Hi 2018

还可以利用该函数结合分组改变字符串的顺序:

string s("Hello_2018!");	
regex pattern("(.{3})(.{2})_(\\d{4})");				//匹配3个任意字符+2个任意字符+下划线+4个数字
cout << regex_replace(s, pattern, "$1$3") << endl;	//输出:Hel2018,将字符串替换为第一个和第三个表达式匹配的内容
cout << regex_replace(s, pattern, "$1$3$2") << endl;//输出:Hel2018lo,交换位置顺序

(4)忽略大小写:

string s("aaaAAA");
regex pattern("a*",regex::icase);//忽略大小写
if(regex_match(s,pattern))
    cout<<"True"<<endl;

L1-009 N个数求和(20分)

本题的要求很简单,就是求N个数字的和。麻烦的是,这些数字是以有理数分子/分母的形式给出的,你输出的和也必须是有理数的形式。

输入格式:

输入第一行给出一个正整数N(≤100)。随后一行按格式a1/b1 a2/b2 ...给出N个有理数。题目保证所有分子和分母都在长整型范围内。另外,负数的符号一定出现在分子前面。

输出格式:

输出上述数字和的最简形式 —— 即将结果写成整数部分 分数部分,其中分数部分写成分子/分母,要求分子小于分母,且它们没有公因子。如果结果的整数部分为0,则只输出分数部分。

输入样例1:

5
2/5 4/15 1/30 -2/60 8/3

输出样例1:

3 1/3

输入样例2:

2
4/3 2/3

输出样例2:

2

输入样例3:

3
1/3 -1/6 1/8

输出样例3:

7/24

解题过程:

本题目考察的即为分数间的运算,一般的思路是进行通分求和后约分,那么就需要掌握求最小公倍数和最大公因数的方法。

  • 求最大公因数:调用algorithm中的__gcd即可
  • 求最小公倍数:两个数乘积除以最大公因数即为它们的最小公倍数

我认为下面这个测试集可以更好得检测你代码的正确性,如果通分时不考虑正负大概率会被测试点5卡住:

输入样例:

3
0/1 0/1 0/1

输出样例:

-2 -1/2

同时还要防止数字过大,针对不同的实现方式可能int也能过,但是我还是建议直接longlong(因为我被long long卡了 v_v)

最终代码实现如下:

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
int main(){
	ios::sync_with_stdio(false);
	int N;
	string s;
	cin>>N;
	//存储分子分母
	vector<ll> c;//存储分子 
	vector<ll> m;//存储分母 
	//正则表达式匹配 
	regex pattern("(-*[0-9]+)/([0-9]+)");
	smatch result;
	for(int i=0;i<N;++i){
		cin>>s;
		regex_match(s,result,pattern); 
		stringstream sstr;
		ll num;
		sstr<<result[1];
		sstr>>num;
		c.push_back(num);
		sstr.clear();
		sstr<<result[2];
		sstr>>num;
		m.push_back(num);
	}
	// 进行通分
	ll lcm=1;//求分母的最小公倍数 
	for(int i=0;i<m.size();++i){
		lcm=lcm*m[i]/__gcd(lcm,m[i]);
	} 
	// 根据分母通分所称倍数改变分子
	ll sum=0;//分子求和 
	for(int i=0;i<c.size();++i){
		c[i]*=(lcm/m[i]);
		sum+=c[i]; 
	} 
	// 进行通分并输出  
	int sign=sum<0?-1:1;//记录sum的符号,便于后续操作 
	sum=abs(sum); 
	if(sum==0){
		cout<<0; 
	} 
	else{
		ll gcd=__gcd(sum,lcm);
		sum/=gcd;
		lcm/=gcd;
		if(sum<lcm) cout<<sign*sum<<"/"<<lcm;
		else{
			int a=sum/lcm;
			sum-=a*lcm;
			if(sum==0) cout<<sign*a;
			else cout<<sign*a<<" "<<sign*sum<<"/"<<lcm;
		}
	}
	return 0;
} 

L1-046 整除光棍(20分)

这里所谓的“光棍”,并不是指单身汪啦~ 说的是全部由1组成的数字,比如1、11、111、1111等。传说任何一个光棍都能被一个不以5结尾的奇数整除。比如,111111就可以被13整除。 现在,你的程序要读入一个整数x,这个整数一定是奇数并且不以5结尾。然后,经过计算,输出两个数字:第一个数字s,表示x乘以s是一个光棍,第二个数字n是这个光棍的位数。这样的解当然不是唯一的,题目要求你输出最小的解。

提示:一个显然的办法是逐渐增加光棍的位数,直到可以整除x为止。但难点在于,s可能是个非常大的数 —— 比如,程序输入31,那么就输出3584229390681和15,因为31乘以3584229390681的结果是111111111111111,一共15个1。

输入格式:

输入在一行中给出一个不以5结尾的正奇数x(<1000)。

输出格式:

在一行中输出相应的最小的sn,其间以1个空格分隔。

输入样例:

31

输出样例:

3584229390681 15

解题过程:

写在这里是因为自己在这道题卡了一下,没有立刻上手去做,主要还是对长整数除法和求余不够熟练,其实还是很简单的:

测试点1卡了一下,主要是因为长整数除法时没有考虑商中间的0,很低级的错误。

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
int main(){
	ios::sync_with_stdio(false);
	int x;
	cin>>x;
	vector<int> s;
	int r=1;
	int n=1;
	while(r!=0&&x!=1){
		r=(r*10+1)%x;
		n++;
	}
	//进行长整数除法
	int D=0;
	for(int i=0;i<n;++i){
		D=D*10+1;
		if(D>=x){
			s.push_back(D/x);
			D=D%x;
		}
		else if(s.size()!=0){
			s.push_back(0);
		}
	} 
	for(int i=0;i<s.size();++i){
		cout<<s[i];
	}
	cout<<" "<<n;
	return 0;
}

L1-059 敲笨钟(20分)

微博上有个自称“大笨钟V”的家伙,每天敲钟催促码农们爱惜身体早点睡觉。为了增加敲钟的趣味性,还会糟改几句古诗词。其糟改的方法为:去网上搜寻压“ong”韵的古诗词,把句尾的三个字换成“敲笨钟”。例如唐代诗人李贺有名句曰:“寻章摘句老雕虫,晓月当帘挂玉弓”,其中“虫”(chong)和“弓”(gong)都压了“ong”韵。于是这句诗就被糟改为“寻章摘句老雕虫,晓月当帘敲笨钟”。

现在给你一大堆古诗词句,要求你写个程序自动将压“ong”韵的句子糟改成“敲笨钟”。

输入格式:

输入首先在第一行给出一个不超过 20 的正整数 N。随后 N 行,每行用汉语拼音给出一句古诗词,分上下两半句,用逗号 , 分隔,句号 . 结尾。相邻两字的拼音之间用一个空格分隔。题目保证每个字的拼音不超过 6 个字符,每行字符的总长度不超过 100,并且下半句诗至少有 3 个字。

输出格式:

对每一行诗句,判断其是否压“ong”韵。即上下两句末尾的字都是“ong”结尾。如果是压此韵的,就按题面方法糟改之后输出,输出格式同输入;否则输出 Skipped,即跳过此句。

输入样例:

5
xun zhang zhai ju lao diao chong, xiao yue dang lian gua yu gong.
tian sheng wo cai bi you yong, qian jin san jin huan fu lai.
xue zhui rou zhi leng wei rong, an xiao chen jing shu wei long.
zuo ye xing chen zuo ye feng, hua lou xi pan gui tang dong.
ren xian gui hua luo, ye jing chun shan kong.

输出样例:

xun zhang zhai ju lao diao chong, xiao yue dang lian qiao ben zhong.
Skipped
xue zhui rou zhi leng wei rong, an xiao chen jing qiao ben zhong.
Skipped
Skipped

解题过程:

STL还是用的不太熟练,可能做的有些复杂,整体思路是:

  • 正则表达式匹配出前后句
  • 检验前后句是否都以ong结尾(注意前一句可能不足三个字符)
  • 如果满足则进行替换
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
bool isyayun(string s){
	regex pattern("([A-Za-z\\s]*),\\s([A-Za-z\\s]*).");
	smatch result;
	string s1,s2;
	if(regex_match(s,result,pattern)){
		s1=result[1];
		s2=result[2];
	}
	int index1=s1.length()-3;
	int index2=s2.length()-3;
	if(index1<0||index2<0) return false;
	if(s1.substr(index1,3).compare("ong")==0
	&&s2.substr(index2,3).compare("ong")==0)
		return true;
	return false;
}
int main(){
	ios::sync_with_stdio(false);
	int N;
	cin>>N;
	cin.ignore();
	vector<string> poetry;
	for(int i=0;i<N;++i){
		string s;
		getline(cin,s);
		poetry.push_back(s);
	}
	for(int i=0;i<N;++i){
		if(isyayun(poetry[i])){
			int index=0;
			int len=0;
			int cnt=0;
			for(int j=poetry[i].length()-1;j>=0;--j){
				len++;
				if(poetry[i][j]==' '){
					index=j;
					cnt++;
					if(cnt==3) break;
				}
			}
			poetry[i].replace(index,len," qiao ben zhong.");
			cout<<poetry[i]<<endl;
		}
		else cout<<"Skipped"<<endl;
	}
	return 0;
}

L1-064 估值一亿的AI核心代码(20分)

AI.jpg

本题要求你实现一个稍微更值钱一点的 AI 英文问答程序,规则是:

  • 无论用户说什么,首先把对方说的话在一行中原样打印出来;
  • 消除原文中多余空格:把相邻单词间的多个空格换成 1 个空格,把行首尾的空格全部删掉,把标点符号前面的空格删掉;
  • 把原文中所有大写英文字母变成小写,除了 I
  • 把原文中所有独立的 can youcould you 对应地换成 I canI could—— 这里“独立”是指被空格或标点符号分隔开的单词;
  • 把原文中所有独立的 Ime 换成 you
  • 把原文中所有的问号 ? 换成惊叹号 !
  • 在一行中输出替换后的句子作为 AI 的回答。

输入格式:

输入首先在第一行给出不超过 10 的正整数 N,随后 N 行,每行给出一句不超过 1000 个字符的、以回车结尾的用户的对话,对话为非空字符串,仅包括字母、数字、空格、可见的半角标点符号。

输出格式:

按题面要求输出,每个 AI 的回答前要加上 AI: 和一个空格。

输入样例:

6
Hello ?
 Good to chat   with you
can   you speak Chinese?
Really?
Could you show me 5
What Is this prime? I,don 't know

输出样例:

Hello ?
AI: hello!
 Good to chat   with you
AI: good to chat with you
can   you speak Chinese?
AI: I can speak chinese!
Really?
AI: really!
Could you show me 5
AI: I could show you 5
What Is this prime? I,don 't know
AI: what Is this prime! you,don't know

解题过程:

本来感觉暴力着一步步做就好,但是限制还是蛮多的:我们需要保证字符串的独立性!这就导致实使用正则表达式进行批量的替换是行不通的,因为有条件限制。

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;

int type(char c){//判断字符类型 
	if(c>='A'&&c<='Z') return 1; //大写字母
	else if(c>='a'&&c<='z') return 2;//小写字母
	else if(c>='0'&&c<='9') return 3;//数字
	else if(c==' ') return 4;//空格
	else return 5;//标点符号 
}
int check(string s,int l,int r){//检查子串的独立性 
	if((l<0||type(s[l])==4||type(s[l])==5)&&(r>=s.length()||type(s[r])==4||type(s[r])==5)){
		return 1;
	}
	else return 0;
}
int main(){
	ios::sync_with_stdio(false);
	int N;
	cin>>N;
	vector<string> Q;
	cin.ignore();
	for(int i=0;i<N;++i){
		string s;
		getline(cin,s);
		Q.push_back(s);
	}
	//遍历全部的字符串
	for(int i=0;i<N;++i){
		cout<<Q[i]<<endl;
		string A("AI: ");
		//========== 进行空格、符号和大小写处理 ==========
		int space=0;//是否有未处理的空格 
		for(int j=0;j<Q[i].length();++j){
			char c=Q[i][j];
			if(space){//有未处理的空格 
				if(type(c)==1||type(c)==2||type(c)==3){
					if(A.length()>4) A=A+' ';
					space=0;
				}
				else if(type(c)==5){
					space=0;
				}
				else continue;
			}
			if(type(c)==1&&c!='I'){//大写转小写 
				A=A+(char)tolower(c);
			}
			else if(type(c)==4){
				space=1;
				continue;
			}
			else if(type(c)==5&&c=='?'){
				A=A+'!';
			}
			else A=A+c;
		}
		//========== 进行字符串替换 ==========
		for(int j=0;j<A.length();++j){
			if(j+6<A.length()&&!A.substr(j,7).compare("can you")&&check(A,j-1,j+7))
				A.replace(j,7,"I can");
			else if(j+8<A.length()&&!A.substr(j,9).compare("could you")&&check(A,j-1,j+9))
				A.replace(j,9,"I could");
			else if(A[j]=='I'&&check(A,j-1,j+1))
				A.replace(j,1,"you");
			else if(j+1<A.length()&&!A.substr(j,2).compare("me")&&check(A,j-1,j+2))
				A.replace(j,2,"you");
		}
		cout<<A<<endl;
	} 
	return 0;
}

L1-071 前世档案(20分)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QKQjbffo-1682092803666)(images/2ab62885-0642-4a48-b41d-94e4e30d7246.jpg)]

网络世界中时常会遇到这类滑稽的算命小程序,实现原理很简单,随便设计几个问题,根据玩家对每个问题的回答选择一条判断树中的路径(如下图所示),结论就是路径终点对应的那个结点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7Grl5HYN-1682092803669)(images/822292db-6097-418b-a245-02e4a2473560.jpg)]

现在我们把结论从左到右顺序编号,编号从 1 开始。这里假设回答都是简单的“是”或“否”,又假设回答“是”对应向左的路径,回答“否”对应向右的路径。给定玩家的一系列回答,请你返回其得到的结论的编号。

输入格式:

输入第一行给出两个正整数:N(≤30)为玩家做一次测试要回答的问题数量;M(≤100)为玩家人数。

随后 M 行,每行顺次给出玩家的 N 个回答。这里用 y 代表“是”,用 n 代表“否”。

输出格式:

对每个玩家,在一行中输出其对应的结论的编号。

输入样例:

3 4
yny
nyy
nyn
yyn

输出样例:

3
5
6
2

解题过程:

本题乍一看还挺复杂的,需要去构造二叉树暴力模拟该过程吗?——显然是不需要的,参考经典的小白鼠试毒药的思路,用户的回答其实就是一串二进制编码,y对应0,n对应1,我们只需要求出对应二进制数的十进制值+1即可:

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
int ans2result(string ans){
	int result=0;
	for(int i=0;i<ans.length();++i){
		if(ans[i]=='n') result=result*2+1;
		else result=result*2+0;
	}
	return result+1;
}
int main(){
	ios::sync_with_stdio(false);
	int N,M;
	cin>>N>>M;
	for(int i=0;i<M;i++){
		string s;
		cin>>s;
		cout<<ans2result(s)<<endl;
	}
	return 0;
}

L1-088 静静的推荐(20分)

天梯赛结束后,某企业的人力资源部希望组委会能推荐一批优秀的学生,这个整理推荐名单的任务就由静静姐负责。企业接受推荐的流程是这样的:

  • 只考虑得分不低于 175 分的学生;
  • 一共接受 K 批次的推荐名单;
  • 同一批推荐名单上的学生的成绩原则上应严格递增;
  • 如果有的学生天梯赛成绩虽然与前一个人相同,但其参加过 PAT 考试,且成绩达到了该企业的面试分数线,则也可以接受。

给定全体参赛学生的成绩和他们的 PAT 考试成绩,请你帮静静姐算一算,她最多能向企业推荐多少学生?

输入格式:

输入第一行给出 3 个正整数:N(≤10^5)为参赛学生人数,K(≤5×10^3)为企业接受的推荐批次,S(≤100)为该企业的 PAT 面试分数线。

随后 N 行,每行给出两个分数,依次为一位学生的天梯赛分数(最高分 290)和 PAT 分数(最高分 100)。

输出格式:

在一行中输出静静姐最多能向企业推荐的学生人数。

输入样例:

10 2 90
203 0
169 91
175 88
175 0
175 90
189 0
189 0
189 95
189 89
256 100

输出样例:

8

样例解释:

第一批可以选择 175、189、203、256 这四个分数的学生各一名,此外 175 分 PAT 分数达到 90 分的学生和 189 分 PAT 分数达到 95 分的学生可以额外进入名单。第二批就只剩下 175、189 两个分数的学生各一名可以进入名单了。最终一共 8 人进入推荐名单。

解题过程:

本题目我一开始是按照题目要求成批次模拟的,显然是一种暴力的方法,一些测试集运行超时了,这里附上暴力的错误代码:

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
bool myCompare(pair<int,int> p1,pair<int,int> p2){
	if(p1.first==p2.first){
		return p1.second<p2.second;
	}
	else return p1.first<p2.first;
}
int main(){
	ios::sync_with_stdio(false);
	int N,K,S;
	cin>>N>>K>>S;
	vector< pair<int,int> > v;
	for(int i=0;i<N;++i){
		pair<int,int> p;
		cin>>p.first>>p.second;
		if(p.first>=175) v.push_back(p);
	}
	sort(v.begin(),v.end(),myCompare);
	int cnt=0;
	while(K>0){
		int last_score=-1;
		for(int i=0;i<v.size();){
			if(v[i].first>last_score){
				last_score=v[i].first;
				cnt++;
				v.erase(v.begin()+i,v.begin()+i+1);
			}
			else if(v[i].first==last_score&&v[i].second>=S){
				v.erase(v.begin()+i,v.begin()+i+1);
				cnt++;
			}
			else{
				i++;
			}
		}
		K--;
	} 
	cout<<cnt;
	return 0;
}

上述方法思路虽然清晰,但是十分冗杂,这显然是因为对题目理解不够所导致的,我们需要从题干中提取出如下信息:

对于分数不低于175的学生:

  • PTA成绩达标的学生一定可以进入推荐名单,无所谓批次
  • PTA成绩不达标的学生,一个分数最多只能有K个学生被推荐

那么简单的实现如下:

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
int main(){
	ios::sync_with_stdio(false);
	int N,K,S;
	cin>>N>>K>>S;
	map<int,int> score_ok;//统计分数合格人数 
	map<int,int> pta_ok;//统计pta合格人数 
	for(int i=0;i<N;++i){
		int score,pta;
		cin>>score>>pta;
		if(score>=175&&pta<S) score_ok[score]++;
		else if(score>=175&&pta>=S) pta_ok[score]++; 
	}
	int cnt=0;
	for(int i=0;i<=290;++i){
		if(score_ok[i]<=K) cnt+=score_ok[i];
		else cnt+=K;
	}
	for(int i=0;i<=290;++i){
		cnt+=pta_ok[i];
	}
	cout<<cnt; 
	return 0;
}

L2-001 紧急救援(25分)

作为一个城市的应急救援队伍的负责人,你有一张特殊的全国地图。在地图上显示有多个分散的城市和一些连接城市的快速道路。每个城市的救援队数量和每一条连接两个城市的快速道路长度都标在地图上。当其他城市有紧急求助电话给你的时候,你的任务是带领你的救援队尽快赶往事发地,同时,一路上召集尽可能多的救援队。

输入格式:

输入第一行给出4个正整数NMSD,其中N(2≤N≤500)是城市的个数,顺便假设城市的编号为0 ~ (N−1);M是快速道路的条数;S是出发地的城市编号;D是目的地的城市编号。

第二行给出N个正整数,其中第i个数是第i个城市的救援队的数目,数字间以空格分隔。随后的M行中,每行给出一条快速道路的信息,分别是:城市1、城市2、快速道路的长度,中间用空格分开,数字均为整数且不超过500。输入保证救援可行且最优解唯一。

输出格式:

第一行输出最短路径的条数和能够召集的最多的救援队数量。第二行输出从SD的路径中经过的城市编号。数字间以空格分隔,输出结尾不能有多余空格。

输入样例:

4 5 0 3
20 30 40 10
0 1 1
1 3 2
0 3 3
0 2 2
2 3 2

输出样例:

2 60
0 1 3

解题过程:

本题目显然是一道Dijkstra模板题,我们首先了解一下Dijkstra的模板:

const int MAXN=1000;//最大结点数
const int INF=1e9;//表示正无穷 
int m[MAXN][MAXN];//记录各结点间距离
map<int,int> isRelax;//标记结点是否被松弛 
map<int,int> dist;//标记源结点到各结点的距离
map<int,int> preNode;//标记前序结点
void Dijkstra(int N,int S){//N为结点个数,S为源节点 
	//初始化
	for(int i=0;i<N;++i){
		if(i!=S) dist[i]=INF;
		preNode[i]=-1;
	} 
	//松弛N个结点 
	for(int i=0;i<N;++i){
		int node=-1;//松弛结点编号
		int len=INF; 
		//选择要松弛的结点 
		for(int j=0;j<N;++j){
			if(i==0){
				node=S;
				break;
			}
			else if(dist[j]<len&&isRelax[j]==0){
				node=j;
				len=dist[j];
			}
		}
		// 对结点node进行松弛
		for(int j=0;j<N;++j){
			if(isRelax[j]==0&&dist[j]>dist[node]+m[node][j]){
				dist[j]=dist[node]+m[node][j];
				preNode[j]=node;
			}
		} 
		isRelax[node]=1;
	}
}

接着根据本题的条件对算法进行改进:

  • 我们需要寻找多条路径,那么前序结点就可能存在多个,利用vector进行存储
  • 递归统计S到D的路径数量
  • 我们需要从多条路径中寻找救援队数量最多的那条,递归遍历全部路径
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
const int MAXN=1000;//最大结点数
const int INF=1e9;//表示正无穷 
int m[MAXN][MAXN];//记录各结点间距离
map<int,int> isRelax;//标记结点是否被松弛 
map<int,int> dist;//标记源结点到各结点的距离
map<int,vector<int> > preNode;//标记前序结点,前序结点可能有很多个 
map<int,int> save;//救援队数量 
void Dijkstra(int N,int S){//N为结点个数,S为源节点 
	//初始化
	for(int i=0;i<N;++i){
		if(i!=S) dist[i]=INF;
	} 
	//松弛N个结点 
	for(int i=0;i<N;++i){
		int node=-1;//松弛结点编号
		int len=INF; 
		//选择要松弛的结点 
		for(int j=0;j<N;++j){
			if(i==0){
				node=S;
				break;
			}
			else if(dist[j]<len&&isRelax[j]==0){
				node=j;
				len=dist[j];
			}
		}
		// 对结点node进行松弛
		for(int j=0;j<N;++j){
			if(isRelax[j]==0&&dist[j]>dist[node]+m[node][j]){
				dist[j]=dist[node]+m[node][j];
				preNode[j].clear();
				preNode[j].push_back(node);
			}
			else if(isRelax[j]==0&&dist[j]==dist[node]+m[node][j]){
				preNode[j].push_back(node);
			}
		} 
		isRelax[node]=1;
	}
}
// 统计最短路径数量
int path_num(int S,int D){
	if(S==D) return 1;
	int cnt=0;
	for(int i=0;i<preNode[D].size();++i){
		cnt+=path_num(S,preNode[D][i]);
	}
	return cnt;
} 
// 寻找救援队最多的路径
pair<int,vector<int> > save_num(int S,int D){
	pair<int,vector<int> > result;
	if(S==D){
		result.first=save[S];
		result.second.push_back(S);
		return result;
	}
	result.first=0;
	for(int i=0;i<preNode[D].size();++i){
		pair<int,vector<int> > cnt=save_num(S,preNode[D][i]);
		cnt.first+=save[D];
		cnt.second.push_back(D);
		if(cnt.first>result.first){
			result.first=cnt.first;
			result.second=cnt.second;
		}
	}
	return result;
}
int main(){
	ios::sync_with_stdio(false);
	int N,M,S,D;
	cin>>N>>M>>S>>D;
	for(int i=0;i<N;++i){
		int num;
		cin>>num;
		save[i]=num;
	}
	for(int i=0;i<N;++i){
		for(int j=0;j<N;++j){
			m[i][j]=INF;
		}
	}
	for(int i=0;i<M;++i){
		int c1,c2,d;
		cin>>c1>>c2>>d;
		m[c1][c2]=d;
		m[c2][c1]=d;
	}
	Dijkstra(N,S);
	int cnt1=path_num(S,D);
	pair<int,vector<int> > cnt2=save_num(S,D);
	cout<<cnt1<<" "<<cnt2.first<<endl;
	for(int i=0;i<cnt2.second.size();++i){
		cout<<cnt2.second[i];
		if(i!=cnt2.second.size()-1) cout<<" ";
	}
	return 0;
}

L2-004 这是二叉搜索树吗?(25分)

一棵二叉搜索树可被递归地定义为具有下列性质的二叉树:对于任一结点,

  • 其左子树中所有结点的键值小于该结点的键值;
  • 其右子树中所有结点的键值大于等于该结点的键值;
  • 其左右子树都是二叉搜索树。

所谓二叉搜索树的“镜像”,即将所有结点的左右子树对换位置后所得到的树。

给定一个整数键值序列,现请你编写程序,判断这是否是对一棵二叉搜索树或其镜像进行前序遍历的结果。

输入格式:

输入的第一行给出正整数 N(≤1000)。随后一行给出 N 个整数键值,其间以空格分隔。

输出格式:

如果输入序列是对一棵二叉搜索树或其镜像进行前序遍历的结果,则首先在一行中输出 YES ,然后在下一行输出该树后序遍历的结果。数字间有 1 个空格,一行的首尾不得有多余空格。若答案是否,则输出 NO

输入样例 1:

7
8 6 5 7 10 8 11

输出样例 1:

YES
5 7 6 8 11 10 8

输入样例 2:

7
8 10 11 8 6 7 5

输出样例 2:

YES
11 8 10 7 5 6 8

输入样例 3:

7
8 6 8 5 10 9 11

输出样例 3:

NO

解题过程:

如果对树的遍历理解透彻,其实本题并不难,前序遍历的特点是第一个结点为根节点,而因为二叉搜索树的特点,我们可以根据其前序遍历的结果唯一确定树结构,对于二叉搜索树的前序遍历结果:

  • 第一个结点为根节点
  • 第一个比根节点值小的为左子树根节点,其下标即为lc
  • 第一个比根节点值大或等于的为右子树根节点,其下标即为rc

递归使用上述规则,可以构造出二叉树结构,同时我们还需要判别遍历方式是对原子树的遍历还是镜像遍历,只需要比较lc和rc大小即可,需要注意的是:

  • 如果前序遍历的是镜像子树,那么后序遍历也需要遍历镜像子树
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
vector<int> pre;//前序遍历序列 
vector<int> back;//后序遍历序列
void pre2back(int start,int end){//[start,end]为子树的前序遍历序列下标区间 
	if(start==end){
		back.push_back(pre[start]);
		return;
	}
	if(start<0||end<0) return;
	int lc=-1;//左子树根节点下标 
	int rc=-1;//右子树根节点下标
	int root=pre[start];//子树根节点值 
	for(int i=start+1;i<=end;++i){
		if(pre[i]<root&&lc==-1) lc=i;
		if(pre[i]>=root&&rc==-1) rc=i;
		if(lc!=-1&&rc==-1&&pre[i]>=root) return;
		if(rc!=-1&&lc==-1&&pre[i]<root) return;
		if(rc!=-1&&lc!=-1&&rc>lc&&pre[i]<root) return;
		if(rc!=-1&&lc!=-1&&lc>rc&&pre[i]>=root) return; 
	}
	if(lc<rc){//前序遍历原子树 
		pre2back(lc,rc==-1?end:rc-1);
		pre2back(rc,end);
	}
	else if(lc>rc){//前序遍历镜像子树 
		pre2back(rc,lc==-1?end:lc-1);
		pre2back(lc,end);
	}
	back.push_back(root);
	return;
}
int main(){
	ios::sync_with_stdio(false);
	int N;
	cin>>N;
	for(int i=0;i<N;++i){
		int a;
		cin>>a;
		pre.push_back(a);
	}
	pre2back(0,N-1);
	// 输出结果 
	if(back.size()==pre.size()){
		cout<<"YES"<<endl;
		for(int i=0;i<back.size();++i){
			cout<<back[i];
			if(i!=back.size()-1) cout<<" ";
		}
	}
	else {
		cout<<"NO";
	}
	return 0;
}

L2-005 集合相似度(25分)

给定两个整数集合,它们的相似度定义为:Nc/Nt×100%。其中Nc是两个集合都有的不相等整数的个数,Nt是两个集合一共有的不相等整数的个数。你的任务就是计算任意一对给定集合的相似度。

输入格式:

输入第一行给出一个正整数N(≤50),是集合的个数。随后N行,每行对应一个集合。每个集合首先给出一个正整数M(≤104),是集合中元素的个数;然后跟M个[0,109]区间内的整数。

之后一行给出一个正整数K(≤2000),随后K行,每行对应一对需要计算相似度的集合的编号(集合从1到N编号)。数字间以空格分隔。

输出格式:

对每一对需要计算的集合,在一行中输出它们的相似度,为保留小数点后2位的百分比数字。

输入样例:

3
3 99 87 101
4 87 101 5 87
7 99 101 18 5 135 18 99
2
1 2
1 3

输出样例:

50.00%
33.33%

解题过程:

本题模拟即可,不再过多赘述,需要注意一个问题是:对于set,find的复杂度要比insert多,能用find解决的不要用insert

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
float same(set<int> s1,set<int> s2){//默认s2.size()>=s1.size() 
	int size=s2.size();
	float Nc=0;
	float Nt=0; 
	for(set<int>::iterator p=s1.begin();p!=s1.end();p++){
		if(s2.find(*p)!=s2.end()) Nc++;
	}
	Nt=s2.size()+s1.size()-Nc;
	return Nc/Nt*100;
}
int main(){
	ios::sync_with_stdio(false);
	vector< set<int> > v;
	int N;cin>>N;
	for(int i=0;i<N;++i){
		int M;cin>>M;
		set<int> s;
		for(int j=0;j<M;++j){
			int a;cin>>a;
			s.insert(a);
		}
		v.push_back(s);
	}
	int K;cin>>K;
	for(int i=0;i<K;++i){
		int a1,a2;
		cin>>a1>>a2;
		a1--;a2--;
		cout<<setiosflags(ios_base::fixed)<<setprecision(2);
		if(v[a1].size()<=v[a2].size()) cout<<same(v[a1],v[a2])<<"%"<<endl;
		else cout<<same(v[a2],v[a1])<<"%"<<endl;
	}
	return 0;
}

L2-006 树的遍历(25分)

给定一棵二叉树的后序遍历和中序遍历,请你输出其层序遍历的序列。这里假设键值都是互不相等的正整数。

输入格式:

输入第一行给出一个正整数N(≤30),是二叉树中结点的个数。第二行给出其后序遍历序列。第三行给出其中序遍历序列。数字间以空格分隔。

输出格式:

在一行中输出该树的层序遍历的序列。数字间以1个空格分隔,行首尾不得有多余空格。

输入样例:

7
2 3 1 5 7 6 4
1 2 3 4 5 6 7

输出样例:

4 1 6 3 5 7 2

解题过程:

作为巩固,先实现一下由后序遍历+中序遍历得出前序遍历的递归实现:

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
vector<int> back;//后续遍历序列
vector<int> mid;//中序遍历序列
vector<int> pre;//前序遍历序列 
void back_mid2pre(int b1,int b2,int m1,int m2){
//当前检索的子树在后序遍历上的下标区间为[b1,b2] 
//当前检索的子树在中序遍历上的下标区间为[m1,m2] 
	if(b1<0||b2<0||m1<0||m2<0) return;
	if(b1>b2||m1>m2) return;
	int root=back[b2];//子树根节点
	pre.push_back(root); 
	if(b1==b2) return;
	int index;//根节点在中序遍历序列中的下标 
	for(int i=m1;i<=m2;++i){
		if(mid[i]==root){
			index=i;break; 
		}
	} 
	int l_len=index-m1;//左子树区间长度 
	int r_len=m2-index;//右子树区间长度 
	back_mid2pre(b1,b1+l_len-1,m1,m1+l_len-1);//检索左子树
	back_mid2pre(b1+l_len,b2-1,index+1,m2);//检索右子树
	return;
}
int main(){
	ios::sync_with_stdio(false);
	int N;cin>>N;
	for(int i=0;i<N;++i){
		int a;cin>>a;
		back.push_back(a);
	} 
	for(int i=0;i<N;++i){
		int a;cin>>a;
		mid.push_back(a);
	} 
	back_mid2pre(0,N-1,0,N-1);
	for(int i=0;i<pre.size();++i){
		cout<<pre[i];
		if(i!=pre.size()-1) cout<<" ";
	}
	return 0;
}

而本题是要我们实现:后序遍历+中序遍历->层序遍历,大体思路是相同的,只需要改很少量的代码:

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
vector<int> back;//后续遍历序列
vector<int> mid;//中序遍历序列
vector<int> seq[30];//层序遍历序列 
void back_mid2seq(int b1,int b2,int m1,int m2,int s){
//当前检索的子树在后序遍历上的下标区间为[b1,b2] 
//当前检索的子树在中序遍历上的下标区间为[m1,m2]
//当前检索的子树根节点处在第s层
	if(b1<0||b2<0||m1<0||m2<0) return;
	if(b1>b2||m1>m2) return;
	int root=back[b2];//子树根节点
	seq[s].push_back(root);
	int index;//根节点在中序遍历序列中的下标 
	for(int i=m1;i<=m2;++i){
		if(mid[i]==root){
			index=i;break; 
		}
	} 
	int l_len=index-m1;//左子树区间长度 
	int r_len=m2-index;//右子树区间长度
	back_mid2seq(b1,b1+l_len-1,m1,m1+l_len-1,s+1);//检索左子树
	back_mid2seq(b1+l_len,b2-1,index+1,m2,s+1);//检索右子树
	return;
}
int main(){
	ios::sync_with_stdio(false);
	int N;cin>>N;
	for(int i=0;i<N;++i){
		int a;cin>>a;
		back.push_back(a);
	} 
	for(int i=0;i<N;++i){
		int a;cin>>a;
		mid.push_back(a);
	} 
	back_mid2seq(0,N-1,0,N-1,0);
	for(int i=0;seq[i].size()>0;++i){
		for(int j=0;j<seq[i].size();++j){
			cout<<seq[i][j];
			if(j!=seq[i].size()-1) cout<<" ";
		}
		if(i+1<30&&seq[i+1].size()>0) cout<<" ";
	}
	return 0;
}

L2-007 家庭房产(25分)

给定每个人的家庭成员和其自己名下的房产,请你统计出每个家庭的人口数、人均房产面积及房产套数。

输入格式:

输入第一行给出一个正整数N(≤1000),随后N行,每行按下列格式给出一个人的房产:

编号 父 母 k 孩子1 ... 孩子k 房产套数 总面积

其中编号是每个人独有的一个4位数的编号;分别是该编号对应的这个人的父母的编号(如果已经过世,则显示-1);k(0≤k≤5)是该人的子女的个数;孩子i是其子女的编号。

输出格式:

首先在第一行输出家庭个数(所有有亲属关系的人都属于同一个家庭)。随后按下列格式输出每个家庭的信息:

家庭成员的最小编号 家庭人口数 人均房产套数 人均房产面积

其中人均值要求保留小数点后3位。家庭信息首先按人均面积降序输出,若有并列,则按成员编号的升序输出。

输入样例:

10
6666 5551 5552 1 7777 1 100
1234 5678 9012 1 0002 2 300
8888 -1 -1 0 1 1000
2468 0001 0004 1 2222 1 500
7777 6666 -1 0 2 300
3721 -1 -1 1 2333 2 150
9012 -1 -1 3 1236 1235 1234 1 100
1235 5678 9012 0 1 50
2222 1236 2468 2 6661 6662 1 300
2333 -1 3721 3 6661 6662 6663 1 100

输出样例:

3
8888 1 1.000 1000.000
0001 15 0.600 100.000
5551 4 0.750 100.000

解题过程:

本题对我来说还是比较复杂的,主要是用的方法太贴近模拟了,不过还是拿满了分数,大体思路如下:

  • 设置一个vector记录各个家庭的信息
  • 设置一个map记录每个编号对应的人属于哪个家庭
  • 对于一组输入(包括本人、父母、孩子)中的编号:
    • 如果全部编号都没有所属家庭,则创建一个新家庭
    • 如果所有有所属家庭的编号的所属家庭相同,则将该组编号合并至那个家庭
    • 如果存在有所属家庭的编号的所属家庭不同,说明这组编号可以建立几组家庭之间的亲属关系,应该将这些家庭合并,我们先将家庭联系记录下来,将该组输入合并至最后一个家庭
  • 根据记录的合并关系合并家庭
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
struct info{
	int people;//最小编号 
	int p_num;//人口数 
	int house;//家庭房产数
	int h_num;//房产面积 
};
bool myCompare(info d1,info d2){
	if(abs(float(d1.h_num)/d1.p_num-float(d2.h_num)/d2.p_num)<0.0000001){
		return d1.people<d2.people;
	}
	else return float(d1.h_num)/d1.p_num>float(d2.h_num)/d2.p_num;
}
map<int,int> merage;//merage[a]=b表示家庭b合并到家庭a 
int merage_final(int i){//返回第i个家庭应该合并到哪个家庭 
	if(merage[i]==0) return i;
	else return merage_final(merage[i]); 
}
int main(){
	ios::sync_with_stdio(false);
	map<int,int> family;//map[a]=b表示编号为a的人属于家庭b,家庭由1-N编号
	vector<info> v;//记录各个家庭信息
	info b;v.push_back(b);//为了使下标从1开始
	int N;cin>>N;
	for(int i=0;i<N;++i){
		int p,f,m,k;cin>>p>>f>>m>>k;
		vector<int> child;
		int index=0;//最大家庭编号 
		index=max(index,family[p]);
		index=max(index,family[f]);
		index=max(index,family[m]);
		for(int j=0;j<k;++j){
			int a;cin>>a;
			child.push_back(a);
			index=max(index,family[a]);
		}
		//检索合并关系
		set<int> s;
		if(family[p]>0) s.insert(family[p]);
		if(family[f]>0) s.insert(family[f]);
		if(family[m]>0) s.insert(family[m]);
		for(int j=0;j<k;++j){
			if(family[child[j]]>0) s.insert(family[child[j]]);
		}
		if(s.size()>1){
			int final=*s.begin();
			set<int>::iterator p=s.begin();p++;
			for(;p!=s.end();++p){
				merage[*p]=final;
			}
		}
		if(index==0){//新家庭 
			index=v.size();
			info a;
			a.p_num=1; 
			a.people=p;
			family[p]=index;
			if(f!=-1){
				a.p_num+=1;
				family[f]=index;
				a.people=min(a.people,f);
			}
			if(m!=-1){
				a.p_num+=1;
				family[m]=index;
				a.people=min(a.people,m);
			}
			for(int j=0;j<k;++j){
				if(child[j]!=-1){
					a.p_num+=1;
					family[child[j]]=index;
					a.people=min(a.people,child[j]);
				}
			}
			cin>>a.house>>a.h_num;
			v.push_back(a);
		}
		else{//家庭已存在 
			info a=v[index];
			if(family[p]==0){
				family[p]=index;
				a.p_num+=1;
				a.people=min(a.people,p);
			}
			if(f!=-1&&family[f]==0){
				family[f]=index;
				a.p_num+=1;
				a.people=min(a.people,f);
			}
			if(m!=-1&&family[m]==0){
				family[m]=index;
				a.p_num+=1;
				a.people=min(a.people,m);
			}
			for(int j=0;j<k;++j){
				if(child[j]!=-1&&family[child[j]]==0){
					family[child[j]]=index;
					a.p_num+=1;
					a.people=min(a.people,child[j]);
				}
			}
			int house,h_num;
			cin>>house>>h_num;
			a.house+=house,a.h_num+=h_num;
			v[index]=a;
		}
	} 
	vector<info> ans;
	for(int i=1;i<v.size();++i){//进行合并 
		int final= merage_final(i);
		if(final!=i){//需要进行合并 
			v[final].people=min(v[final].people,v[i].people);
			v[final].p_num+=v[i].p_num;
			v[final].house+=v[i].house;
			v[final].h_num+=v[i].h_num;
			v[i].people=-1;
		}
	}
	for(int i=1;i<v.size();++i){
		if(v[i].people!=-1){
			ans.push_back(v[i]);
		}
	}
	cout<<ans.size()<<endl;
	sort(ans.begin(),ans.end(),myCompare);
	for(int i=0;i<ans.size();++i){
		cout<<setw(4)<<setfill('0')<<ans[i].people<<" ";
		cout<<ans[i].p_num<<" ";
		cout<<setiosflags(ios_base::fixed)<<setprecision(3);
		cout<<float(ans[i].house)/ans[i].p_num<<" "<<float(ans[i].h_num)/ans[i].p_num<<endl;
	}
	return 0;
}

L2-012 关于堆的判断(25分)

将一系列给定数字顺序插入一个初始为空的小顶堆H[]。随后判断一系列相关命题是否为真。命题分下列几种:

  • x is the rootx是根结点;
  • x and y are siblingsxy是兄弟结点;
  • x is the parent of yxy的父结点;
  • x is a child of yxy的一个子结点。

输入格式:

每组测试第1行包含2个正整数N(≤ 1000)和M(≤ 20),分别是插入元素的个数、以及需要判断的命题数。下一行给出区间[−10000,10000]内的N个要被插入一个初始为空的小顶堆的整数。之后M行,每行给出一个命题。题目保证命题中的结点键值都是存在的。

输出格式:

对输入的每个命题,如果其为真,则在一行中输出T,否则输出F

输入样例:

5 4
46 23 26 24 10
24 is the root
26 and 23 are siblings
46 is the parent of 23
23 is a child of 10

输出样例:

F
T
F
T

解题过程:

首先要了解最小堆的结构:

  • 最小堆是一棵完全二叉树
  • 全部的非叶子结点值都不大于它的任意叶子结点值

如何存储最小堆?我认为没有必要去模拟树结构,利用vector按照层序遍历结果去存储即可,对于下标为i的结点有如下特点:

  • 其父节点的下标为:
    ⌊ i − 1 2 ⌋ \lfloor\frac{i-1}2\rfloor 2i1

  • 其左子节点和右子节点的下标分别为:
    2 i + 1        2 i + 2 2i+1~~~~~~2i+2 2i+1      2i+2

那么本题的重点就是小顶堆的构造与更新,这部分操作也很简单,只需要逐次比较结点与其父节点的值即可实现结点上浮

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
vector<int> v;//小顶堆 
void insert(int a){
	v.push_back(a);
	int index=v.size()-1;
	int f=floor((index-1)/2.0);
	while(f>=0){
		if(v[f]>v[index]){
			swap(v[f],v[index]);
		}
		index=f;
		f=floor((f-1)/2.0);
	}
	return;
} 
int main(){
	ios::sync_with_stdio(false);
	int N,M;
	cin>>N>>M;
	for(int i=0;i<N;++i){
		int a;cin>>a;
		insert(a);
	}
	map<int,int> m;//构造值与下标的对应关系
	for(int i=0;i<v.size();++i){
		m[v[i]]=i;
	} 
	cin.ignore();
	string s;
	for(int i=0;i<M;++i){
		getline(cin,s);
		if(s.find("root")!=string::npos){
			if(v[0]==stod(s)) cout<<"T"<<endl;
			else cout<<"F"<<endl; 
		}
		else if(s.find("siblings")!=string::npos){
			int a1=stod(s);
			int a2=stod(s.substr(s.find("and ")+4,6));
			int f1=floor((m[a1]-1)/2.0);
			int f2=floor((m[a2]-1)/2.0);
			if(f1==f2) cout<<"T"<<endl;
			else cout<<"F"<<endl;
		}
		else if(s.find("parent")!=string::npos){
			int a1=stod(s);
			int a2=stod(s.substr(s.find("of ")+3));
			int f2=floor((m[a2]-1)/2.0);
			if(v[f2]==a1) cout<<"T"<<endl;
			else cout<<"F"<<endl;
		}
		else if(s.find("child")!=string::npos){
			int a1=stod(s);
			int a2=stod(s.substr(s.find("of ")+3));
			int f1=floor((m[a1]-1)/2.0);
			if(v[f1]==a2) cout<<"T"<<endl;
			else cout<<"F"<<endl;
		}
	}
	return 0;
}

L2-013 红色警报(25分)

战争中保持各个城市间的连通性非常重要。本题要求你编写一个报警程序,当失去一个城市导致国家被分裂为多个无法连通的区域时,就发出红色警报。注意:若该国本来就不完全连通,是分裂的k个区域,而失去一个城市并不改变其他城市之间的连通性,则不要发出警报。

输入格式:

输入在第一行给出两个整数N(0 < N ≤ 500)和M(≤ 5000),分别为城市个数(于是默认城市从0到N-1编号)和连接两城市的通路条数。随后M行,每行给出一条通路所连接的两个城市的编号,其间以1个空格分隔。在城市信息之后给出被攻占的信息,即一个正整数K和随后的K个被攻占的城市的编号。

注意:输入保证给出的被攻占的城市编号都是合法的且无重复,但并不保证给出的通路没有重复。

输出格式:

对每个被攻占的城市,如果它会改变整个国家的连通性,则输出Red Alert: City k is lost!,其中k是该城市的编号;否则只输出City k is lost.即可。如果该国失去了最后一个城市,则增加一行输出Game Over.

输入样例:

5 4
0 1
1 3
3 0
0 4
5
1 2 0 4 3

输出样例:

City 1 is lost.
City 2 is lost.
Red Alert: City 0 is lost!
City 4 is lost.
City 3 is lost.
Game Over.

解题过程:

这道题对于并查集来说再合适不过了,其实家庭房产那道题用并查集做也会更简单,首先了解一下何为并查集:

int N;//元素数量
int fa[N];
int rnk[N];
void init(){
	for(int i=0;i<N;++i){
		fa[i]=i;
		rnk[i]=1;
	}
	return;
}
int find(int x){
	if(fa[x]==x) return x;
	else{
		fa[x]=find(fa[x]);
		return fa[x];
	}
}
void Union(int i,int j){
	int x=find(i);
	int y=find(j);
	if(rnk[x]<=rnk[y]){
		fa[x]=y;
	}
	else fa[y]=x;
	if(rnk[x]==rnk[y]&&x!=y) rnk[y]++;
	return;
}

那么作用到本题,代码如下:

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
// map<int,int> fa;
// map<int,int> rnk;
int fa[500]={0};
int rnk[500]={0};
vector<pair<int,int> > road;
map<int,int> lost;
int N,M,K;
void init(){
	for(int i=0;i<N;++i){
		fa[i]=i;
		rnk[i]=1;
	}
	return;
}
int find(int x){
	if(fa[x]==x) return x;
	else{
		fa[x]=find(fa[x]);
		return fa[x];
	}
}
void Union(int i,int j){
	int x=find(i);
	int y=find(j);
	if(rnk[x]<=rnk[y]){
		fa[x]=y;
	}
	else fa[y]=x;
	if(rnk[x]==rnk[y]&&x!=y) rnk[y]++;
	return;
}
int region(){
	int cnt=0;
	for(int i=0;i<N;++i){
		if(lost[i]==0&&fa[i]==i){
			cnt++;
		}
	}
	return cnt;
}
int main(){
	ios::sync_with_stdio(false);
	cin>>N>>M;
	init();
	for(int i=0;i<M;++i){
		pair<int,int> p;
		cin>>p.first>>p.second;
		road.push_back(p);
		Union(p.first,p.second);
	}
	//统计城市数量
	int cnt=region();
	//开始丢失城市 
	cin>>K;
	for(int i=0;i<K;++i){
		int k;cin>>k;
		lost[k]=1;
		//重新构造并查集
		init();
		for(int j=0;j<road.size();++j){
			if(lost[road[j].first]||lost[road[j].second]) continue;
			Union(road[j].first,road[j].second);
		} 
		int cnt1=region();
		if(cnt>=cnt1){
			cnt=cnt1;
			cout<<"City "<<k<<" is lost."<<endl;
		}
		else{
			cnt=cnt1;
			cout<<"Red Alert: ";
			cout<<"City "<<k<<" is lost!"<<endl;
		}
		if(cnt==0){
			cout<<"Game Over."<<endl;
			break;
		}
	}
	return 0;
}

在本题我又学到了一个知识点:能直接开数组的时候,不要用map!!!!

L2-014 列车调度(25分)

火车站的列车调度铁轨的结构如下图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vA3M6l22-1682092803671)(images/188)]

两端分别是一条入口(Entrance)轨道和一条出口(Exit)轨道,它们之间有N条平行的轨道。每趟列车从入口可以选择任意一条轨道进入,最后从出口离开。在图中有9趟列车,在入口处按照{8,4,2,5,3,9,1,6,7}的顺序排队等待进入。如果要求它们必须按序号递减的顺序从出口离开,则至少需要多少条平行铁轨用于调度?

输入格式:

输入第一行给出一个整数N (2 ≤ N ≤105),下一行给出从1到N的整数序号的一个重排列。数字间以空格分隔。

输出格式:

在一行中输出可以将输入的列车按序号递减的顺序调离所需要的最少的铁轨条数。

输入样例:

9
8 4 2 5 3 9 1 6 7

输出样例:

4

解题过程:

见面先暴力试试,直接使用queue进行模拟:

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
vector< queue<int> > v;//每个队列对应一个轨道 
int m[100001]={-1};//map[a]=b表示编号为a的列车在队列v[b]中等待 
int wait;//等待驶出的火车编号
void inset(int a){
	for(int i=0;i<v.size();++i){
		if(v[i].size()==0||a<v[i].back()){
			v[i].push(a);
			m[a]=i; 
			return;
		}
	}
	queue<int> q;
	q.push(a);
	v.push_back(q);
	m[a]=v.size()-1;
	return;
}
void go(){
	for(int i=wait;i>0;--i){
		if(m[i]!=-1&&v[m[i]].front()==wait){
			v[m[i]-1].pop();
			wait--;
		}
		else break;
	}
	return;
}
int main(){
	ios::sync_with_stdio(false);
	int N;cin>>N;
	wait=N;
	for(int i=0;i<N;++i){
		int a;cin>>a;
		inset(a);
		go();
	}
	cout<<v.size();
	return 0;
}

果然。。。两个测试集超时了,不亏是我,从网上看到的用set解决的方法十分的巧妙:

  • 我们不需要记录铁轨上全部的列车数量,只需要记录每列铁轨上的等待列车的最小值即可
  • 对于新进入铁轨的列车,如果每列铁轨上的最小值都比它的编号小,新开一列铁轨;否则用该列车编号去替换任意一列最小值比它大的铁轨最小值编号(lower_bound直接选择即可)
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
int main(){
	ios::sync_with_stdio(false);
	int N;cin>>N;
	set<int> s;
	for(int i=0;i<N;++i){
		int a=0;cin>>a;
		set<int>::iterator p=s.lower_bound(a);
		if(p!=s.end()){
			s.erase(p);
		}
		s.insert(a);
	}
	cout<<s.size();
	return 0;
}

L2-018 多项式A除以B(25分)

这仍然是一道关于A/B的题,只不过A和B都换成了多项式。你需要计算两个多项式相除的商Q和余R,其中R的阶数必须小于B的阶数。

输入格式:

输入分两行,每行给出一个非零多项式,先给出A,再给出B。每行的格式如下:

N e[1] c[1] ... e[N] c[N]

其中N是该多项式非零项的个数,e[i]是第i个非零项的指数,c[i]是第i个非零项的系数。各项按照指数递减的顺序给出,保证所有指数是各不相同的非负整数,所有系数是非零整数,所有整数在整型范围内。

输出格式:

分两行先后输出商和余,输出格式与输入格式相同,输出的系数保留小数点后1位。同行数字间以1个空格分隔,行首尾不得有多余空格。注意:零多项式是一个特殊多项式,对应输出为0 0 0.0。但非零多项式不能输出零系数(包括舍入后为0.0)的项。在样例中,余多项式其实有常数项-1/27,但因其舍入后为0.0,故不输出。

输入样例:

4 4 1 2 -3 1 -1 0 -1
3 2 3 1 -2 0 1

输出样例:

3 2 0.3 1 0.2 0 -1.0
1 1 -3.1

解题过程:

理解多项式除法过程后暴力模拟即可:

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;

int main(){
	ios::sync_with_stdio(false);
	
	map<int,double> A;
	map<int,double> B;
	map<int,double> Q;
	vector<int> B_e;
	int A_e;
	int N,e;double c;
	cin>>N;
	for(int i=0;i<N;++i){
		cin>>e>>c;
		A[e]=c;
		if(i==0) A_e=e;
	}
	cin>>N;
	for(int i=0;i<N;++i){
		cin>>e>>c;
		B[e]=c;
		B_e.push_back(e);
	}
	int max_e=max(A_e,B_e[0]);
	while(A_e>=B_e[0]){
		e=A_e-B_e[0];
		c=A[A_e]/B[B_e[0]];
		Q[e]=c;
		for(int i=0;i<B_e.size();++i){
			A[B_e[i]+e]-=B[B_e[i]]*c;
		}
		A_e--;
	}
	//去除0多项式 
	vector<pair<int,double> > Q_ans;
	vector<pair<int,double> > R_ans;
	for(int i=max_e;i>=0;--i){
		pair<int,double> p;
		if(abs(Q[i])>=0.05){
			p.first=i;
			p.second=Q[i];
			Q_ans.push_back(p);
		}
		if(abs(A[i])>=0.05){
			p.first=i;
			p.second=A[i];
			R_ans.push_back(p);
		}
	}
	//输出答案 
	if(Q_ans.size()==0) cout<<"0 0 0.0"<<endl;
	else{
		cout<<Q_ans.size()<<" ";
		for(int i=0;i<Q_ans.size();++i){
			cout<<Q_ans[i].first<<" ";
			cout<<setiosflags(ios_base::fixed)<<setprecision(1);
			cout<<Q_ans[i].second;
			if(i!=Q_ans.size()-1) cout<<" ";
			else cout<<endl;
		}
	}
	if(R_ans.size()==0) cout<<"0 0 0.0"<<endl;
	else{
		cout<<R_ans.size()<<" ";
		for(int i=0;i<R_ans.size();++i){
			cout<<R_ans[i].first<<" ";
			cout<<setiosflags(ios_base::fixed)<<setprecision(1);
			cout<<R_ans[i].second;
			if(i!=R_ans.size()-1) cout<<" ";
			else cout<<endl;
		}
	}
	return 0;
}

L2-020 功夫传人(25分)

一门武功能否传承久远并被发扬光大,是要看缘分的。一般来说,师傅传授给徒弟的武功总要打个折扣,于是越往后传,弟子们的功夫就越弱…… 直到某一支的某一代突然出现一个天分特别高的弟子(或者是吃到了灵丹、挖到了特别的秘笈),会将功夫的威力一下子放大N倍 —— 我们称这种弟子为“得道者”。

这里我们来考察某一位祖师爷门下的徒子徒孙家谱:假设家谱中的每个人只有1位师傅(除了祖师爷没有师傅);每位师傅可以带很多徒弟;并且假设辈分严格有序,即祖师爷这门武功的每个第i代传人只能在第i-1代传人中拜1个师傅。我们假设已知祖师爷的功力值为Z,每向下传承一代,就会减弱r%,除非某一代弟子得道。现给出师门谱系关系,要求你算出所有得道者的功力总值。

输入格式:

输入在第一行给出3个正整数,分别是:N(≤105)——整个师门的总人数(于是每个人从0到N−1编号,祖师爷的编号为0);Z——祖师爷的功力值(不一定是整数,但起码是正数);r ——每传一代功夫所打的折扣百分比值(不超过100的正数)。接下来有N行,第i行(i=0,⋯,N−1)描述编号为i的人所传的徒弟,格式为:

K**i ID[1] ID[2] ⋯ ID[K**i]

其中K**i是徒弟的个数,后面跟的是各位徒弟的编号,数字间以空格间隔。K**i为零表示这是一位得道者,这时后面跟的一个数字表示其武功被放大的倍数。

输出格式:

在一行中输出所有得道者的功力总值,只保留其整数部分。题目保证输入和正确的输出都不超过1010。

输入样例:

10 18.0 1.00
3 2 3 5
1 9
1 4
1 7
0 7
2 6 1
1 8
0 9
0 4
0 3

输出样例:

404

解题过程:

本题难度并不高,利用记忆化递归就可以基本解决,只需要注意祖师爷也可以是得道者即可。

此处我犯的错误是:没有用double和long long!

当发现很多测试样例无法通过,而且输出可能很大时,要考虑是否数据类型是否太小了!

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
ll master[100001];//记录每个人的师傅
double ability[100001];//记录每个人的功力 
double niu[100001];//记录得到者的功力 
ll N;
double Z;
double r;
double gongli(int i){//获取弟子i的功力 
	if(ability[i]!=0) return ability[i];
	else if(niu[i]==0){
		ability[i]=(1-r/100)*gongli(master[i]);
		return ability[i];
	}
	else if(niu[i]!=0){
		ability[i]=niu[i]*(1-r/100)*gongli(master[i]);
		return ability[i];
	}
}
int main(){
	ios::sync_with_stdio(false);
	cin>>N>>Z>>r;
	for(int i=0;i<N;++i){
		int K;cin>>K;
		if(K==0){
			double a;
			cin>>a;
			niu[i]=a;
		} 
		else{
			for(int j=0;j<K;++j){
				ll a;cin>>a;
				master[a]=i;
			}
		}
	}
	ability[0]=Z;
	if(niu[0]!=0) ability[0]*=niu[0];
	double sum=0;
	for(int i=0;i<N;++i){
		if(niu[i]!=0) sum+=gongli(i);
	}
	cout<<ll(sum);
	return 0;
}

L2-025 分而治之(25分)

分而治之,各个击破是兵家常用的策略之一。在战争中,我们希望首先攻下敌方的部分城市,使其剩余的城市变成孤立无援,然后再分头各个击破。为此参谋部提供了若干打击方案。本题就请你编写程序,判断每个方案的可行性。

输入格式:

输入在第一行给出两个正整数 N 和 M(均不超过10 000),分别为敌方城市个数(于是默认城市从 1 到 N 编号)和连接两城市的通路条数。随后 M 行,每行给出一条通路所连接的两个城市的编号,其间以一个空格分隔。在城市信息之后给出参谋部的系列方案,即一个正整数 K (≤ 100)和随后的 K 行方案,每行按以下格式给出:

Np v[1] v[2] ... v[Np]

其中 Np 是该方案中计划攻下的城市数量,后面的系列 v[i] 是计划攻下的城市编号。

输出格式:

对每一套方案,如果可行就输出YES,否则输出NO

输入样例:

10 11
8 7
6 8
4 5
8 4
8 1
1 2
1 4
9 8
9 1
1 10
2 4
5
4 10 3 8 4
6 6 1 7 5 4 9
3 1 8 4
2 2 8
7 9 8 7 6 5 4 2

输出样例:

NO
YES
YES
NO
NO

解题过程:

还是网上的方法巧妙,既不需要用map存稀疏矩阵,也不需要太高的时间复杂度:

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
int main(){
	ios::sync_with_stdio(false);
	vector<pair<int,int> > e;
	int visit[10001];
	int N,M;
	cin>>N>>M;
	for(int i=0;i<M;++i){
		int u,v;
		cin>>u>>v;
		pair<int,int> p(u,v);
		e.push_back(p);
	}
	int K;cin>>K;
	for(int i=0;i<K;++i){
		memset(visit,0,sizeof(visit));
		int Np;cin>>Np;
		for(int j=0;j<Np;++j){
			int c;cin>>c;
			visit[c]=1;
		}
		int flag=1;
		for(int j=0;j<e.size();++j){
			if(visit[e[j].first]==0&&visit[e[j].second]==0){
				flag=0;break;
			}
		}
		if(flag==0) cout<<"NO"<<endl;
		else cout<<"YES"<<endl;
	}
	return 0;
}

L2-028 秀恩爱分得快(25分)

古人云:秀恩爱,分得快。

互联网上每天都有大量人发布大量照片,我们通过分析这些照片,可以分析人与人之间的亲密度。如果一张照片上出现了 K 个人,这些人两两间的亲密度就被定义为 1/K。任意两个人如果同时出现在若干张照片里,他们之间的亲密度就是所有这些同框照片对应的亲密度之和。下面给定一批照片,请你分析一对给定的情侣,看看他们分别有没有亲密度更高的异性朋友?

输入格式:

输入在第一行给出 2 个正整数:N(不超过1000,为总人数——简单起见,我们把所有人从 0 到 N-1 编号。为了区分性别,我们用编号前的负号表示女性)和 M(不超过1000,为照片总数)。随后 M 行,每行给出一张照片的信息,格式如下:

K P[1] ... P[K]

其中 K(≤ 500)是该照片中出现的人数,P[1] ~ P[K] 就是这些人的编号。最后一行给出一对异性情侣的编号 A 和 B。同行数字以空格分隔。题目保证每个人只有一个性别,并且不会在同一张照片里出现多次。

输出格式:

首先输出 A PA,其中 PA 是与 A 最亲密的异性。如果 PA 不唯一,则按他们编号的绝对值递增输出;然后类似地输出 B PB。但如果 AB 正是彼此亲密度最高的一对,则只输出他们的编号,无论是否还有其他人并列。

输入样例 1:

10 4
4 -1 2 -3 4
4 2 -3 -5 -6
3 2 4 -5
3 -6 0 2
-3 2

输出样例 1:

-3 2
2 -5
2 -6

输入样例 2:

4 4
4 -1 2 -3 0
2 0 -3
2 2 -3
2 -1 2 
-3 2

输出样例 2:

-3 2

解题过程:

简单的模拟,但是坑很多,重点是如何处理“-0”,同时需要注意:

  • 只有当A和B同时互为对方亲密度最高者时,才输出A B
  • 否则,输出A B全部的亲密度最高者
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
vector<vector<float> > v;
int gender[1001];
int main(){
	ios::sync_with_stdio(false);
	memset(gender,-1,sizeof(gender));
	int N,M;cin>>N>>M;
	for(int i=0;i<N;++i){
		vector<float> v0;
		for(int j=0;j<N;++j){
			v0.push_back(0.0);
		}
		v.push_back(v0);
	}
	for(int i=0;i<M;++i){
		int K;cin>>K;
		vector<int> v0;
		for(int j=0;j<K;++j){
			string s;cin>>s;
			if(s[0]=='-') gender[abs(stoi(s))]=0;
			else gender[stoi(s)]=1;
			v0.push_back(abs(stoi(s)));
		}
		for(int j=0;j<K-1;++j){
			for(int k=j+1;k<K;++k){
				int jj=v0[j];
				int kk=v0[k];
				if(gender[jj]!=gender[kk]){
					v[jj][kk]+=1.0/K;
					v[kk][jj]+=1.0/K;
				}
			}
		}
	}
	
	int A,B;
	string As,Bs;
	cin>>As>>Bs;
	if(As[0]=='-') gender[abs(stoi(As))]=0;
	else gender[abs(stoi(As))]=1;
	if(Bs[0]=='-') gender[abs(stoi(Bs))]=0;
	else gender[abs(stoi(Bs))]=1;
	A=abs(stoi(As));
	B=abs(stoi(Bs));
	
	float Amax=*max_element(v[A].begin(),v[A].end());
	float Bmax=*max_element(v[B].begin(),v[B].end());
	vector<int> PA;
	vector<int> PB;
	for(int i=0;i<N;++i){
		if(abs(v[A][i]-Amax)<0.000001) PA.push_back(i);
		if(abs(v[B][i]-Bmax)<0.000001) PB.push_back(i);
	}
	if(abs(v[A][B]-Amax)<0.000001&&abs(v[B][A]-Bmax)<0.000001){
		if(gender[A]==0) cout<<"-";
		cout<<A<<" ";
		if(gender[B]==0) cout<<"-";
		cout<<B<<endl;
	}
	else{
		for(int i=0;i<PA.size();++i){
			if(gender[A]==0) cout<<"-";
			cout<<A<<" ";
			if(gender[PA[i]]==0) cout<<"-";
			cout<<PA[i]<<endl;
		}
		for(int i=0;i<PB.size();++i){
			if(gender[B]==0) cout<<"-";
			cout<<B<<" ";
			if(gender[PB[i]]==0) cout<<"-";
			cout<<PB[i]<<endl;
		}
	}
	return 0;
}

L2-029 特立独行的幸福(25分)

对一个十进制数的各位数字做一次平方和,称作一次迭代。如果一个十进制数能通过若干次迭代得到 1,就称该数为幸福数。1 是一个幸福数。此外,例如 19 经过 1 次迭代得到 82,2 次迭代后得到 68,3 次迭代后得到 100,最后得到 1。则 19 就是幸福数。显然,在一个幸福数迭代到 1 的过程中经过的数字都是幸福数,它们的幸福是依附于初始数字的。例如 82、68、100 的幸福是依附于 19 的。而一个特立独行的幸福数,是在一个有限的区间内不依附于任何其它数字的;其独立性就是依附于它的的幸福数的个数。如果这个数还是个素数,则其独立性加倍。例如 19 在区间[1, 100] 内就是一个特立独行的幸福数,其独立性为 2×4=8。

另一方面,如果一个大于1的数字经过数次迭代后进入了死循环,那这个数就不幸福。例如 29 迭代得到 85、89、145、42、20、4、16、37、58、89、…… 可见 89 到 58 形成了死循环,所以 29 就不幸福。

本题就要求你编写程序,列出给定区间内的所有特立独行的幸福数和它的独立性。

输入格式:

输入在第一行给出闭区间的两个端点:1<A<B≤104。

输出格式:

按递增顺序列出给定闭区间 [A,B] 内的所有特立独行的幸福数和它的独立性。每对数字占一行,数字间以 1 个空格分隔。

如果区间内没有幸福数,则在一行中输出 SAD

输入样例 1:

10 40

输出样例 1:

19 8
23 6
28 3
31 4
32 3

**注意:**样例中,10、13 也都是幸福数,但它们分别依附于其他数字(如 23、31 等等),所以不输出。其它数字虽然其实也依附于其它幸福数,但因为那些数字不在给定区间 [10, 40] 内,所以它们在给定区间内是特立独行的幸福数。

输入样例 2:

110 120

输出样例 2:

SAD

解题过程:

第一眼并查集,第二眼貌似是没有必要的,用简单的递归即可,需要完成的工作包括:

  • 判断一个数是否为素数
  • 计算一个数各位的平方和
  • 利用递归判断一个数是否是幸福数(本题不用记忆化递归也不会超时)
  • 注意循环过程中前面的数对后面的数的影响,以及后面的数对前面的数的影响
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;

int A,B;
int duli[10001];//独立性 
int isduli[10001];//是否特立独行 
bool isSushu(int n){//判断是否为素数 
	for(int i=2;i<=sqrt(n);++i){
		if(n%i==0) return false;
	}
	return true;
} 
int bitSum(int n){//求n的各位平方和 
	int sum=0;
	while(n>0){
		sum+=(n%10)*(n%10);
		n/=10;
	} 
	return sum;
}
vector<int> v;//记录每个数在递归过程中的依赖值 
bool isXingFu(int n){//判断n是否幸福 
	int sum=bitSum(n);
	if(sum==1){
		v.push_back(n);
		return true;
	}
	else{
		if(find(v.begin(),v.end(),n)==v.end()) {
			v.push_back(n);
			return isXingFu(sum);
		}
		else return false;//陷入死循环 
	}
}
int main(){
	ios::sync_with_stdio(false);
	cin>>A>>B;
	memset(isduli,-1,sizeof(isduli));
	for(int i=A;i<=B;++i){
		v.clear();
		if(isduli[i]!=0&&isXingFu(i)){
			isduli[i]=1; 
			duli[i]=isSushu(i)?2*(v.size()):v.size();
			for(int j=1;j<v.size();++j){
				isduli[v[j]]=0;
			}
		}
	}
	int flag=1;
	for(int i=A;i<=B;++i){
		if(isduli[i]==1) {
			flag=0;
			cout<<i<<" "<<duli[i]<<endl;
		}
	}
	if(flag) cout<<"SAD"<<endl;
	return 0;
}

L2-036 网红点打卡攻略(25分)

一个旅游景点,如果被带火了的话,就被称为“网红点”。大家来网红点游玩,俗称“打卡”。在各个网红点打卡的快(省)乐(钱)方法称为“攻略”。你的任务就是从一大堆攻略中,找出那个能在每个网红点打卡仅一次、并且路上花费最少的攻略。

输入格式:

首先第一行给出两个正整数:网红点的个数 N(1<N≤200)和网红点之间通路的条数 M。随后 M 行,每行给出有通路的两个网红点、以及这条路上的旅行花费(为正整数),格式为“网红点1 网红点2 费用”,其中网红点从 1 到 N 编号;同时也给出你家到某些网红点的花费,格式相同,其中你家的编号固定为 0

再下一行给出一个正整数 K,是待检验的攻略的数量。随后 K 行,每行给出一条待检攻略,格式为:

n V1 V2 ⋯ V**n

其中 n(≤200) 是攻略中的网红点数,V**i 是路径上的网红点编号。这里假设你从家里出发,从 V1 开始打卡,最后从 V**n 回家。

输出格式:

在第一行输出满足要求的攻略的个数。

在第二行中,首先输出那个能在每个网红点打卡仅一次、并且路上花费最少的攻略的序号(从 1 开始),然后输出这个攻略的总路费,其间以一个空格分隔。如果这样的攻略不唯一,则输出序号最小的那个。

题目保证至少存在一个有效攻略,并且总路费不超过 109。

输入样例:

6 13
0 5 2
6 2 2
6 0 1
3 4 2
1 5 2
2 5 1
3 1 1
4 1 2
1 6 1
6 3 2
1 2 1
4 5 3
2 0 2
7
6 5 1 4 3 6 2
6 5 2 1 6 3 4
8 6 2 1 6 3 4 5 2
3 2 1 5
6 6 1 3 4 5 2
7 6 2 1 3 4 5 2
6 5 2 1 4 3 6

输出样例:

3
5 11

样例说明:

第 2、3、4、6 条都不满足攻略的基本要求,即不能做到从家里出发,在每个网红点打卡仅一次,且能回到家里。所以满足条件的攻略有 3 条。

第 1 条攻略的总路费是:(0->5) 2 + (5->1) 2 + (1->4) 2 + (4->3) 2 + (3->6) 2 + (6->2) 2 + (2->0) 2 = 14;

第 5 条攻略的总路费同理可算得:1 + 1 + 1 + 2 + 3 + 1 + 2 = 11,是一条更省钱的攻略;

第 7 条攻略的总路费同理可算得:2 + 1 + 1 + 2 + 2 + 2 + 1 = 11,与第 5 条花费相同,但序号较大,所以不输出。

解题过程:

本题可能会下意识得认为需要使用Floyd算法,但是仔细分析样例之后会发现,判断一个攻略是否合理,只需要保证相邻访问的两个点之间存在一条不存在中间结点的路径,即这两个结点之间存在一条边。

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
ll Map[202][202];
int main(){
	ios::sync_with_stdio(false);
	memset(Map,0,sizeof(Map));
	ll N,M;
	cin>>N>>M;
	for(int i=0;i<M;++i){
		ll u,v,p;
		cin>>u>>v>>p;
		Map[u][v]=p;
		Map[v][u]=p;
	}
	ll K;cin>>K;
	ll cnt=0;
	ll min_len=1000000000;
	ll ans=-1;
	for(int i=0;i<K;++i){
		ll n;cin>>n;
		vector<ll> v;
		set<ll> s;
		for(int j=0;j<n;++j){
			ll a;cin>>a;
			v.push_back(a);
			s.insert(a);
		}
		if(s.size()!=N||v.size()!=N) continue;
		else if(Map[0][v[0]]==0||Map[0][v[N-1]]==0) continue;
		else{
			ll len=Map[0][v[0]]+Map[0][v[N-1]];
			int flag=0;
			for(int j=0;j<v.size()-1;++j){
				if(Map[v[j]][v[j+1]]==0){
					flag=1;break;
				}
				len+=Map[v[j]][v[j+1]];
			}
			if(flag) continue;
			cnt++;
			if(len<min_len){
				min_len=len;
				ans=i+1;
			}
		}
	}
	cout<<cnt<<endl;
	cout<<ans<<" "<<min_len<<endl;
	return 0;
} 

L2-042 老板的作息表(25分)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FYWQDYxK-1682092803673)(images/f4125c83-7ab2-4e3f-bc52-048a723803ee.png)]

新浪微博上有人发了某老板的作息时间表,表示其每天 4:30 就起床了。但立刻有眼尖的网友问:这时间表不完整啊,早上九点到下午一点干啥了?

本题就请你编写程序,检查任意一张时间表,找出其中没写出来的时间段。

输入格式:

输入第一行给出一个正整数 N,为作息表上列出的时间段的个数。随后 N 行,每行给出一个时间段,格式为:

hh:mm:ss - hh:mm:ss

其中 hhmmss 分别是两位数表示的小时、分钟、秒。第一个时间是开始时间,第二个是结束时间。题目保证所有时间都在一天之内(即从 00:00:00 到 23:59:59);每个区间间隔至少 1 秒;并且任意两个给出的时间区间最多只在一个端点有重合,没有区间重叠的情况。

输出格式:

按照时间顺序列出时间表中没有出现的区间,每个区间占一行,格式与输入相同。题目保证至少存在一个区间需要输出。

输入样例:

8
13:00:00 - 18:00:00
00:00:00 - 01:00:05
08:00:00 - 09:00:00
07:10:59 - 08:00:00
01:00:05 - 04:30:00
06:30:00 - 07:10:58
05:30:00 - 06:30:00
18:00:00 - 19:00:00

输出样例:

04:30:00 - 05:30:00
07:10:58 - 07:10:59
09:00:00 - 13:00:00
19:00:00 - 23:59:59

解题过程:

先来丢个人,第一次我想到的使用差分数组,逐秒覆盖,找到未被覆盖的时间段,但是这样的方法不仅复杂,而且会超时,错误代码如下:

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
map<int,int> t;//记录各秒是否被覆盖 
map<int,int> a;//差分数组 :a[i]=t[i]-t[i-1]; 
int main(){
	ios::sync_with_stdio(false);
	for(int i=0;i<86400;++i){ 
		a[i]=t[i]-t[i-1];
	}
	int N;cin>>N;
	for(int i=0;i<N;++i){
		string s;cin>>s;
		char c;cin>>c;
		string e;cin>>e;
		int start=stoi(s.substr(0,2))*3600+stoi(s.substr(3,2))*60+stoi(s.substr(6,2));
		int end=stoi(e.substr(0,2))*3600+stoi(e.substr(3,2))*60+stoi(e.substr(6,2));
		a[start]+=1;
		a[end]-=1;
	}
	for(int i=0;i<86400;++i){
		t[i]=a[i]+t[i-1];
	}
	int start=-1;
	int end=-1;
	vector<string> ans;
	for(int i=0;i<86400;++i){
		if(t[i]==0&&start==-1){
			start=i;
		}
		if(t[i]!=0&&start!=-1){
			end=i;
			
			string sh=to_string(start/3600);
			if(sh.length()==1) sh="0"+sh;
			start-=(start/3600)*3600;
			string sm=to_string(start/60);
			if(sm.length()==1) sm="0"+sm;
			start-=(start/60)*60;
			string ss=to_string(start);
			if(ss.length()==1) ss="0"+ss;
			
			string eh=to_string(end/3600);
			if(eh.length()==1) eh="0"+eh;
			end-=(end/3600)*3600;
			string em=to_string(end/60);
			if(em.length()==1) em="0"+em;
			end-=(end/60)*60;
			string es=to_string(end);
			if(es.length()==1) es="0"+es;
			
			string s=sh+":"+sm+":"+ss+" - "+eh+":"+em+":"+es;
			ans.push_back(s);
			start=-1;
			end=-1;
		}
	}
	if(start!=-1&&end==-1){
		string sh=to_string(start/3600);
		if(sh.length()==1) sh="0"+sh;
		start-=(start/3600)*3600;
		string sm=to_string(start/60);
		if(sm.length()==1) sm="0"+sm;
		start-=(start/60)*60;
		string ss=to_string(start);
		if(ss.length()==1) ss="0"+ss;
		
		string s=sh+":"+sm+":"+ss+" - 23:59:59";
		ans.push_back(s);
	}
	for(int i=0;i<ans.size();++i){
		cout<<ans[i]<<endl;
	}
	return 0;
} 

其实我们只需要将各个时间段存储至set,遍历set即可:

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#include<regex>
using namespace std;
typedef long long ll;
int main(){
	ios::sync_with_stdio(false);
	int N;cin>>N;
	set<pair<string,string> > t;
	for(int i=0;i<N;++i){
		string s1;cin>>s1;
		char c;cin>>c;
		string s2;cin>>s2;
		pair<string,string> p(s1,s2);
		t.insert(p);
	}
	pair<string,string> p("23:59:59","");
	t.insert(p);
	string start="00:00:00";
	set<pair<string,string> >::iterator i;
	for(i=t.begin();i!=t.end();i++){
		if(start<i->first) cout<<start<<" - "<<i->first<<endl;
		start=i->second;
	}
	return 0;
} 
  • 3
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考 程序设计竞赛相关代码、设计文档、使用说明,供学习参考

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值