日常刷题(寒假 + 大一下)

前言:

郑轻未来ACM主敲的崛起之路,欢迎各位大佬雅正鄙人的题解

写题解是个很好的方法使自己专心,刚学算法可能会有迷茫期,没事,慢慢来,写一道题,写一个题解,重在思路,捋清楚思路,不能浮躁,一步一个脚印才能追赶上大佬的脚步!

目录

L2-024 部落 (25 分)

知识点:朴素并查集

R7-5 相似的单词-zzuli _ACM摸底考试_2022_2_21

R7-6 成绩排序 (15 分)zzuli _ACM摸底考试_2022_2_21

C 山楂_牛客小白月赛45_2022_3_4

D 切糕_牛客小白月赛45_2022_3_4

英语作业(滑动窗口)

7-4 三足鼎立 

7-5 地下迷宫探索 

7-8 叠猫猫 

7-10 德才论 

7-11 奥运排行榜 


L2-024 部落 (25 分)

知识点:朴素并查集

难点:多集合合并

破解:以最小数为祖宗节点

总结:与 y 总 的模板题相比,新增了,多集合合并,参与合并的总人数,合并后集合数目,

但, y总 的模板依然好用!总体难度不大

题目如下

在一个社区里,每个人都有自己的小圈子,还可能同时属于很多不同的朋友圈。我们认为朋友的朋友都算在一个部落里,于是要请你统计一下,在一个给定社区中,到底有多少个互不相交的部落?并且检查任意两个人是否属于同一个部落。

输入格式:

输入在第一行给出一个正整数N(≤104),是已知小圈子的个数。随后N行,每行按下列格式给出一个小圈子里的人:

K P[1] P[2] ⋯ P[K]

其中K是小圈子里的人数,P[i](i=1,⋯,K)是小圈子里每个人的编号。这里所有人的编号从1开始连续编号,最大编号不会超过104。

之后一行给出一个非负整数Q(≤104),是查询次数。随后Q行,每行给出一对被查询的人的编号。

输出格式:

首先在一行中输出这个社区的总人数、以及互不相交的部落的个数。随后对每一次查询,如果他们属于同一个部落,则在一行中输出Y,否则输出N

输入样例:

43 10 1 22 3 44 1 5 7 83 9 6 4210 53 7

输出样例:

10 2YN

代码如下:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 10010;

int n, m;
int p[N];
vector<int> num;//保存已经输入的人
int sum=0; // 结果圈子数目

// 初始化,一圈一人
void init()
{
    for(int i=1; i<N; i++)
        p[i] = i;
}

// 寻找祖宗节点 + 路径压缩
int find(int x)
{
    if(p[x]!=x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    cin >> n ;

    init();

    while(n--)
    {
        int k;
        scanf("%d",&k);
        
        int min_ = N;
        int s[N] = {};
        for( int i=0; i<k; i++ )
        {
            int x;
            scanf("%d",&x);
            num.push_back(p[x]);
            min_ = min(min_,p[x]);
            s[i] = p[x];
        }
        
        // 合并
        for(int i=0; i<k; i++)
            if(s[i] != min_) p[find(s[i])] = find(min_);
    }

     // 去掉重复元素,得到总人数
    sort(num.begin(),num.end());
    num.erase(unique(num.begin(), num.end()), num.end());

    // 寻找祖宗节点(总集合数)
    for(int i=0; i<num.size(); i++ )
        if(p[num[i]]==num[i]) sum++;
    
    cout << num.size() << ' ' << sum << endl;

    cin >> m ;
    while(m -- )
    {
        int a, b;
        cin >> a >> b ;

        if(find(a)==find(b)) puts("Y");
        else puts("N");
    }


    return 0;
}

R7-5 相似的单词-zzuli _ACM摸底考试_2022_2_21

两个单词的组成字母一样,相应的字母数量也一样,则称这两个单词是相似的。比如dog和god是相似的。现在给两个单词,要让它们成为相似单词,怎么办呢?比如dog和good要相似,good删除一个'o'嘛...好简单。 我们可以进行一系列操作,一次操作是删除一个单词的一个字母。现在我想知道两个单词要相似最少要经过几步的操作。

输入格式:

首先输入一个整数T(0<T<=10),表示有T个测试实例。每个测试实例包含两个单词,每个单词的长度小于等于1000。 单词中只包含英文小写字母。

输出格式:

输出最少经过几步的操作可以使两个单词成为相似单词。每个输出占一行。

输入样例:

2
dog
god
dog
good

输出样例:

在这里给出相应的输出。例如:

0
1

代码如下:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>

using namespace std;

const int N = 30;

int n;
int w[N];

int main()
{
    cin >> n ;
    while(n -- )
    {
    	memset(w, 0, sizeof w);
    	int res = 0, i ,j ;
    	string a, b;
    	cin >> a ;
    	cin >> b ;
    	sort(a.begin(),a.end());
    	sort(b.begin(),b.end());
    	
    	for( i=0; i < a.size(); i ++ )	w[a[i] - 'a'] ++ ;

		for( i=0; i < b.size(); i ++ )  w[b[i] - 'a'] -- ;
		
		for( i=0; i < 28; i ++) res += abs(w[i]);
		
		cout << res << endl;
	} 
	
    return 0;
}

R7-6 成绩排序 (15 分)zzuli _ACM摸底考试_2022_2_21

给出班里某门课程的成绩单,请你按成绩从高到低对成绩单排序输出,如果有相同分数则名字字典序小的在前。

输入格式:

第一行为n (3< n < 200),表示班里的学生数目;

接下来的n行,每行为每个学生的名字和他的成绩, 中间用单个空格隔开。名字只包含字母且长度不超过20,成绩为一个不大于100的非负整数。

输出格式:

把成绩单按分数从高到低的顺序进行排序并输出,每行包含名字和分数两项,之间有一个空格。

输入样例:

4
Tom 90
Dongdong 90
Ding 92
Tim 28

输出样例:

Ding 92
Dongdong 90
Tom 90
Tim 28

代码如下:

方法一:

typedef pair<int,string> PIS

vector<PIS> m;

#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <string>

using namespace std;

// sort 排序默认按照first,将成绩提前,便于比较
typedef pair<int,string> PIS;

int main()
{
	vector<PIS> m;
	int n;
	cin >> n ;
	while(n -- )
	{
		string str;
		int a;
		cin >> str;
		cin >> a;
		m.push_back({a,str});
	}
	
	// 排序成绩,默认是升序
	sort(m.begin(),m.end());
	
	// 排序相同成绩的人的姓名字典序
	// 此处是迭代器遍历
	for(auto i=m.end()-1; i>=m.begin(); i-- )
	{
		for(auto j=i-1; j>=m.begin(); j-- )
		{
		    if(i->first == j->first)
		    {
		        string a = i->second, b = j->second;
		        if(a > b) 
		        {
		            swap(i->second ,j->second);
		        }
		    }
		}
	}
	
	for(auto i=m.end()-1; i>=m.begin(); i-- )
	{
	    cout << i->second << " " << i->first << endl;
	}
	
	return 0;
}

方法二:

结构体自定义排序!!!sort ( a,  a + n,  cmp );

#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <string>

using namespace std;

const int N = 1e6+10;

int n;

struct STU{
    int x;
    string y;
}stu[N];

bool cmp(STU a, STU b)
{
    if(a.x != b.x) return a.x > b.x;
    else return a.y < b.y;
}

int main()
{
	cin >> n;
	
	for(int i = 0; i < n; i ++ )
	{
		string str;
		int a;
		cin >> str >> a;
		stu[i] = {a,str};
	}

	sort(stu,stu+n,cmp);

	for(int i = 0; i < n; i ++ )
	    cout << stu[i].y << " " << stu[i].x << endl;

	return 0;
}

C 山楂_牛客小白月赛45_2022_3_4

题目描述

    众所周知,清楚姐姐最近迷上了一个老年游戏“山楂串”(点进去可以玩)这个游戏中我们可以将34个iii级糖果合并,升级成为一个高一级的糖果并且获得x∗ix*ix∗i点积分,xxx为消耗同级糖果的数量,iii为你消耗的糖果等级,当你拥有了一个9级糖果也就代表你有了一串山楂串,这个时候你的9级糖果就会消失。

    请问 如果给定你每级若干个糖果,你最多能得到多少积分。

输入描述:

第一行输入8个非负整数 ai≤109a_i \leq 10^9ai​≤109分别代表iii级糖果的数量。

输出描述:

一个正整数输出你获得的最高积分。

示例1

输入

3 3 2 2 2 2 2 2

输出

110

说明

我们将1级糖果拿3个出来全部合并 获得3点积分并得到一个2级糖果

我们将2级糖果拿4个出来全部合并 获得8点积分并得到一个3级糖果

我们将3级糖果拿3个出来全部合并 获得9点积分并得到一个4级糖果

我们将4级糖果拿3个出来全部合并 获得12点积分并得到一个5级糖果

我们将5级糖果拿3个出来全部合并 获得15点积分并得到一个6级糖果

我们将6级糖果拿3个出来全部合并 获得18点积分并得到一个7级糖果

我们将7级糖果拿3个出来全部合并 获得21点积分并得到一个8级糖果

我们将8级糖果拿3个出来全部合并 获得24点积分并得到一个9级糖果

9级糖果全部消失

最终我们的得分为: 3+8+9+12+15+18+21+24=1103+8+9+12+15+18+21+24=1103+8+9+12+15+18+21+24=110

考点:贪心

代码如下:

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 10;

LL res;
LL a[N];

int main()
{
    for(int i = 1; i <= 8; i ++ ) scanf("%lld",&a[i]);
    
    for(int i = 1; i <= 8; i ++ )
    {
        if(a[i] < 3) continue;
        else if(a[i] == 5)
        {
            res += 4 * i;
            a[i + 1] += 1;
        }
        else
        {
            LL m = a[i] / 3;
            res += a[i] * i;
            a[i + 1] += m;
        }
    }
    
    printf("%lld\n",res);
    
    return 0;
}

D 切糕_牛客小白月赛45_2022_3_4

题目描述

小沙觉得括号真是一个神奇的东西,以至于他看什么都觉得这东西像一串括号,小沙买来了切糕,他准备将它分成几份拿来吃,但是小沙突然希望把他眼中的括号切糕全部切成合法的括号切糕。

请问小沙有多少种切法,不同的切法仅看 切的位置,如果有一个位置切的不同,那么切法就不一样。

如果你无论如何也切不出来合法切糕,那么 输出-1

合法的括号:指对于一个字符串他的左括号数量和右括号一样多,且对于任意前缀左括号始终不小于右括号数。

字符串的前缀:指字符串的从第一个字符开始,到任意字符停止的一个字符串。

左括号  (     右括号     )

答案对1e9+7取模

输入描述:

输入一行字符串,仅包含'(' ')'两种字符 字符串长度不超过10610^6106

输出描述:

输出一个整数代表有多少种切法

示例1

输入

()()

输出

2

说明

可以不切 这样合法串为 ()()

可以切一次 ()     () 切成两个

示例2

输入

(())

输出

1
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 1e6+10, mod = 1e9+7;

LL l ,r, n, res, i;
char st[N];

// 快速幂
LL qmi(LL a, LL n )
{
    LL res = 1;
    while(n)
    {
        if(n & 1) res = (res * a) % mod;
        a = (a * a) % mod;
        n >>= 1;
    }
    
    return res;
}

int main()
{
    scanf("%s",st);
    
    while(st[i] != '\0')
    {
        if( l < r )
        {
            printf("-1\n");
            return 0;
        }
        if(st[i] == '(') l ++;
        else r ++;
        if(l == r) n ++, l = r = 0;
        i ++;
    }
    
    // n - 1 为可切的位置的数量
    res = qmi(2, n-1);
    
    if(l != r) puts("-1");
    else printf("%lld\n",res);
    
    return 0;
}

英语作业(滑动窗口)

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网

题目描述

在写英语作文的时候,两个相同单词靠的太近肯定不好。现在 ZHR 给了你一段n个单词的英文,问你有多少对相同单词中间间隔的单词数小于等于k。

输入:
第一行两个整数,为n 和 k 。

第二行n个由仅小写字母组成的单词。每个单词长度小于等于10 。

1≤k≤n≤105。

输出:

一行一个正整数,表示有多少对单词中间间隔的单词数小于等于kk

思路如下:

滑动窗口长度为 k + 1,  尾 + 中间 k 个数(不算头部那一个)

样例1

输入:

11 2

i love you you love mi mixue ice cream and tea

输出:

2

样例2

输入:

10 2
a a a a a a a a a a

输出:

24

代码如下:

#include <bits/stdc++.h>

using namespace std;

typedef pair<string,int> PSI;
typedef long long LL;

const int N = 1e6+10;

string a[N];
int ne[N];
map<string, int> mp;

int main()
{
    int n, k;
    LL res = 0;
    cin >> n >> k;

    for(int i = 1; i <= n; i ++ )  cin >> a[i];
    
    // 滑动窗口统计
    for(int i = 1, j = 1; j <= n; j ++ )
    {
        if(j - i  >= k + 2) // 长度为 k + 2, 要将头元素划出
        {
            mp[a[i]] --;
            i ++;
        }
        res += mp[a[j]];
        mp[a[j]] ++;
    }

    cout << res << endl;

    return 0;
}

简化代码如下:

#include <bits/stdc++.h>

using namespace std;

typedef pair<string,int> PSI;
typedef long long LL;

const int N = 1e6+10;

string a[N];
int ne[N];
map<string, int> mp;

int main()
{
    int n, k;
    LL res = 0;
    cin >> n >> k;

    for(int i = 1; i <= n; i ++ )  cin >> a[i];
    
    // 滑动窗口统计
    for(int i = 1; i <= n; i ++ )
    {
        if( i - k - 2 >= 1 ) mp[a[i - k - 2]] --;
        res += mp[a[i]];
        mp[a[i]] ++;
    }

    cout << res << endl;

    return 0;
}

7-4 三足鼎立 

当三个国家中的任何两国实力之和都大于第三国的时候,这三个国家互相结盟就呈“三足鼎立”之势,这种状态是最稳定的。

现已知本国的实力值,又给出 n 个其他国家的实力值。我们需要从这 n 个国家中找 2 个结盟,以成三足鼎立。有多少种选择呢?

输入格式:

输入首先在第一行给出 2 个正整数 n(2≤n≤105)和 P(≤109),分别为其他国家的个数、以及本国的实力值。随后一行给出 n 个正整数,表示n 个其他国家的实力值。每个数值不超过 109,数字间以空格分隔。

输出格式:

在一行中输出本国结盟选择的个数。

输入样例:

7 30
42 16 2 51 92 27 35

输出样例:

9

样例解释:

能联合的另外 2 个国家的 9 种选择分别为:

{16, 27}, {16, 35}, {16, 42}, {27, 35}, {27, 42}, {27, 51}, {35, 42}, {35, 51}, {42, 51}。

思路:直接双重循环骗分,一定会TLE(时间超限),观察题目,发现查找的答案具有单调性,

想到二分!!!,再加上绝对值不等式,| a[i] - m | < x < | a[i] + m | ,x 为目标值

好,思路清晰,二分查找,左边界,右边界。

问题解决,接下来看我操作!!!

代码如下

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;

const int N = 1e6+10;

int n, m;
int a[N];

int main()
{
    cin >> n >> m;

    for(int i = 0; i < n; i ++ )
        scanf("%d",&a[i]);

    sort(a, a+n);

    LL sum = 0;
    for(int i = 0; i < n; i ++ )
    {
        int t = a[i];
        
        // 绝对值不等式 
        // 加上  m,a[i]的和 与 m, a[i]的差 的区间
        // 从 i 的下一个位置开始查找
        int r = lower_bound(a + i + 1, a + n, t + m) - a;
        int l = upper_bound(a + i + 1, a + n, abs(m-t)) - a;
        sum += r - l;
    }

    cout << sum << endl;

    return 0;
}

7-5 地下迷宫探索 

地道战是在抗日战争时期,在华北平原上抗日军民利用地道打击日本侵略者的作战方式。地道网是房连房、街连街、村连村的地下工事,如下图所示。

我们在回顾前辈们艰苦卓绝的战争生活的同时,真心钦佩他们的聪明才智。在现在和平发展的年代,对多数人来说,探索地下通道或许只是一种娱乐或者益智的游戏。本实验案例以探索地下通道迷宫作为内容。

假设有一个地下通道迷宫,它的通道都是直的,而通道所有交叉点(包括通道的端点)上都有一盏灯和一个开关。请问你如何从某个起点开始在迷宫中点亮所有的灯并回到起点?

输入格式:

输入第一行给出三个正整数,分别表示地下迷宫的节点数N(1<N≤1000,表示通道所有交叉点和端点)、边数M(≤3000,表示通道数)和探索起始节点编号S(节点从1到N编号)。随后的M行对应M条边(通道),每行给出一对正整数,分别是该条边直接连通的两个节点的编号。

输出格式:

若可以点亮所有节点的灯,则输出从S开始并以S结束的包含所有节点的序列,序列中相邻的节点一定有边(通道);否则虽然不能点亮所有节点的灯,但还是输出点亮部分灯的节点序列,最后输出0,此时表示迷宫不是连通图。

由于深度优先遍历的节点序列是不唯一的,为了使得输出具有唯一的结果,我们约定以节点小编号优先的次序访问(点灯)。在点亮所有可以点亮的灯后,以原路返回的方式回到起点。

输入样例1:

6 8 1
1 2
2 3
3 4
4 5
5 6
6 4
3 6
1 5

输出样例1:

1 2 3 4 5 6 5 4 3 2 1

输入样例2:

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

输出样例2:

6 4 5 4 6 0

注意:无向图!!!

思路:直接深搜(邻接表(稠密图)储存,用邻接矩阵(稀疏图)遍历太过于麻烦),进去时记录一下,出来时记录一下,点不能重复,边可以重复(但搜过边,点一定不能搜了,也就相当于这条边不能搜了)

代码如下:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;

const int N = 1010, M = 100010;

int n, m, k;
bool v[N][N], st[N];
int path[M];
int cnt = 0;

void dfs(int x)
{
    for(int i = 1; i <= n; i ++ )
    {
        if(v[x][i] == true && !st[i] )
        {
            path[cnt++] = i;
            st[i] = true;
            dfs(i);
            path[cnt++] = x;
        }
    }
}

int main()
{
    cin >> n >> m >> k;

    for(int i = 0; i < m; i ++ )
    {
        int a, b;
        scanf("%d%d",&a,&b);
        v[a][b] = true;
        v[b][a] = true;
    }
    path[cnt++] = k;
    st[k] = true;
    dfs(k);

    printf("%d",path[0]);
    for(int i=1;i<cnt;i++)
        printf(" %d",path[i]);
    int f=0;
    for(int i=1;i<=n;i++)if(!st[i])f=1;
    if(f)printf(" 0");

    return 0;
}

7-6 农田缩减 

农夫约翰的 N 头奶牛分布在其二维农场的不同位置。

约翰想用一个长方形的围栏把所有的奶牛围起来,围栏的边需要平行于 x 轴和 y 轴。

在能够包含所有奶牛的情况下(处于围栏边界的奶牛也算包含在内),约翰希望围栏围起的面积尽可能小。

不幸的是,由于上个季度的牛奶产量很低,约翰的预算十分紧张。

因此,他希望建立一个更小的围栏,甚至为了实现这一目标,他愿意卖掉农场中的一头奶牛。

请帮助约翰计算,卖掉牛群中的一头奶牛以后,他可以用围栏围起来的最小面积(为剩下的奶牛建造尽可能小的围栏)。

对于这个问题,请将奶牛视为点,将围栏视为四个线段的集合。

注意,答案可以是零,例如,所有剩余的奶牛最终都站在同一条垂直或水平线上。

输入格式:

第一行包含整数 N(3≤N≤50000 ,)。

接下来 N 行,每行包含两个整数 x,y(1≤x,y≤40000),表示一头牛所在的位置坐标为 (x,y)。

输出格式:

输出卖掉牛群中的一头奶牛以后,约翰可以用围栏围起来的最小面积。

输入样例:

4
2 4
1 1
5 2
17 25

输出样例:

12

思路:查找边界,枚举

代码如下;

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;

const int N = 1e6+10;;

int n, m;
vector<PII> fac;
vector<int> xi, yi;

int main()
{
    cin >> n;

    for(int i = 0; i < n; i ++ )
    {
        int x, y;
        scanf("%d%d",&x,&y);
        fac.push_back({x,y});
        xi.push_back(x);
        yi.push_back(y);
    }

    sort(xi.begin(), xi.end());
    sort(yi.begin(), yi.end());

    int res = INT_MAX;

    for(int i = 0; i < n; i ++ )
    {
        int dx = fac[i].first, dy = fac[i].second, wid = 40000, hgt = 40000;
        //cout << dx << " " << dy << endl;
        if(dx == xi[0]) wid = xi[n-1] - xi[1];
        else if(dx == xi[n-1]) wid = xi[n-2] - xi[0];
        else wid = xi[n-1] - xi[0];

        if(dy == yi[0]) hgt = yi[n-1] - yi[1];
        else if(dy == yi[n-1]) hgt = yi[n-2] - yi[0];
        else hgt = yi[n-1] - yi[0];

        res = min(res, wid*hgt);
    }

    cout << res << endl;

    return 0;
}

7-8 叠猫猫 

一年一度的叠猫猫活动又来了,这次的叠猫猫邀请别人进组后可以把两个组的人合并。
现在有 n 个人参加这个活动, m 次邀请,每次输出合并后的组员个数。

输入格式:

输入在一行中给出2个正整数 n,m(1<=n,m<=105)。

之后的 m 行,每行输入两个正整数 x,y(1<=x,y<=n) 代表 x 邀请 y 进组

输出格式:

每次邀请后,在一行内输出一个整数代表 y 所在组的总人数。

输入样例:

在这里给出一组输入。例如:

5 4
1 2
3 4
2 3
2 4

输出样例:

在这里给出相应的输出。例如:

2
2
4
4

提示:

20%的数据, 1<=n,m<=10

40%的数据, 1<=n,m<=102

60%的数据,1<=n,m<=104

100%的数据, 1<=n,m<=105

思路:合并人数的并查集,模板题

代码如下:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;

const int N = 1e6+10;

int n, m;
int p[N];
int cnt[N];

// 查找祖宗节点
int find1(int x)
{
    if(p[x] != x) p[x] = find1(p[x]);
    return p[x];
}

int main()
{
    cin >> n >> m;

    for(int i = 1; i <= n; i ++ ) p[i] = i, cnt[i] = 1;

    while(m -- )
    {
        int x, y;
        scanf("%d %d",&x, &y);

        if(find1(x) != find1(y)) cnt[find1(x)] += cnt[find1(y)];
        p[find1(y)] = find1(x);

        cout << cnt[find1(x)] << endl;
    }

    return 0;
}

7-9 打工人上人 (300 分)

总所周知,联通的路由之间经常会出现故障。keven正在峡谷之巅快乐,无空维护路由之间的最短路,那么请你帮他回答所有的询问。

首先给出一个网络,现简化为无向连通图有 n 个路由, m 条边组成,且每条边长均为1

会出现3种如下操作:

操作1:A,B 两个路由间的通路断开。

操作2:A,B 两个路由间的通路连接。

操作3:询问 A,B 间的最短路。

输入格式:

第一行三个整数 n,m,q(1<=n<=100,1<=m<=n∗(n−1)/2,1<q<=10000)

接下来 m 行,每行 A,B 两个整数表示 A,B 间有一条通路

接下来 q 行,每行 op,A,B 三个整数,对应上述操作

保证所有输入合法,不出现重边、自环。

输出格式:

您需要按顺序回答每次操作3,如果联通输出最短路的大小,否则输出-1

输入样例:

在这里给出一组输入。例如:

3 3 8
3 2
1 2
3 1
3 2 3
3 1 3
1 2 3
3 3 2
3 2 1
1 1 3
2 2 3
3 1 3

输出样例:

在这里给出相应的输出。例如:

1
1
2
1
2

思路:太菜了,不会dfs直接求........套一个 dijkstra 吧(有点大材小用了hh)

不多说了,看代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;

const int N = 110, INF = 0x3f3f3f3f;

int n, m, q;
int g[N][N];
int dist[N];
int st[N];

int dijkstra(int x, int y)
{
    memset(st, 0, sizeof st);
    memset(dist, 0x3f, sizeof dist);
    dist[x] = 0;

    for (int i = 0; i < n - 1; i ++ )
    {
        int t = -1;
            for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;

        st[t] = true;

        // 用t更新其他点的距离
        for (int j = 1; j <= n; j ++ )
            dist[j] = min(dist[j], dist[t] + g[t][j]);

    }
}

int main()
{
    cin >> n >> m >> q;

    memset(g, 0x3f, sizeof g);
    while(m -- )
    {
        int x, y;
        scanf("%d%d",&x,&y);
        g[x][y] = g[y][x] = 1;
    }

    while(q -- )
    {
        int n, a, b;
        scanf("%d %d %d",&n, &a, &b);
        if(n == 1)
        {
            g[a][b] = g[b][a] = INF;
        }
        else if(n == 2)
        {
            g[a][b] = g[b][a] = 1;
        }
        else
        {
            dijkstra(a, b);
            if(dist[b] == INF) puts("-1");
            else printf("%d\n",dist[b]);
        }
    }


    return 0;
}

7-10 德才论 

宋代史学家司马光在《资治通鉴》中有一段著名的“德才论”:“是故才德全尽谓之圣人,才德兼亡谓之愚人,德胜才谓之君子,才胜德谓之小人。凡取人之术,苟不得圣人,君子而与之,与其得小人,不若得愚人。”

现给出一批考生的德才分数,请根据司马光的理论给出录取排名。

输入格式:

输入第一行给出 3 个正整数,分别为:N(≤105),即考生总数;L(≥60),为录取最低分数线,即德分和才分均不低于 L 的考生才有资格被考虑录取;H(<100),为优先录取线——德分和才分均不低于此线的被定义为“才德全尽”,此类考生按德才总分从高到低排序;才分不到但德分到优先录取线的一类考生属于“德胜才”,也按总分排序,但排在第一类考生之后;德才分均低于 H,但是德分不低于才分的考生属于“才德兼亡”但尚有“德胜才”者,按总分排序,但排在第二类考生之后;其他达到最低线 L 的考生也按总分排序,但排在第三类考生之后。

随后 N 行,每行给出一位考生的信息,包括:准考证号、德分、才分,其中准考证号为 8 位整数,德才分为区间 [0, 100] 内的整数。数字间以空格分隔。

输出格式:

输出第一行首先给出达到最低分数线的考生人数 M,随后 M 行,每行按照输入格式输出一位考生的信息,考生按输入中说明的规则从高到低排序。当某类考生中有多人总分相同时,按其德分降序排列;若德分也并列,则按准考证号的升序输出。

输入样例:

14 60 80
10000001 64 90
10000002 90 60
10000011 85 80
10000003 85 80
10000004 80 85
10000005 82 77
10000006 83 76
10000007 90 78
10000008 75 79
10000009 59 90
10000010 88 45
10000012 80 100
10000013 90 99
10000014 66 60

输出样例:

12
10000013 90 99
10000012 80 100
10000003 85 80
10000011 85 80
10000004 80 85
10000007 90 78
10000006 83 76
10000005 82 77
10000002 90 60
10000014 66 60
10000008 75 79
10000001 64 90

思路:此题为分类排序,结构体 + sort + cmp 排序,注意结构体中应声明一个类型变量clas(分类此数属于哪个组别,再按照常规排序即可。

注意:cmp 里一般 比较 == ,返回 < 或 > 

代码如下:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;

const int N = 1e6+10;;

int k, n, m;
struct STU
{
    string x;
    int a;
    int b;
    int sum;
    int clas;
}stu[N];

bool cmp(STU n1, STU n2)
{
    if( n1.clas != n2.clas ) return n1.clas < n2.clas;
    else if( n1.sum != n2.sum ) return n1.sum > n2.sum;
    else if( n1.a != n2.a) return n1.a > n2.a;
    else return n1.x < n2.x;
}

int main()
{
    cin >> k >> n >> m;

    int cnt = 0;
    for(int i = 0; i < k; i ++ )
    {
        string str;
        cin >> str;
        int x, y, clas;
        scanf("%d%d",&x,&y);

        if(x >= m && y >= m) clas = 1;
        else if(x >= m && y < m) clas = 2;
        else if(x < m && y < m && x >= y) clas = 3;
        else clas = 4;

        if(x >= n && y >= n)
            stu[cnt++] = {str,x,y,x+y,clas};
    }

    stable_sort(stu, stu+cnt, cmp);

    cout << cnt << endl;
    for(int i = 0; i < cnt; i ++ )
    {
        cout << stu[i].x;
        printf(" %d %d\n",stu[i].a, stu[i].b);
    }

    return 0;
}

7-11 奥运排行榜 

每年奥运会各大媒体都会公布一个排行榜,但是细心的读者发现,不同国家的排行榜略有不同。比如中国金牌总数列第一的时候,中国媒体就公布“金牌榜”;而美国的奖牌总数第一,于是美国媒体就公布“奖牌榜”。如果人口少的国家公布一个“国民人均奖牌榜”,说不定非洲的国家会成为榜魁…… 现在就请你写一个程序,对每个前来咨询的国家按照对其最有利的方式计算它的排名。

输入格式:

输入的第一行给出两个正整数N和M(≤224,因为世界上共有224个国家和地区),分别是参与排名的国家和地区的总个数、以及前来咨询的国家的个数。为简单起见,我们把国家从0 ~ N−1编号。之后有N行输入,第i行给出编号为i−1的国家的金牌数、奖牌数、国民人口数(单位为百万),数字均为[0,1000]区间内的整数,用空格分隔。最后面一行给出M个前来咨询的国家的编号,用空格分隔。

输出格式:

在一行里顺序输出前来咨询的国家的排名:计算方式编号。其排名按照对该国家最有利的方式计算;计算方式编号为:金牌榜=1,奖牌榜=2,国民人均金牌榜=3,国民人均奖牌榜=4。输出间以空格分隔,输出结尾不能有多余空格。

若某国在不同排名方式下有相同名次,则输出编号最小的计算方式。

输入样例:

4 4
51 100 1000
36 110 300
6 14 32
5 18 40
0 1 2 3

输出样例:

1:1 1:2 1:3 1:4

思路:结构体存下来,挨个比较,没什么技术含量

代码如下:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;

const int N = 1e6+10;;

int n, m;

struct STU
{
    int gold;
    int jp;
    int popu;
}stu[N];

int goldpaiming(int x)
{
    int pm = 1;
    int p = stu[x].gold;
    for(int i = 0; i < n; i ++ )
        if(stu[i].gold > p) pm++;
    return pm;
}

int jpaiming(int x)
{
    int pm = 1;
    int p = stu[x].jp;
    for(int i = 0; i < n; i ++ )
        if(stu[i].jp > p) pm++;
    return pm;
}

int popugold(int x)
{
    int pm = 1;
    double p = stu[x].gold * 1.0/stu[x].popu;
    for(int i = 0; i < n; i ++ )
        if(stu[i].gold*1.0/stu[i].popu > p) pm++;
    return pm;
}

int popuj(int x)
{
    int pm = 1;
    double p = stu[x].jp*1.0/stu[x].popu;
    for(int i = 0; i < n; i ++ )
        if(stu[i].jp*1.0/stu[i].popu > p) pm++;
    return pm;
}

void check(int a, int b, int c, int d)
{
    int f, f1, f2;

    if(a > b) a = b, f1 = 2;
    else f1 = 1;

    if(c > d) c = d, f2 = 4;
    else f2 = 3;

    if(a > c) a = c, f = f2;
    else f = f1;

    printf("%d:%d",a,f);
}

int main()
{
    cin >> n >> m;

    for(int i = 0; i < n; i ++ )
        scanf("%d %d %d",&stu[i].gold, &stu[i].jp, &stu[i].popu);

    for(int i = 0; i < m; i ++ )
    {
        int x;
        scanf("%d",&x);
        if(i) printf(" ");
        int a = goldpaiming(x);
        int b = jpaiming(x);
        int c = popugold(x);
        int d = popuj(x);
        check(a, b, c, d);
    }

    return 0;
}

阅览室 (20 分)

天梯图书阅览室请你编写一个简单的图书借阅统计程序。当读者借书时,管理员输入书号并按下S键,程序开始计时;当读者还书时,管理员输入书号并按下E键,程序结束计时。书号为不超过1000的正整数。当管理员将0作为书号输入时,表示一天工作结束,你的程序应输出当天的读者借书次数和平均阅读时间。

注意:由于线路偶尔会有故障,可能出现不完整的纪录,即只有S没有E,或者只有E没有S的纪录,系统应能自动忽略这种无效纪录。另外,题目保证书号是书的唯一标识,同一本书在任何时间区间内只可能被一位读者借阅。

输入格式:

输入在第一行给出一个正整数N(≤10),随后给出N天的纪录。每天的纪录由若干次借阅操作组成,每次操作占一行,格式为:

书号([1, 1000]内的整数) 键值SE) 发生时间hh:mm,其中hh是[0,23]内的整数,mm是[0, 59]内整数)

每一天的纪录保证按时间递增的顺序给出。

输出格式:

对每天的纪录,在一行中输出当天的读者借书次数和平均阅读时间(以分钟为单位的精确到个位的整数时间)。zheng

输入样例:

3
1 S 08:10
2 S 08:35
1 E 10:00
2 E 13:16
0 S 17:00
0 S 17:00
3 E 08:10
1 S 08:20
2 S 09:00
1 E 09:20
0 E 17:00

输出样例:

2 196
0 0
1 60

思路:输入 S 标记一下,输入 ( E && 标记存在)  计算时长,同时 刷新标记,需要注意,第一,可能借一个书多次,需要刷新标记;第二,输出为整数,可以用 printf( " . 0f %",(double) x ) ; 来控制五舍六入;

代码如下:

#include <bits/stdc++.h>

using namespace std;

const int N = 510, M = 10010;

int n, m, k;

int main()
{
    cin >> m;

    while(m -- )
    {
        int n, h, m;
        char ch;
        int f[1010] = {0}, time[1010] = {0}, sum = 0, cnt = 0;
        while(scanf("%d %c %d:%d",&n, &ch, &h,&m))
        {
            if(n == 0) break;
            if(ch == 'S')
            {
                f[n] = 1;
                time[n] = h*60+m;
            }
            if(ch == 'E')
            {
                if(f[n])
                {
                    f[n] = 0;
                    sum += h*60+m - time[n];
                    cnt ++;
                }
            }
        }
        if(!cnt) puts("0 0");
        else printf("%d %.0f\n",cnt, (double)sum/cnt);
    }

    return 0;
}

杨辉三角形(蓝桥加强版)

 

 思路:杨辉三角为全排列数,左右对称,查找第一次出现,只查左半边即可,中间的数为

全排列数 C(k,2k),从下方开始查找,16层就够了,17层是两亿,超过题目范围,二分每一行的数,查找目标数,注意输出时,从 k 从 0 行,0 列 开始 res = L *( L+1) / 2 + k + 1;   

代码如下:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef unsigned long long ULL;

const int N = 3010, M = 1e6+10;

int  n;

// 暴力求组合数
LL C(int a, int b)
{
    LL res = 1;
    for(int i = a, j = 1; j <= b; i --, j ++ )
    {
        // 不能写成 res *= i/j, i 不是 j 倍数是,取整会有误差
        res = res * i / j; 
        if(res > n) return res;
    }
    return res;
}

// 二分查找目标
bool check(int k)
{
    LL l = k*2, r = n;
    while(l < r)
    {
        LL mid = (l+r)>>1;
        if(C(mid, k) >= n) r = mid;
        else l = mid + 1;
    }

    if(C(l, k) != n) return false;

    printf("%lld\n",l*(l+1)/2+k+1);

    return true;
}

int main()
{
    cin >> n;
    for(int k = 16; ; k -- )
        if(check(k))
            break;

    return 0;
}

全球变暖(dfs)

题目描述

你有一张某海域 NxN 像素的照片,"."表示海洋、"#"表示陆地,如下所示:

.......

.##....

.##....

....##.

..####.

...###.

.......

其中"上下左右"四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有 2 座岛屿。

由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。

例如上图中的海域未来会变成如下样子:

.......

.......

.......

.......

....#..

.......

.......

请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。

输入描述

第一行包含一个整数 N (1≤N≤1000)。

以下 N 行 N 列代表一张海域照片。

照片保证第 1 列、第 N 行、第 N 列的像素都是海洋。、

输出一个整数表示答案。

输入输出样例

输入:

7
.......
.##....
.##....
....##.
..####.
...###.
.......

输出: 

1

 思路:

        找到一个岛屿,上下左右判断该岛是否被淹没,若没有被淹没,则该群岛不会被淹没,标记一下;若被淹没,则向四周拓展,继续判断是否被淹没,dfs 出来,若没标记,被淹,res ++,否则res不变,

代码如下:

#include <bits/stdc++.h>

using namespace std;

typedef pair<double, double> PBB;
typedef pair<int,int> PII;
typedef long long LL;

const int N = 1010., INF = 0x3f3f3f3f;

int n;
char g[N][N];
bool st[N][N];
int f;

int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};

void dfs(int x, int y)
{
    st[x][y] = true;

    if(g[x+1][y] == '#' && g[x-1][y] == '#' && g[x][y+1] == '#' && g[x][y-1] == '#')
        f = 1;

    for(int i = 0; i < 4; i ++ )
    {
        int x1 = x + dx[i], y1 = y + dy[i];
        if(g[x1][y1] == '#' && !st[x1][y1])
        {
            dfs(x1, y1);
        }
    }
}

int main()
{
    cin >> n;

    for(int i = 0; i < n; i ++ )
            scanf("%s",g[i]);

    int res = 0;
    for(int i = 0; i < n; i ++ )
    {
        for(int j = 0; j < n; j ++ )
        {
            if(!st[i][j] && g[i][j] == '#')
            {
                f = 0;
                dfs(i, j);
                if(!f) res ++;
            }
        }
    }

    cout << res << endl;

    return 0;
}

小朋友的崇拜圈(dfs)

班里 N 个小朋友,每个人都有自己最崇拜的一个小朋友(也可以是自己)。

在一个游戏中,需要小朋友坐一个圈,每个小朋友都有自己最崇拜的小朋友在他的右手边。

求满足条件的圈最大多少人?

小朋友编号为1,2,3,⋯N。

输入描述

输入第一行,一个整数 N(3<N<10^5)。

接下来一行 N 个整数,由空格分开。

输出描述

要求输出一个整数,表示满足条件的最大圈的人数。

输入:

9
3 4 2 5 3 8 4 6 9

输出:

4

如下图所示,崇拜关系用箭头表示,红色表示不在圈中。

显然,最大圈是[2 4 5 3] 构成的圈。

思路:直接dfs,从一个点开始找,若返回这个点,更新一下max,但是,因为n是10^5,直接太暴力的dfs,一定会TLE;可以优化一下,优化第一点为,记录入度,入度为零的点,一定不会在最大圈中,dfs时跳过入度为0的点,第二点为,已经循环过的点,再次dfs相当于重复,可以记录以下已经遍历到的点,下一次不遍历这个点。好,思路到此为止,兄弟们,看代码!!!

代码如下:

#include <bits/stdc++.h>

using namespace std;

const int N = 2e6+10, INF = 0x3f3f3f3f;

int n;
int path[N], in[N];
bool st[N];
int ans;

void dfs(int u, int v, int cnt)
{
    if(path[u] == v)
    {
        ans = max(ans, cnt);
        return;
    }

    st[path[u]] = true;
    dfs(path[u], v, cnt+1);
}

int main()
{
    cin >> n;

    for(int i = 1; i <= n; i ++ )
        scanf("%d",&path[i]), in[path[i]] ++;

    for(int i = 1; i <= n; i ++ )
    {
        if( !st[i] && in[i] )
            dfs(i, i, 1);
    }

    cout << ans << endl;

    return 0;
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AC自动寄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值