PTA的天梯赛与PAT练习记录

PTA的天梯赛与PAT练习记录

    • L1-078吉老师的回归
    • L1-080乘法口诀数列
    • L2-005集合相似度
    • L2-014列车调度
    • L2-025分而治之
    • L2-029特立独行的幸福
    • L2-030冰岛人
    • L2-032彩虹瓶
    • L2-038病毒溯源
    • L2-039清点代码库
    • L3-003社交集群
    • L3-028森森旅游
    • 1010一元多项式求导
    • 1012数字分类
    • 1014福尔摩斯的约会
    • 1028人口普查
    • 1030完美数列
    • 1033旧键盘打字
    • 1034有理数四则运算

L1-078吉老师的回归

曾经在天梯赛大杀四方的吉老师决定回归天梯赛赛场啦!

为了简化题目,我们不妨假设天梯赛的每道题目可以用一个不超过 500 的、只包括可打印符号的字符串描述出来,如:Problem A: Print “Hello world!”。

众所周知,吉老师的竞赛水平非常高超,你可以认为他每道题目都会做(事实上也是……)。因此,吉老师会按照顺序看题并做题。但吉老师水平太高了,所以签到题他就懒得做了(浪费时间),具体来说,假如题目的字符串里有 qiandao 或者 easy(区分大小写)的话,吉老师看完题目就会跳过这道题目不做。

现在给定这次天梯赛总共有几道题目以及吉老师已经做完了几道题目,请你告诉大家吉老师现在正在做哪个题,或者吉老师已经把所有他打算做的题目做完了。

输入格式:
输入第一行是两个正整数 N,M (1≤M≤N≤30),表示本次天梯赛有 N 道题目,吉老师现在做完了 M 道。
接下来 N 行,每行是一个符合题目描述的字符串,表示天梯赛的题目内容。吉老师会按照给出的顺序看题——第一行就是吉老师看的第一道题,第二行就是第二道,以此类推。

输出格式:
在一行中输出吉老师当前正在做的题目对应的题面(即做完了 M 道题目后,吉老师正在做哪个题)。如果吉老师已经把所有他打算做的题目做完了,输出一行 Wo AK le。

输入样例 1:

5 1
L1-1 is a qiandao problem.
L1-2 is so...easy.
L1-3 is Easy.
L1-4 is qianDao.
Wow, such L1-5, so easy.

输出样例 1:

L1-4 is qianDao.

输入样例 2:

5 4
L1-1 is a-qiandao problem.
L1-2 is so easy.
L1-3 is Easy.
L1-4 is qianDao.
Wow, such L1-5, so!!easy.

输出样例 2:

Wo AK le

m是做完的题数,签到题是看了跳过,不算在m里,即使m为0,只要当前是签到题就会跳过,因此m的判断不能放在字符串的判断前,不然会出现当前是签到题却退出循环的情况

#include
#include
#include
using namespace std;

string s[40];

int main()
{
	int n,m,i;
	scanf("%d%d",&n,&m);
	getchar();
	for(i=0;i<n;++i)
		getline(cin,s[i]);
	for(i=0;i<n;++i)
	{
		if(s[i].find("easy")==-1&&s[i].find("qiandao")==-1)
		{
			if(m==0)break;
			--m;
		}
	}
	if(i==n)puts("Wo AK le");
	else cout<<s[i]<<endl;
	return 0;
}

L1-080乘法口诀数列

本题要求你从任意给定的两个 1 位数字 a 1 a_1 a1 a 2 a_2 a2开始,用乘法口诀生成一个数列 { a n a_n an​​},规则为从 a 1 a_1 a1开始顺次进行,每次将当前数字与后面一个数字相乘,将结果贴在数列末尾。如果结果不是 1 位数,则其每一位都应成为数列的一项。

输入格式:
输入在一行中给出 3 个整数,依次为 a 1 a_1 a1 a 2 a_2 a2和 n,满足 0 ≤ a ​ 1 , a 2 ≤ 9 0≤a_​1,a_2≤9 0a1,a29 0 < n ≤ 1 0 3 00<n103

输出格式:
在一行中输出数列的前 n 项。数字间以 1 个空格分隔,行首尾不得有多余空格。

输入样例:

2 3 10

输出样例:

2 3 6 1 8 6 8 4 8 4

样例解释:
数列前 2 项为 2 和 3。从 2 开始,因为 2×3=6,所以第 3 项是 6。因为 3×6=18,所以第 4、5 项分别是 1、8。依次类推…… 最后因为第 6 项有 6×8=48,对应第 10、11 项应该是 4、8。而因为只要求输出前 10 项,所以在输出 4 后结束。


wa了一个点是因为x>=10写成x>10(@_@)

#include
#include
#include
using namespace std;

vector<int>vec;

int main()
{
	int a1,a2,n;
	scanf("%d%d%d",&a1,&a2,&n);
	vec.push_back(a1);
	vec.push_back(a2);
	for(int i=1;vec.size()<n;++i)
	{
		int x=vec[i]*vec[i-1];
		if(x>=10)
		{
			vec.push_back(x/10);
			vec.push_back(x%10);
		}
		else vec.push_back(x);
	}	
	printf("%d",vec[0]);
	for(int i=1;i<n;++i)
		printf(" %d",vec[i]);
	puts("");
	return 0;
}

L2-005集合相似度

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

输入格式:
输入第一行给出一个正整数N(≤50),是集合的个数。随后N行,每行对应一个集合。每个集合首先给出一个正整数M( ≤ 1 0 4 ≤10^4 104),是集合中元素的个数;然后跟M个 [ 0 , 1 0 9 ] [0,10^9] [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%

最多用 1 0 4 10^4 104个集合,不然会超时,统计两个集合都有的不相等整数个数,两个集合的总数减去重复的一半即为两个集合一共有的不相等整数个数

#include
#include
#include
using namespace std;

const int N=1e4+10;
unordered_set<int>S[N];

int main()
{
    int n,m,x,y;
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        scanf("%d",&m);
        while(m--)
        {
            scanf("%d",&x);
            S[i].insert(x);
        }
    }
    scanf("%d",&m);
    while(m--)
    {
        scanf("%d%d",&x,&y);
        int cnt=0,sum=S[x].size()+S[y].size();
        for(int item:S[x])
            if(S[y].count(item))++cnt;
        printf("%.2lf%%\n",1.0*cnt/(sum-cnt)*100);
    }
    return 0;
}

L2-014列车调度

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

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

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

输入样例:

9
8 4 2 5 3 9 1 6 7

输出样例:

4

维护每条轨道的最小值,列车x应放到比它大且最接近它的列车后面,更新最小值,原因:如果随便放到比它大的列车y后面,可能会出现列车只能放到列车y后面,此时它就另外需要一条轨道,不是最优解,把列车x放到合适的轨道就不会发生

#include
#include
#include
using namespace std;

set<int>S;

int main()
{
    int n,x;
    scanf("%d",&n);
    while(n--)
    {
        scanf("%d",&x);
        auto it=S.lower_bound(x);
        if(it!=S.end())S.erase(it);
        S.insert(x);
    }
    printf("%d\n",S.size());
    return 0;
}

L2-025分而治之

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

输入格式:
输入在第一行给出两个正整数 N 和 M(均不超过10000),分别为敌方城市个数(于是默认城市从 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

标记当前方案要攻下的城市,判断所有边,只要有一条边的两个点还连通就不是孤立无援

#include
#include
#include
using namespace std;

const int N=10010;
struct edge{
    int x,y;
}e[N];

int main()
{
    int n,m,k,x;
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;++i)
        scanf("%d%d",&e[i].x,&e[i].y);
    scanf("%d",&n);
    while(n--)
    {
        bool flag=true;
        unordered_set<int>S;
        scanf("%d",&k);
        while(k--)
        {
            scanf("%d",&x);
            S.insert(x);
        }
        for(int i=0;i<m;++i)
            if(S.count(e[i].x)==0&&S.count(e[i].y)==0)
            {
                flag=false;
                break;
            }
        if(flag)puts("YES");
        else puts("NO");
    }
    return 0;
}

L2-029特立独行的幸福

对一个十进制数的各位数字做一次平方和,称作一次迭代。如果一个十进制数能通过若干次迭代得到 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 ≤ 1 0 4 11<A<B104

输出格式:
按递增顺序列出给定闭区间 [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

一开始写了两个迭代器删来删去人都晕了,要懂得选择容易实现的方法。在每个数字演化时要标记他经过的数字,不然当这个数字在后面出现且不会被标记时就会出错,而他本身不用标记,除非其他数字演化过来把他标记。求出1就记录初始数字经过的数量,求出1的数字中没有被经过的才是答案。写的时候出现代码块错误修改外部循环值,重复定义外部引用变量等问题_(:з」∠)_
第4个点过不去的,这里提供一组数据以供测试

67 220
#include
#include
#include
#include
using namespace std;

const int N=1e4+10;
bool vis[N];
map<int,int>MAP;
 
bool isprime(int n)
{
    if(n<2)return false;
	for(int i=2;i*i<=n;++i)
		if(n%i==0)
			return false;
	return true;
} 
int f(int x)
{
	int sum=0;
	while(x)
	{
		int t=x%10;
		sum+=t*t;
		x/=10;
	}
	return sum;
}
int main()
{
	int a,b;
	bool flag=false;
	scanf("%d%d",&a,&b);
	for(int i=a;i<=b;++i)
	{
		int cnt=1,t=i;
		unordered_set<int>S;
		while(true)
		{
			t=f(t);
			if(S.count(t)||t==1)break;
			++cnt;
			vis[t]=true;
			S.insert(t);
		} 
		if(t==1)MAP[i]=cnt;
	}
	for(auto item:MAP)
	{
        int x=item.first;
		if(!vis[x])
		{
			printf("%d ",x); 
			int sum=item.second;
			if(isprime(x))sum*=2;	
			printf("%d\n",sum);
			flag=true;
		}
	}
	if(!flag)puts("SAD");
	return 0;
} 

L2-030冰岛人

2018年世界杯,冰岛队因1:1平了强大的阿根廷队而一战成名。好事者发现冰岛人的名字后面似乎都有个“松”(son),于是有网友科普如下:
冰岛人沿用的是维京人古老的父系姓制,孩子的姓等于父亲的名加后缀,如果是儿子就加 sson,女儿则加 sdottir。因为冰岛人口较少,为避免近亲繁衍,本地人交往前先用个 App 查一下两人祖宗若干代有无联系。本题就请你实现这个 App 的功能。

输入格式:
输入首先在第一行给出一个正整数 N( 1 < N ≤ 1 0 5 11<N105),为当地人口数。随后 N 行,每行给出一个人名,格式为:名 姓(带性别后缀),两个字符串均由不超过 20 个小写的英文字母组成。维京人后裔是可以通过姓的后缀判断其性别的,其他人则是在姓的后面加 m 表示男性、f 表示女性。题目保证给出的每个维京家族的起源人都是男性。

随后一行给出正整数 M,为查询数量。随后 M 行,每行给出一对人名,格式为:名1 姓1 名2 姓2。注意:这里的姓是不带后缀的。四个字符串均由不超过 20 个小写的英文字母组成。

题目保证不存在两个人是同名的。

输出格式:
对每一个查询,根据结果在一行内显示以下信息:

  • 若两人为异性,且五代以内无公共祖先,则输出 Yes;
  • 若两人为异性,但五代以内(不包括第五代)有公共祖先,则输出 No;
  • 若两人为同性,则输出 Whatever;
  • 若有一人不在名单内,则输出 NA。

所谓“五代以内无公共祖先”是指两人的公共祖先(如果存在的话)必须比任何一方的曾祖父辈分高。

输入样例:

15
chris smithm
adam smithm
bob adamsson
jack chrissson
bill chrissson
mike jacksson
steve billsson
tim mikesson
april mikesdottir
eric stevesson
tracy timsdottir
james ericsson
patrick jacksson
robin patricksson
will robinsson
6
tracy tim james eric
will robin tracy tim
april mike steve bill
bob adam eric steve
tracy tim tracy tim
x man april mikes

输出样例:

Yes
No
No
Whatever
Whatever
NA

虽然题目给定当地人口数最大为 1 0 5 10^5 105,但存在所有人的姓都没有在名的位置上出现的情况,所以数组要开到 2 × 1 0 5 2×10^5 2×105以上且要初始化整个数组

以m和f区分性别的不用去除姓的后缀

判断性别要在判断五代以内有无公共祖先之前,题目已指明

#include
#include
#include
using namespace std;

const int N=2e5+10;
int fa[N];
bool sex[N];
unordered_map<string,int>name;

bool find(int x,int y) 
{
    int sum1=0;
    for(int i=x;i!=-1;i=fa[i],++sum1)
    {
        int sum2=0;
        for(int j=y;j!=-1;j=fa[j],++sum2)
        {
            if(sum1>=4&&sum2>=4)break;//剪枝,为了遍历两点到根路径上的所有情况,不能退出整个循环
            if(i==j)return true;//五代以内有公共祖先
        }
    }
    return false;
}
int main()
{
    int n,m,cnt=0;
    string a,b,s;
    for(int i=0;i<N;++i)
        fa[i]=-1;
    scanf("%d",&n);
    for(int i=0;i<n;++i)
    {
        cin>>a>>b;
        s=b;
        if(name.count(a)==0)
            name[a]=cnt++;
        int len=b.size();
        if(b[len-1]=='n'||b[len-1]=='m')
            sex[name[a]]=true;
        if(b[len-1]=='n')
            s=b.substr(0,len-4);
        else if(b[len-1]!='m'&&b[len-1]!='f')
            s=b.substr(0,len-7);
        if(name.count(s)==0)
            name[s]=cnt++;
        fa[name[a]]=name[s];
    }
    scanf("%d",&m);
    while(m--)
    {
        cin>>a>>s>>b>>s;
        if(name.count(a)==0||name.count(b)==0)
            puts("NA");
        else if(sex[name[a]]==sex[name[b]])
            puts("Whatever");
        else if(find(name[a],name[b]))
            puts("No");
        else puts("Yes");
    }
    return 0;
}

L2-032彩虹瓶

彩虹瓶的制作过程(并不)是这样的:先把一大批空瓶铺放在装填场地上,然后按照一定的顺序将每种颜色的小球均匀撒到这批瓶子里。

假设彩虹瓶里要按顺序装 N 种颜色的小球(不妨将顺序就编号为 1 到 N)。现在工厂里有每种颜色的小球各一箱,工人需要一箱一箱地将小球从工厂里搬到装填场地。如果搬来的这箱小球正好是可以装填的颜色,就直接拆箱装填;如果不是,就把箱子先码放在一个临时货架上,码放的方法就是一箱一箱堆上去。当一种颜色装填完以后,先看看货架顶端的一箱是不是下一个要装填的颜色,如果是就取下来装填,否则去工厂里再搬一箱过来。

如果工厂里发货的顺序比较好,工人就可以顺利地完成装填。例如要按顺序装填 7 种颜色,工厂按照 7、6、1、3、2、5、4 这个顺序发货,则工人先拿到 7、6 两种不能装填的颜色,将其按照 7 在下、6 在上的顺序堆在货架上;拿到 1 时可以直接装填;拿到 3 时又得临时码放在 6 号颜色箱上;拿到 2 时可以直接装填;随后从货架顶取下 3 进行装填;然后拿到 5,临时码放到 6 上面;最后取了 4 号颜色直接装填;剩下的工作就是顺序从货架上取下 5、6、7 依次装填。

但如果工厂按照 3、1、5、4、2、6、7 这个顺序发货,工人就必须要愤怒地折腾货架了,因为装填完 2 号颜色以后,不把货架上的多个箱子搬下来就拿不到 3 号箱,就不可能顺利完成任务。

另外,货架的容量有限,如果要堆积的货物超过容量,工人也没办法顺利完成任务。例如工厂按照 7、6、5、4、3、2、1 这个顺序发货,如果货架够高,能码放 6 只箱子,那还是可以顺利完工的;但如果货架只能码放 5 只箱子,工人就又要愤怒了……

本题就请你判断一下,工厂的发货顺序能否让工人顺利完成任务。

输入格式:
输入首先在第一行给出 3 个正整数,分别是彩虹瓶的颜色数量 N( 1 < N ≤ 1 0 3 11<N103)、临时货架的容量 M( 随后 K 行,每行给出 N 个数字,是 1 到N 的一个排列,对应工厂的发货顺序。
一行中的数字都以空格分隔。

输出格式:
对每个发货顺序,如果工人可以愉快完工,就在一行中输出 YES;否则输出 NO。

输入样例:

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

输出样例:

YES
NO
NO

这道题是一般的栈模拟题,然而第一个样例一直答案错误,对比正确题解发现,原本代码是一边读入一边处理,中途当货物无法放入已满货架退出循环,会导致后面的数据没有读入而答案错误_(:з)∠)_,正确代码如下

#include
#include
#include
using namespace std;

const int N=1e3+10;
int a[N];

int main()
{
	int n,m,k;
	scanf("%d%d%d",&n,&m,&k);
	while(k--)
	{
		int now=1;
		bool flag=true;
		stack<int>sta;
		for(int i=0;i<n;++i)
			scanf("%d",&a[i]);
		for(int i=0;i<n;++i)
		{
			if(a[i]==now)
			{
				++now;
				while(sta.size()>0&&sta.top()==now)
				{
					sta.pop();
					++now;
				}
			}
			else if(sta.size()==m)
			{
				flag=false;
				break;
			}
			else sta.push(a[i]);
		}
		if(!flag||now<n)puts("NO");
		else puts("YES");
	}
	return 0;
}

L2-038病毒溯源

病毒容易发生变异。某种病毒可以通过突变产生若干变异的毒株,而这些变异的病毒又可能被诱发突变产生第二代变异,如此继续不断变化。
现给定一些病毒之间的变异关系,要求你找出其中最长的一条变异链。
在此假设给出的变异都是由突变引起的,不考虑复杂的基因重组变异问题 —— 即每一种病毒都是由唯一的一种病毒突变而来,并且不存在循环变异的情况。

输入格式:
输入在第一行中给出一个正整数 N(≤ 1 0 4 10^4 104),即病毒种类的总数。于是我们将所有病毒从 0 到 N−1 进行编号。
随后 N 行,每行按以下格式描述一种病毒的变异情况:
k 变异株1 …… 变异株k
其中 k 是该病毒产生的变异毒株的种类数,后面跟着每种变异株的编号。第 i 行对应编号为 i 的病毒(0≤i

输出格式:
首先输出从源头开始最长变异链的长度。
在第二行中输出从源头开始最长的一条变异链,编号间以 1 个空格分隔,行首尾不得有多余空格。如果最长链不唯一,则输出最小序列。
注:我们称序列 { a 1 , ⋯ , a n a_1,⋯,a_n a1,,an} 比序列 { b 1 , ⋯ , b n b_1,⋯,b_n b1,,bn} “小”,如果存在 1≤k≤n 满足 a i = b i a_i=b_i ai=bi对所有 i a k < b k a_kak<bk

输入样例:

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

输出样例:

4
0 4 9 1

比赛时把int类型的fa数组开成bool类型,每次循环下标加一来找根结点找到的只有0和1_(:з)∠)_

#include
#include
#include
using namespace std;

const int N=1e4+10;
int n,fa[N];
bool map[N][N];
vector<int>vec,ans;

void dfs(int x)
{
    if(ans.size()<vec.size())
        ans=vec;
	for(int i=0;i<n;++i)
    {
        if(map[x][i])
        {
            vec.push_back(i);
            dfs(i);
            vec.pop_back();
        }
    }
}
int main()
{
	int k,x;
	scanf("%d",&n);
	for(int i=0;i<n;++i)
		fa[i]=-1;
	for(int i=0;i<n;++i)
	{
		scanf("%d",&k);
		for(int j=0;j<k;++j)
		{
			scanf("%d",&x);
			fa[x]=i;
			map[i][x]=true;
		}
	}
	x=0;
	while(fa[x]!=-1)x=fa[x];
	dfs(x);
    int len=ans.size();
	printf("%d\n%d",len+1,x);
	for(int i=0;i<len;++i)
		printf(" %d",ans[i]);
	puts("");
	return 0;	
}

L2-039清点代码库

上图转自新浪微博:“阿里代码库有几亿行代码,但其中有很多功能重复的代码,比如单单快排就被重写了几百遍。请设计一个程序,能够将代码库中所有功能重复的代码找出。各位大佬有啥想法,我当时就懵了,然后就挂了。。。”

这里我们把问题简化一下:首先假设两个功能模块如果接受同样的输入,总是给出同样的输出,则它们就是功能重复的;其次我们把每个模块的输出都简化为一个整数(在 int 范围内)。于是我们可以设计一系列输入,检查所有功能模块的对应输出,从而查出功能重复的代码。你的任务就是设计并实现这个简化问题的解决方案。

输入格式:
输入在第一行中给出 2 个正整数,依次为 N( ≤ 1 0 4 ≤10^4 104)和 M( ≤ 1 0 2 ≤10^2 102),对应功能模块的个数和系列测试输入的个数。
随后 N 行,每行给出一个功能模块的 M 个对应输出,数字间以空格分隔。

输出格式:
首先在第一行输出不同功能的个数 K。随后 K 行,每行给出具有这个功能的模块的个数,以及这个功能的对应输出。数字间以 1 个空格分隔,行首尾不得有多余空格。输出首先按模块个数非递增顺序,如果有并列,则按输出序列的递增序给出。

注:所谓数列 { A 1 , . . . , A M A_1, ..., A_M A1,...,AM} 比 { B 1 , . . . , B M B_1, ..., B_M B1,...,BM} 大,是指存在 1≤i A 1 = B 1 , . . . , A i = B i A_1=B_1,...,A_i=B_i A1=B1...Ai=Bi成立,且 A i + 1 > B i + 1 A_{i+1}>B_{i+1} Ai+1>Bi+1

输入样例:

7 3
35 28 74
-1 -1 22
28 74 35
-1 -1 22
11 66 0
35 28 74
35 28 74

输出样例:

4
3 35 28 74
2 -1 -1 22
1 11 66 0
1 28 74 35

比赛时写unordered_map用vector映射其他类型报错了,写map又乱改,其实map可以用vector映射其他类型

#include
#include
#include
#include
#include
using namespace std;

struct node{
	int sum;
	vector<int>vec;
	bool operator<(const node&w)const
	{
		if(sum==w.sum)return vec<w.vec;
		return sum>w.sum;
	}
};
map<vector<int>,int>MAP;
vector<node>vec;

int main()
{
	int n,m,x;
	scanf("%d%d",&n,&m);
	for(int i=0;i<n;++i)
	{
		vector<int>t;
		for(int j=0;j<m;++j)
		{
			scanf("%d",&x);
			t.push_back(x);
		}
		MAP[t]++;
	}
	for(auto item:MAP)
		vec.push_back({item.second,item.first});
	sort(vec.begin(),vec.end());
	printf("%d\n",vec.size());
	for(auto item1:vec)
	{
		printf("%d",item1.sum);
		for(int item2:item1.vec)
			printf(" %d",item2);
		puts("");
	}
	return 0;
}

L3-003社交集群

当你在社交网络平台注册时,一般总是被要求填写你的个人兴趣爱好,以便找到具有相同兴趣爱好的潜在的朋友。一个“社交集群”是指部分兴趣爱好相同的人的集合。你需要找出所有的社交集群。

输入格式:
输入在第一行给出一个正整数 N(≤1000),为社交网络平台注册的所有用户的人数。于是这些人从 1 到 N 编号。随后 N 行,每行按以下格式给出一个人的兴趣爱好列表:
K i : h i [ 1 ]   h i [ 2 ] … h i [ K i ] K_i:h_i[1]\,h_i[2]…h_i[K_i] Ki:hi[1]hi[2]hi[Ki]
其中 K i K_i Ki(>0)是兴趣爱好的个数, h i [ j ] h_i[j] hi[j]是第j个兴趣爱好的编号,为区间 [1, 1000] 内的整数。

输出格式:
首先在一行中输出不同的社交集群的个数。随后第二行按非增序输出每个集群中的人数。数字间以一个空格分隔,行末不得有多余空格。

输入样例:

8
3: 2 7 10
1: 4
2: 5 3
1: 4
1: 3
1: 4
4: 6 8 1 5
1: 4

输出样例:

3
4 3 1

想到的解法是维护兴趣的集合,找每个人的第一个兴趣的根结点,让sum[根结点]++,这么做要注意给定的N是人的编号,不代表兴趣的编号是1~N,所以遍历的时候要遍历整个数组,因为这个wa吐了_(:з)∠)_

#include
#include
#include
#include
using namespace std;

const int N=1010;
int fa[N],sum[N];
vector<int>vec;

int find(int x)
{
    if(fa[x]!=x)
    {
        fa[x]=find(fa[x]);
        sum[fa[x]]+=sum[x];
        sum[x]=0;
    }
    return fa[x];
}
int main()
{
    int n,k,x;
    scanf("%d",&n);
    for(int i=1;i<N;++i)
        fa[i]=i;
    for(int i=0;i<n;++i)
    {
        scanf("%d: %d",&k,&x);
        int fx=find(x);
        sum[fx]++;
        for(int j=1;j<k;++j)
        {
            scanf("%d",&x);
            int fy=find(x);
            if(fx!=fy)
                fa[fy]=fx;
        }
    }
    for(int i=1;i<N;++i)
        find(i);
    for(int i=1;i<N;++i)
        if(fa[i]==i&&sum[i]>0)
            vec.push_back(sum[i]);
    sort(vec.begin(),vec.end(),greater<int>());
    int ans=vec.size();
    printf("%d\n",ans);
    for(int i=0;i<ans-1;++i)
        printf("%d ",vec[i]);
    printf("%d\n",vec.back());
    return 0;
}

这里再提供一个维护人的集合的解法

#include
#include
#include
#include
using namespace std;

const int N=1010;
int fa[N],sum[N],pos[N];
vector<int>vec;

int find(int x)
{
    if(fa[x]!=x)
        fa[x]=find(fa[x]);
    return fa[x];
}
void join(int x,int y)
{
    int fx=find(x);
    int fy=find(y);
    fa[fx]=fy;
    sum[fy]+=sum[fx];
}
int main()
{
    int n,k,x;
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        fa[i]=i;
        sum[i]=1;
    }
    for(int i=1;i<=n;++i)
    {
        scanf("%d: ",&k);
        while(k--)
        {
            scanf("%d",&x);
            if(pos[x]==0)//该点没有被访问过,将其指向有该兴趣的人
                pos[x]=i;
            else join(i,pos[x]);
        }
    }
    for(int i=1;i<=n;++i)
        if(fa[i]==i)
            vec.push_back(sum[i]);
    sort(vec.begin(),vec.end(),greater<int>());
    int ans=vec.size();
    printf("%d\n",ans);
    for(int i=0;i<ans-1;++i)
        printf("%d ",vec[i]);
    printf("%d\n",vec.back());
    return 0;
}

L3-028森森旅游

好久没出去旅游啦!森森决定去 Z 省旅游一下。

Z 省有 n 座城市(从 1 到 n 编号)以及 m 条连接两座城市的有向旅行线路(例如自驾、长途汽车、火车、飞机、轮船等),每次经过一条旅行线路时都需要支付该线路的费用(但这个收费标准可能不止一种,例如车票跟机票一般不是一个价格)。

Z 省为了鼓励大家在省内多逛逛,推出了旅游金计划:在 i 号城市可以用 1 元现金兑换 a i a_i ai元旅游金(只要现金足够,可以无限次兑换)。城市间的交通即可以使用现金支付路费,也可以用旅游金支付。具体来说,当通过第 j 条旅行线路时,可以用 c j c_j cj元现金或 d j d_j dj元旅游金支付路费。注意: 每次只能选择一种支付方式,不可同时使用现金和旅游金混合支付。但对于不同的线路,旅客可以自由选择不同的支付方式。

森森决定从 1 号城市出发,到 n 号城市去。他打算在出发前准备一些现金,并在途中的某个城市将剩余现金 全部 换成旅游金后继续旅游,直到到达 n 号城市为止。当然,他也可以选择在 1 号城市就兑换旅游金,或全部使用现金完成旅程。

Z 省政府会根据每个城市参与活动的情况调整汇率(即调整在某个城市 1 元现金能换多少旅游金)。现在你需要帮助森森计算一下,在每次调整之后最少需要携带多少现金才能完成他的旅程。

输入格式:
输入在第一行给出三个整数 n,m 与 q( 1 ≤ n ≤ 1 0 5 1≤n≤10^5 1n105 1 ≤ m ≤ 2 × 1 0 5 1≤m≤2×10^5 1m2×105 1 ≤ q ≤ 1 0 5 1≤q≤10^5 1q105),依次表示城市的数量、旅行线路的数量以及汇率调整的次数。

接下来 m 行,每行给出四个整数 u,v,c 与 d( 1 ≤ u , v ≤ n 1≤u,v≤n 1u,vn 1 ≤ c , d ≤ 1 0 9 1≤c,d≤10^9 1c,d109),表示一条从 u 号城市通向 v 号城市的有向旅行线路。每次通过该线路需要支付 c 元现金或 d 元旅游金。数字间以空格分隔。输入保证从 1 号城市出发,一定可以通过若干条线路到达 n 号城市,但两城市间的旅行线路可能不止一条,对应不同的收费标准;也允许在城市内部游玩(即 u 和 v 相同)。

接下来的一行输入 n 个整数 a 1 , a 2 , ⋯ , a n a_1,a_2,⋯,a_n a1,a2,,an 1 ≤ a i ≤ 1 0 9 1≤a_i≤10^9 1ai109),其中 a i a_i ai表示一开始在 i 号城市能用 1 元现金兑换 a i a_i ai个旅游金。数字间以空格分隔。

接下来 q 行描述汇率的调整。第 i 行输入两个整数 x i x_i xi a i a_i ai 1 ≤ x ​ i ≤ n 1≤x_​i≤n 1xin 1 ≤ a i ≤ 1 0 9 1≤a_i≤10^9 1ai109),表示第 i 次汇率调整后, x i x_i xi号城市能用 1 元现金兑换 a i a_i ai个旅游金,而其它城市旅游金汇率不变。请注意:每次汇率调整都是在上一次汇率调整的基础上进行的。

输出格式:
对每一次汇率调整,在对应的一行中输出调整后森森至少需要准备多少现金,才能按他的计划从 1 号城市旅行到 n 号城市。
再次提醒: 如果森森决定在途中的某个城市兑换旅游金,那么他必须将剩余现金全部、一次性兑换,剩下的旅途将完全使用旅游金支付。

输入样例:

6 11 3
1 2 3 5
1 3 8 4
2 4 4 6
3 1 8 6
1 3 10 8
2 3 2 8
3 4 5 3
3 5 10 7
3 3 2 3
4 6 10 12
5 6 10 6
3 4 5 2 5 100
1 2
2 1
1 17

输出样例:

8
8
1

样例解释:
对于第一次汇率调整,森森可以沿着 1→2→4→6 的线路旅行,并在 2 号城市兑换旅游金;
对于第二次汇率调整,森森可以沿着 1→2→3→4→6 的线路旅行,并在 3 号城市兑换旅游金;
对于第三次汇率调整,森森可以沿着 1→3→5→6 的线路旅行,并在 1 号城市兑换旅游金。


枚举中点,用前半部分所支付现金加上后半部分至少能兑换所支付旅游金的现金
正向建图,找现金从起点到各点的最短路
反向建图,找旅游金从终点到各点的最短路,因为正向建图的中点要找当前点到终点的最短路,所以反向建图

#include
#include
#include
#include
#include
#include
using namespace std;

typedef long long ll;
typedef pair<int,int>pii;
typedef pair<ll,int>plli;
const int N=1e5+10;
const ll INF=0x3f3f3f3f3f3f3f3f;
int ratio[N];
ll dis1[N],dis2[N];
bool vis[N];
vector<pii>g1[N],g2[N];
multiset<ll>S;

void dijkstra(vector<pii>g[],ll dis[],int x)
{
    memset(dis,0x3f,sizeof dis1);
    memset(vis,0,sizeof vis);
    priority_queue<plli,vector<plli>,greater<plli> >heap;
    heap.push({0,x});
    dis[x]=0;
    while(!heap.empty())
    {
        plli t=heap.top();
        heap.pop();
        int now=t.second;
        if(vis[now])continue;
        vis[now]=true;
        for(int j=0;j<g[now].size();++j)
        {
            int next=g[now][j].first;
            int d=g[now][j].second;
            if(dis[next]>dis[now]+d)
            {
                dis[next]=dis[now]+d;
                heap.push({dis[next],next});
            }
        }
    }
}
int main()
{
    int n,m,q,a,b,c,d;
    scanf("%d%d%d",&n,&m,&q);
    while(m--)
    {
        scanf("%d%d%d%d",&a,&b,&c,&d);
        g1[a].push_back({b,c});
        g2[b].push_back({a,d});
    }
    for(int i=1;i<=n;++i)
        scanf("%d",&ratio[i]);
    dijkstra(g1,dis1,1);
    dijkstra(g2,dis2,n);
    for(int i=1;i<=n;++i)
        if(dis1[i]!=INF&&dis2[i]!=INF)
            S.insert(dis1[i]+(dis2[i]+ratio[i]-1)/ratio[i]);
    while(q--)
    {
        scanf("%d%d",&a,&b);
        if(dis1[a]!=INF&&dis2[a]!=INF)
        {
            S.erase(S.find(dis1[a]+(dis2[a]+ratio[a]-1)/ratio[a]));
            ratio[a]=b;
            S.insert(dis1[a]+(dis2[a]+ratio[a]-1)/ratio[a]);
        }
        printf("%lld\n",*S.begin());
    }
    return 0;
}

1010一元多项式求导

注意“零多项式”的指数和系数都是 0,但是表示为 0 0

这句话指的是所有指数和系数都是0的多项式,而当一个多项式不是零多项式时,它其中一项的指数和系数任意一个为0,此项直接输出0

1012数字分类

可能出现某类数字不存在或结果为0的情况,因此要保存这类数字出现的次数

1014福尔摩斯的约会

“对”指上下两个字符串相同位置的两个字符
一个星期有7天,因此字母的取值为A~G
小时字母对的位置要在星期字母对后面

1028人口普查

按年月日去比较,不用计算居民的生日和2014.9.6之间的天数

1030完美数列

#include
#include
#include
using namespace std;

typedef long long ll;
const int N=1e5+10;
ll a[N];

int main()
{
    int n,p,sum=0;
    scanf("%d%d",&n,&p);
    for(int i=0;i<n;++i)
        scanf("%lld",&a[i]);
    sort(a,a+n);
    for(int i=0;i<n;++i)
        for(int j=i+sum;j<n;++j)//下一次更新至少大于sum
        {
            if(a[j]<=a[i]*p)
                sum=j-i+1;
            else break;
        }
    printf("%d\n",sum);
    return 0;
}

1033旧键盘打字

虽然题目说用下划线代表空格,但数据包含空格的情况,应读取一整行
在找这个样例时想过会不会是空格问题,但被自己否决, 细心尝试还是好的

1034有理数四则运算

题目虽说负号只可能出现在分子前,但分子分母交叉的结果可能为负,要根据负负得正,正负得负判断

注意约分忽略负号

#include
#include
using namespace std;

typedef long long ll;

ll gcd(ll a,ll b)
{
    return b?gcd(b,a%b):a;
}
void f(ll x,ll y)
{
    if(y==0)
    {
        printf("Inf");
        return;
    }
    bool flag=x<0&&y>0||x>0&&y<0;
    x=abs(x);
    y=abs(y);
    ll z=gcd(x,y);
    x/=z;
    y/=z;
    if(x==0)printf("0");
    else
    {
        if(flag)printf("(-");
        if(x>=y)
        {
            printf("%lld",x/y);
            x%=y;
            if(x!=0)printf(" %lld/%lld",x,y);
        }
        else printf("%lld/%lld",x,y);
        if(flag)printf(")");   
    }
}
int main()
{
    ll a,b,c,d;
    scanf("%lld/%lld %lld/%lld",&a,&b,&c,&d);
    f(a,b);printf(" + ");f(c,d);printf(" = ");f(a*d+b*c,b*d);
    puts("");
    f(a,b);printf(" - ");f(c,d);printf(" = ");f(a*d-b*c,b*d);
    puts("");
    f(a,b);printf(" * ");f(c,d);printf(" = ");f(a*c,b*d);
    puts("");
    f(a,b);printf(" / ");f(c,d);printf(" = ");f(a*d,b*c);
    puts("");
    return 0;
}

你可能感兴趣的:(数据结构,算法)