睿抗编程国赛 赛后补题

741 阅读19分钟

赛后总结:这次只能说是运气不错,前三道都是我擅长的,中位线 60 分最后结果62分又是只高两分,后两道的图论又骗了两分,但有点可惜的是前几天才看到过一道类似的图论题没有去把它过掉,不然还能涨30分,还是要多补题啊


RC-u1 大家一起查作弊

  • 与字符串相关的题而且题面还这么长看着都头疼,比赛时我先跳过了这道题,回头仔细看题才意识到就是一道纯模拟的题
  • 但交了几次都差那么两个测试点,原来是没有考虑到按行输入字符串时只输入了一个符合题目所说关键词的字符串和该字符串以关键词结尾的情况
  • 在一行循环后再加一次判断这道题就很容易AC了

Code

#include<bits/stdc++.h>
using namespace std;
#define ll long long

int fast_io=[](){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	return 0;

}();

int main(){
    ll len=0,sum=0,ans=0;
    string str;
    string jc="";
    bool dx=0,xx=0,sz=0;
    while(getline(cin,str)){
        for(int i=0;i<str.size();++i){
            if(str[i]>='a'&&str[i]<='z'){
                xx=1;
                jc+=str[i];
            }
            else if(str[i]>='A'&&str[i]<='Z'){
                dx=1;
                jc+=str[i];
            }
            else if(str[i]>='0'&&str[i]<='9'){
                sz=1;
                jc+=str[i];
            }
            else{
                if(dx&&xx&&sz){
                    ans+=5;
                    len+=jc.size();
                    sum++;
                    jc="";
                    dx=0,xx=0,sz=0;
                }
                else if(sz&&(dx||xx)){
                    ans+=3;
                    len+=jc.size();
                    sum++;
                    jc="";
                    dx=0,xx=0,sz=0;
                }
                else if(dx&&xx){
                    ans+=1;
                    len+=jc.size();
                    sum++;
                    jc="";
                    dx=0,xx=0,sz=0;
                }
                else{
                    if(jc.size()>0){
                        sum++;
                        len+=jc.size();
                    }
                    jc="";
                    dx=0,xx=0,sz=0;
                }
            }
        }
        if(jc.size()>0){
        	if(dx&&xx&&sz){
                ans+=5;
                len+=jc.size();
                sum++;
                jc="";
                dx=0,xx=0,sz=0;
            }
            else if(sz&&(dx||xx)){
                ans+=3;
                len+=jc.size();
                sum++;
                jc="";
                dx=0,xx=0,sz=0;
            }
            else if(dx&&xx){
                ans+=1;
                len+=jc.size();
                sum++;
                jc="";
                dx=0,xx=0,sz=0;
            }
            else{
                if(jc.size()>0){
                    sum++;
                    len+=jc.size();
                }
                jc="";
            	dx=0,xx=0,sz=0;
            }
    	}
    }
    cout << ans << endl << len << " " << sum;
    return 0;
}

题面

在今年的睿抗比赛上,有同学的提交代码如下:

public asfiasfgwef12(){
int tsadflas=3;
int masf11233=2;
int[]wasdf1213= new int[10 +1];
int[] vasf124l = new int[10 + I];
int[][] ddasf1234p= new int[masf11233
...

你肯定很奇怪,这看上去代码似乎不像是正常写出来的代码呀?没错,这是这位同学在网络上购买了所谓的“保研综测套餐”,商家为逃避赛后查重,给这位同学发去了经过混淆的代码。然而经过技术支持方的努力,这位同学不仅被封禁,与 TA 购买了相同“套餐”的同学也利用技术手段全部查出,目前主办方已向警方报案,这些同学的“保研”梦很有可能会转变为“案底”梦……因此如果你在比赛前也购买了类似的服务,现在迷途知返还来得及——毕竟这个商家起码还做了一些努力,许多商家号称“一对一”,实际上将一份代码发给了数十位同学……

回到题目,虽然具体检查的手段无法公开,但我们可以尝试简化再简化的手段来找到被这样混淆的程序。 对于给定的大量字符串,你首先要提取出所有的关键词。一个关键词的定义是:由大写字母、小写字母、数字组成的字符串,并且前后均为非大小写字母及数字(包含开头及换行符)。如以下的字符串:

int[] vasf124l = new int[10 + I];

关键词为:intvasf124lnewint10以及I

然后对于所有关键词,你需要计算可疑分数的和以及关键词平均长度。其中一个关键词的可疑分数如下定义:

  • 如果一个关键词同时包含大写字母、小写字母、数字,则分数 + 5 分;
  • 否则,如同时包含(大写字母、数字)或(小写字母、数字)的,分数 + 3 分;
  • 否则,如同时包含(大写字母、小写字母)的,分数 + 1 分;
  • 其余情况不加分。

对于给定的数据,请你输出所有关键词的可疑分数的和、长度的和以及数量。

输入格式:

输入包含若干行字符串,表示待检测的程序。保证每行字符串的长度不超过 1000(除了一行最后的换行符外),输入总长度不超过 6×1046×10^4,并且至少有一个关键词。

输出格式:

对于输入的所有字符串,第一行输出可疑分数的和,第二行输出关键词的总长度以及关键词的个数(以避免计算平均值带来的浮点误差),用一个空格隔开。

输入样例:

static void nnmMNBkf3kfa(){
    int fefvB4=2;
    int [][]fsdk9A=new int[fefvB4][fefvB4];
    fsdk9A[0][0]=1;
    for (int gfdgsUB3 = 0; gfdgsUB3 < fefvB4; gfdgsUB3++) {
        for (int fdnbXZ8 = 0; fdnbXZ8<fefvB4-gfdgsUB3-1; fdnbXZ8++) {
            fsdk9A[gfdgsUB3][fdnbXZ8+1]=fsdk9A[gfdgsUB3][fdnbXZ8]+gfdgsUB3+fdnbXZ8+2;
            fsdk9A[gfdgsUB3+1][fdnbXZ8]=fsdk9A[gfdgsUB3][fdnbXZ8]+gfdgsUB3+fdnbXZ8+1;
            break;
        }
        break;
    }
}

输出样例:

155
276 54

RC-u2 谁进线下了?||

  • 这道和省赛那道大致一样,就把 20 只队伍变为 30 只,还有没有排名的队伍不输出,我想的是用一个结构体保存这些关键数据就解决了,感觉这才应该是第一题

Code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
typedef struct node{
    ll id,ged;
    bool cs=0;
}N;

N drr[32];

bool cmp(N a,N b){
    if(a.ged==b.ged)
        return a.id<b.id;
    return a.ged>b.ged;
}

ll a[21]={0,25,21,18,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0};

inline void dw(){
    for(int i=0;i<20;++i){
        ll c,p;
        cin >> c >> p;
        drr[c].ged+=a[p];
        drr[c].cs=1;
    }
}

int main(){
    ll n;
    cin >> n;
    for(int i=1;i<=30;++i)
        drr[i].id=i;
    while(n--)
        dw();
    sort(drr+1,drr+31,cmp);
    for(int i=1;i<=30;++i)
        if(drr[i].cs)
            cout << drr[i].id << " " << drr[i].ged << endl;
    return 0;
}

题干

Xepa Legends 是一个第一人称射击类大逃杀(“吃鸡”)游戏,每局游戏共有 20 支 3 人小队参加,最后获胜的队伍被称为“捍卫者”。

最近 Xepa Legends 举行了亚太地区南赛区的线上比赛,争夺 7 个前往德国曼海姆参加线下赛的资格,国内共有 14 支队伍参与到了其中。因为比赛十分激烈,直到最后谁进了线下仍有巨大的疑问。小 K 喜欢的国内知名战队 DreamTear 因其队内选手杀马特表现不佳,正好卡在出线分数前后,请你赶紧帮帮小 K,计算一下最后的分数情况,看看他喜欢的战队出线了没有吧!

Xepa Legends 的常规赛共有 30 支队伍参加,被分为三组,进行 N 轮比赛,每轮由三组中的两组组成 20 支队伍的参赛阵容,进行若干场比赛,最后每个队伍会获得一个当轮比赛的排名。

对于每轮比赛,队伍会根据排名获得一个在当轮比赛的赋分

image.png

给定若干轮比赛队伍获得的当轮比赛排名,请你计算出队伍的赋分,并在若干轮比赛后计算出总赋分,从而最终确定 DreamTear 战队能否进入线下,还是只能耍耍花招了。

例如,

  • DreamTear 战队在第一轮比赛中获得了第 17 名,第三轮比赛中获得了第 11 名,第四轮比赛中获得了第 11 名,那么 DreamTear 战队可获 3 + 9 + 9 = 21 分的赋分;
  • KV 战队在第一轮比赛中获得了第 10 名,第三轮比赛中获得了第 2 名,第四轮比赛中获得了第 6 名,那么他们可获得 10 + 21 + 14 = 45 分的赋分。

注:本题与实际情况无关,所有比赛规则、队伍、队员名称均为虚构。

输入格式:

输入第一行是一个正整数 N (≤20),表示有 N 轮比赛。

接下来有 N 部分输入,每部分是一轮比赛的情况。对每一场比赛,信息共分 20 行,第 i 行(i=1,⋯,20)给出的两个非负整数 c 和 p 表示编号为 c 的队伍在这轮比赛里获得了第 p 名。

数据保证所有给定的情况中,排名永远大于等于 1 且小于等于 20,队伍编号由 1 开始,不超过 30。

输出格式:

输出若干行,按分数从大到小依次输出队伍的编号及该队所有轮次游戏结束后的总分。如分数相同,队伍编号较小的先输出。

注意由于统计的时候比赛可能并没有完全结束,所以每个队伍参加的比赛轮数不一定相同,此时仍然正常计分统计即可。不要输出未参赛的队伍分数。

输入样例:

3
1 1
2 2
9 3
6 4
7 5
11 6
3 7
13 8
8 9
16 10
4 11
19 12
17 13
5 14
12 15
15 16
14 17
10 18
20 19
18 20
5 11
10 12
30 13
22 14
1 1
28 20
21 16
26 17
2 2
24 3
4 4
29 5
8 6
7 15
6 7
3 8
9 9
25 10
23 19
27 18
19 20
26 19
27 18
18 17
21 16
12 15
28 14
20 13
17 12
14 11
13 10
23 9
29 8
22 7
30 6
15 5
24 4
25 3
16 2
11 1

输出样例:

1 50
2 42
11 39
24 34
16 31
6 29
9 29
25 28
29 27
3 25
4 25
8 25
13 22
30 21
7 20
15 19
22 19
5 15
17 15
14 12
23 12
10 10
12 10
19 8
20 8
21 8
28 6
26 4
27 4
18 3

RC-u3 势均力敌

  • n=3 时:若三个元素为 1,2,3 那么组合出的 6 个数为 123,132,213,231,312,321 若按题目的需求分为两组则分别是:123,312,213 和 132,231,321 能够发现第一组三个数字更好找出规律,是由 1,2,3 这三个数字不断循环,如下图,代码实现就简单了

image.png

  • n=4 时:就如加上 4 能组合出 24 个数 1234, 1243, 1324, 1342, 1423, 1432, 2134, 2143, 2314, 2341, 2413, 2431, 3124, 3142, 3214, 3241, 3412, 3421, 4123, 4132, 4213, 4231, 4312, 4321 可以发现,如果按第一位数字分组可以分为 4 组每组 6 个数,那么处理一个组的数就可以只处理后 3 位按照 n=3 的方法解决,之后输出各个组分出的数就可以解决

Code

#include<bits/stdc++.h>
using namespace std;
int main(){
    int n;
    cin >> n;
    int a[5];
    for(int i=0;i<n;++i)
        cin >> a[i];
    if(n==3){
        for(int i=0;i<3;++i){
            for(int j=0;j<3;++j)
                cout << a[(i+j)%3];
            cout << endl;
        }
    }
    else{
        for(int i=0;i<4;++i){
            int b[3]={a[(i+1)%4],a[(i+2)%4],a[(i+3)%4]};
            for(int j=0;j<3;++j){
                cout << a[i];
                for(int k=0;k<3;++k)
                    cout << b[(j+k)%3];
                cout << endl;
            }
        }
    }
    return 0;
}

题面

用 n>2 个不同的个位数字组成一个 n 位数,显然有 n! 个不同的结果。可以证明,这 n! 个数字可以被分为势均力敌的两组 —— 即平方和相等、且个数也相等的两组。
本题就请你用程序验证一下这个结论。
因为本题是一道简单题,所以规模很小,只考虑 n≤4 的情况。

输入格式:

输入第一行给出正整数 n(2<n≤4),随后一行给出 n 个不同的、在区间 [1, 9] 内的个位数字,其间以空格分隔。

输出格式:

将所有组成的 n! 个不同的 n 位数分为平方和相等、且个数也相等的两组。但你只需要输出其中一组就可以了。每个数字占一行,共输出 n!/2 行。
注意:解可能不唯一,输出任何一组解就可以。

输入样例:

3
5 2 1

输出样例:

125
512
251

RC-u4 City 不 City

  • 这题就是我前几天看到那道题的类似,没有及时把它过掉,太不City了

  • AI代码分析:这段代码是使用Dijkstra算法在一个加权有向图中寻找从源点s到目标点t的最短路径。以下是对代码的详细解释:

  • typedef pair<LL, LL> PII;: 定义了一个名为PIIpair类型,用于存储距离和顶点编号。

  • void add(int a, int b, int c): 这个函数用于向图中添加边,其中ab是边的两个顶点,c是边的权重。

  • void dijkstra(): 这是Dijkstra算法的核心部分。它首先初始化距离数组dist和标记数组st,将源点s的距离设置为0,并将其放入优先队列q中。然后进入循环,当队列非空时,取出队列中的顶点t,更新t的所有邻居顶点j的距离。在更新距离的同时,也更新邻居顶点到源点的最大价值。

  • void solve(): 在这个函数中,首先读取输入的顶点数n、边数m、源点s和目标点t,然后读取每个顶点的资源价值rd[i]。接着,使用add函数构建图的边。最后调用dijkstra函数来计算从源点s到目标点t的最短路径和最大资源价值。

  • 这段代码的目的是根据给定的图结构和资源分布,找出从源点到目标点的最优路径,同时考虑路径上的资源价值。这种问题通常在资源调配、网络路由或其他需要权衡路径长度和资源利用的场景中出现。

Code

// 之后补上

// 感谢评论区大佬 @[有时长风] 友情提供的Code

#include<bits/stdc++.h>
using namespace std;
#define LL long long

int fast_io=[](){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	return 0;

}();

typedef pair<LL, LL> PII;
const int N = 1e3 + 100, mod = 998244353;
const double expp = 1e-6;
int h[N], e[10 * N], ne[10 * N], w[10 * N], idx;
int dist[N];
bool st[N];
LL n, m, s, t;
vector<LL>rd(N);
void add(int a, int b, int c){ 
	e[idx] = b;
	ne[idx] = h[a];
	w[idx] = c;
	h[a] = idx++;
}
void dijkstra(){
	LL dp[N];
	memset(dp, 0x3f, sizeof dp);
	dp[s] = 0;
	rd[s] = 0;
	memset(dist, 0x3f, sizeof dist);
	dist[s] = 0;
	priority_queue<PII, vector<PII>, greater<PII>>q;
	q.push({ 0,s });
	while(q.size()){
		auto t = q.top();
		q.pop();
		int bh = t.second, distance = t.first;
		if(st[bh])
			continue;
		st[bh] = true;
		for(int i = h[bh]; i != -1; i = ne[i]){
			int j = e[i];
			if(dist[j] > distance + w[i]){
				dp[j] = max(dp[bh], rd[bh]);
				dist[j] = distance + w[i];
				q.push({ dist[j],j });
			}
			else if(dist[j] == distance + w[i]){
				if(dp[j] > max(dp[bh], rd[bh])){
					dp[j] = max(dp[bh], rd[bh]);
				}
			}
		}
	}
	if(dist[t] == 0x3f3f3f3f){
		cout << "Impossible\n";
	}
	else{
		cout << dist[t] << ' ' << dp[t] << '\n';
	}
}
void solve(){
	cin >> n >> m >> s >> t;
	memset(h, -1, sizeof h);
	for(int i = 1; i <= n; i++)
		cin >> rd[i];
	for(int i = 1; i <= m; i++){
		int a, b, c;
		cin >> a >> b >> c;
		add(a, b, c);
		add(b, a, c);
	}
	dijkstra();
}
int main(){
	solve();
	return 0;
}

题面

“City 不 City”因为一位外国友人保保熊直播旅游时总是用奇怪的腔调说“好 city,啊!”而走红中国社交网络,成为网络热梗。事实上,有一些叛逆的年轻人在旅行时会刻意避开网红打卡点,选择一些小众的特色地方小城镇,不追求 city,而喜欢说“好 country,啊”。
下面给定各个城镇的旅游热度和城镇间的旅行花销,请你为前来咨询的旅行者规划一条最经济的路线,并且尽可能避开热度很高的网红点。

输入格式:

输入第一行首先给出 4 个正整数:n 和 m(1<n1031m5n1<n≤10^3,1≤m≤5n),依次为城镇数量(于是城镇编号从 1 到 n)和城镇间的通路条数;s 和 t 依次为旅行者的出发地和目的地的城镇编号。
随后一行给出 n 个不超过 100 的正整数,依次为 n 个城镇的旅游热度。
再后面是 m 行,每行给出一条通路连接的两个城镇的编号、这条通路的最小花销(其数值为不超过 10310^3 的正整数)。通路是双向的,题目保证任一对城镇间至多给出一条通路。
同一行的数字间均以空格分隔。

输出格式:

题目要求从 s 到 t 的最小花销路线;若这样的路线不唯一,则取途径城镇的最高旅游热度值最小的那条路线。
在一行中输出从 s 到 t 的最小花销、以及途经城镇的最高旅游热度值(若没有途经的城镇,则热度值为 0)。数值间以 1 个空格分隔,行首尾不得有多余空格。
若从 s 根本走不到 t,则在一行中输出 Impossible

输入样例 1:

8 14 7 8
100 20 30 10 50 80 100 100
7 1 1
7 2 2
7 3 1
7 4 2
1 2 1
1 5 2
2 5 1
3 4 1
3 5 3
3 6 2
4 6 1
5 6 1
5 8 1
6 8 2

输出样例 1:

4 50

样例解释:

从 7 到 8 的最短路径有 3 条,其中 2 条都经过城镇 1,于是对应的最高旅游热度值是城镇 1 的热度值 100。解路径为 7->2->5->8,途径城镇 2 和 5,对应的最高旅游热度值是城镇 5 的热度值 50。在最短路径长度相等的情况下,取热度值小的解,故输出的热度值为 50。

输入样例 2:

3 1 1 2
10 20 30
1 3 1

输出样例 2:

Impossible

RC-u5 贪心消消乐

  • 好一个开心消消乐,一点都不开心,图论怎么就是学不会啊

Code

#include <bits/stdc++.h>
using i64 = long long;
using namespace std;
int  main() {
	int n;
	cin >> n;
	const int inf = -5e5;
	vector a(n + 1, vector<int>(n + 1));
	for (int i = 1; i <= n; i ++) {
		for (int j = 1; j <= n; j ++) {
			cin >> a[j][i];
			if (!a[j][i])
				a[j][i] = inf;
		}
	}
	auto Min = [](array<int, 5> &x, array<int, 5> &y)->array<int, 5> {
		if (x[0] != y[0])
			return x[0] > y[0] ? x : y;
		if (x[1] != y[1])
			return x[1] < y[1] ? x : y;
		if (x[2] != y[2])
			return x[2] < y[2] ? x : y;
		if (x[3] != y[3])
			return x[3] < y[3] ? x : y;
		return x[4] < y[4] ? x : y;
	};
 
	int ans = 0;
	while (true) {
		array<int, 5> res{inf, 0, 0, 0, 0};
		for (int i = 1; i <= n; i ++) {
			vector<int> pre(n + 1);
			for (int j = i; j <= n; j ++) {
				vector<array<int, 2>> dp(n + 1);
				for (int k = 1; k <= n; k ++)
					pre[k] += a[j][k];
				for (int k = 1; k <= n; k ++) {
					dp[k][1] = k;
					if (k > 1 && dp[k - 1][0] + pre[k] > dp[k][0]) {
						dp[k] = dp[k - 1];
					}
					dp[k][0] += pre[k];
					array<int, 5> t = {dp[k][0], i, dp[k][1], j, k};
					res = Min(res, t);
				}
			}
		}
 
		if (res[0] <= 0) {
			break;
		}
		ans += res[0];
		auto [v, x1, y1, x2, y2] = res;
		cout << '(' << x1 << ", " << y1 << ") (" << x2 << ", " << y2 << ") " << v << '\n';
 
		int len = y2 - y1 + 1, no = -1e5;
		for (int i = x1; i <= x2; i ++) {
			for (int j = y1; j <= y2; j ++) {
				a[i][j] = no;
			}
			for (int j = n; j >= 1; j --) {
				if (a[i][j] != no) {
					int k = j;
					while (k + 1 <= n && a[i][k + 1] == no) {
						swap(a[i][k], a[i][k + 1]);
						k ++;
					}
				}
			}
			for (int j = 1; j <= n; j ++)
				if (!a[i][j])
					a[i][j] = inf;
		}
	}
	cout << ans << '\n';
	return 0;
}

题面

f1.jpg

“消消乐”是以消去方块赢取分数的游戏。这里介绍一种超级简单的玩法:玩家每次按住并拖动鼠标,在屏幕上划出一个矩形,则矩形内的方块就被消去,玩家得到所有被消去的方块的分数之和。
每个方块上的小动物对应不同的得分,例如消去上图中的绿色青蛙得 2 分、消去紫色猫头鹰得 5 分、消去黄色小鸡得 9 分、消去蓝色小牛得 1 分、消去红色狐狸得 3 分、消去棕色小熊得 8 分。有些方块是冰块,消去冰块会被扣分,所以冰块上标注了负分。有些方块是黑洞,玩家的矩形内不能包含任何黑洞,否则所有分数都被黑洞吸走。在一个矩形被消去后,其上方的方块会掉落以填补空缺,而多出的空缺被黑洞填补。
本题请你帮助玩家实现一个基于贪心策略的自动消除程序,每次都争取获得最多的分数,直到无法继续获得更多的分数。

输入格式:

每个输入包含 1 个测试用例。每个测试用例第 1 行给出一个不超过 100 的正整数 N,对应正方形游戏屏幕的高度和宽度。随后 N 行,每行给出 N 个数字,代表对应方块的分数,其中黑洞用 0 表示,小动物用正整数分表示,冰块用负整数分表示。数值均在 [−100,100] 区间内。

输出格式:

每一步的消除策略占一行,格式为:

(x1, y1) (x2, y2) 得分

其中 (x1, y1) 为矩形左上角的横纵坐标,(x2, y2) 为矩形右下角的横纵坐标,得分 为消去这个矩形的得分。这里我们将游戏屏幕的左上角坐标定义为 (1,1),右下角坐标定义为 (N,N)。
最后一行给出总分。
注意:当有多个矩形同时对应最大得分时,优先选择 x1 最小的;如果 x1 一样,则优先选择 y1 最小的;如果 y1 也一样,则优先选择 x2 最小的;如果 x2 还是一样,则优先选择 y2 最小的。

输入样例:

4
0 2 5 0
9 2 -6 2
-4 1 -4 3
-1 8 0 -2

输出样例:

(1, 2) (2, 4) 15
(3, 1) (3, 1) 5
(4, 2) (4, 3) 5
(2, 4) (2, 4) 2
27

样例解释:

游戏的初始界面如题面所示。消去过程如下:
f2.jpg f3.jpg f4.jpg


这几次的比赛感觉自己没有什么提升,明显到了瓶颈期,之后一段时间也许还会学一些算法,但重心会向前端方向倾倒,加油吧自己。这能二等?