Bootstrap

2024中国大学生算法设计超级联赛(2)

🚀欢迎来到本文🚀
🍉个人简介:陈童学哦,彩笔ACMer一枚。
🏀所属专栏:杭电多校集训
本文用于记录回顾总结解题思路便于加深理解。

A - 鸡爪

Problem Description

在这里插入图片描述
一个鸡爪是由 4 个部分组成,一个点与三个与该点相邻的边,三个边的另一端点被认为不在鸡爪中。

一个图上的鸡爪数是该图最多成形成几个鸡爪,使得图上每个点与边最多一个鸡爪中(注意上文点与边是否在鸡爪中的定义)。

现在给你 n 条边,你可以使用任意个点,构造一个简单无向图(没有自环重边),要求最大化该图的鸡爪数,并输出n条边的两端点。如果有多解,请让输出的 2n 个数字在行优先遍历的顺序下,字典序最小

字典序:序列A的字典序小于序列B,当且仅当存在i (1≤i≤n),使得A[i]<B[i],且对任意的j (1≤j<i),A[j]=B[j]。

在这里插入图片描述
Input

第一行为一个整数 T ( 1 ≤ T ≤ 1 0 4 ) ,表示测试样例个数。 第一行为一个整数 T(1≤T≤10^4) ,表示测试样例个数。 第一行为一个整数T(1T104),表示测试样例个数。

每个样例一行,为一个整数 n ( 1 ≤ n ≤ 1 0 4 ) 。保证所有样例的 n 的 ≤ 1 0 4 。 每个样例一行,为一个整数 n(1≤n≤10^4)。保证所有样例的n的≤10^4。 每个样例一行,为一个整数n(1n104)。保证所有样例的n104

在这里插入图片描述
Output

每个样例输出 n 行,每行两个正整数,表示该无向边连接的两个顶点(顶点从 1 开始编号) 每个样例输出 n 行,每行两个正整数,表示该无向边连接的两个顶点(顶点从 1 开始编号) 每个样例输出n行,每行两个正整数,表示该无向边连接的两个顶点(顶点从1开始编号)

解题思路

构造题(不会构造 ^ __ ^)。要使得构造的简单无向图的鸡爪数尽可能的多,而一个鸡爪由一个点和三条相邻边且只能使用一次。我们可以考虑倒着构造每个点,且除了第1个点外每个点需要与没有用过且与自身不相等的另外3个点连边。第一个点的与3 + n % 3 个点相连。

AC代码

#include<bits/stdc++.h>
#define look(x) cout << #x << " ==  " << x << "\n"
using namespace std;
using i64 = long long;
const int N = 2e5 + 10;
const int MOD1 = 1e9 + 7;
const int MOD2 = 998244353;

void solve(){
	int n;
	cin >> n;
	//需要跑几轮
	int m = n / 3;
	//每个点都连3个点
	vector<int> a(m + 1,3);
	//特判,不足3条边的情况
	if(n <= 2){
		m = 1;
		a.resize(2,0);
	}
	//第一个点还需要连的n % 3个点
	a[1] += n % 3;
	//存答案
	set<pair<int,int>> ans;
	//倒着从大到小开始
	for(int i = m;i >= 1;i --){
		//遍历出与i相连的3个点
		for(int j = 1;a[i] > 0;j ++){
			//不与自身相等且没有出现过
			if(i != j && !ans.count(minmax(i,j))){
				ans.insert(minmax(i,j));
				//点数减1
				a[i] --;
			}
		}
	}
	for(auto [x,y] : ans){
		cout << x << " " << y << "\n";
	}
}

int main(){	

	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	
	int t = 1;
	cin >> t;

	while(t --){
		solve();
	}
	
	return 0;
}

F - 传奇勇士小凯

Problem Description

传说在遥远的魔法大陆有着一个村庄叫做卡卡奇里奇,这里有n座房屋,n−1条道路,保证了任意两座房屋之间都可以通过道路相互可达。这里环境优美,居民幸福地生活着。

但是有一天晚上,原本平静的村庄突然受到一不明寄生生命体的袭击,奇怪的怪物绑架了所有居民,并控制了所有房屋。凌晨时分,在外游历的勇士小凯收到了卡卡奇里奇国王的召唤来到了卡卡奇里奇,奉命消灭所有怪物,解救整个村庄。

由于有人工智能Fairy的存在,卡卡奇里奇国王能够知道小凯和每个房屋的怪物的较量中的获胜概率是多少。在一场小凯和第i个房屋的怪物较量中,小凯有pi15的概率获得胜利,成功消灭第i个房屋的所有怪物,同时小凯有1−pi15的概率失败,那么第i个房屋的怪物会依旧存在,只能之后再挑战。由于没有被消灭怪物会在每个晚上恢复元气,所以每一天小凯对第i个房屋的怪物的战斗胜率是固定的。

经过了长途跋涉之后,小凯来到了卡卡奇里奇的1号房屋开始了战斗。每一天白天,小凯都会对当前他所在的房屋的怪物发起挑战,如果成功那么他会询问卡卡奇里奇国王然后在国王的建议之下选择一个与当前房屋相邻的(有直接的道路相连的)还存在怪物的房屋前进(但是第二天才能对该房屋的怪物进行挑战),如果不存在这样的房屋,那么喜欢摸鱼的小凯便会离开这个村庄去摸鱼。如果挑战失败,那么小凯会在这个房屋门口休息一个晚上,等到下一天继续发起挑战。

国王希望摸鱼的小凯在村庄呆的久一点,所以他想问你在他的控制之下,小凯最多期望在村庄里停留多少天?请你以最简分数的形式告诉他这个答案。

在这里插入图片描述

Input

第一行一个整数 T ( 1 ≤ T ≤ 20 ) 表示数据组数。 第一行一个整数T (1≤T≤20)表示数据组数。 第一行一个整数T(1T20)表示数据组数。

对于每组数据,第一行有一个整数 n ( 1 ≤ n ≤ 1 0 5 ) ,表示房屋的数量。 对于每组数据,第一行有一个整数n (1≤n≤10^5),表示房屋的数量。 对于每组数据,第一行有一个整数n(1n105),表示房屋的数量。

接下来有n−1行,每行两个整数u,v,表示村庄的一条道路,保证没有重边,没有自环。(1≤u,v≤n,u≠v)​

接下来有 n 个整数,第 i 个整数 p i ( 1 ≤ p i ≤ 15 ) 表示小凯打倒第 i 个村庄的怪物的概率为 p i 15 。 接下来有n个整数,第i个整数p_i (1≤p_i≤15)表示小凯打倒第i个村庄的怪物的概率为p_i15。 接下来有n个整数,第i个整数pi(1pi15)表示小凯打倒第i个村庄的怪物的概率为pi15

数据保证 ∑ n ≤ 5 ⋅ 1 0 5 数据保证∑n≤5⋅10^5 数据保证n5105

在这里插入图片描述

Output

对于每组数据,输出一个最简分数( A / B , A 与 B 互质)表示小凯最多期望在村庄里停留多少天。数据保证答案不会为 0 。 对于每组数据,输出一个最简分数(A/B,A与B互质)表示小凯最多期望在村庄里停留多少天。数据保证答案不会为0。 对于每组数据,输出一个最简分数(A/BAB互质)表示小凯最多期望在村庄里停留多少天。数据保证答案不会为0

在这里插入图片描述

解题思路

要我们求期望,首先如果做一件事成功的概率为 P P P,那么这件事成功的期望就是 1 / P 1/P 1/P,而这里小凯有 p i / 15 p_i/15 pi/15的概率获得胜利,那么期望就是 15 / p i 15/p_i 15/pi/。
然后我们可以先求每个点的期望,然后dfs从1开始遍历图求最大的期望天数,但是这里的期望期望是个分数,为了方便计算我们可以考虑通分,因为 p i p_i pi为1到15,所以通分的分母就应该是1到15的最小公倍数。
然后我们可以在分母相同的情况下求出每个点的分子,最后dfs累加答案求最大期望值。
最后再求个分子和分母的gcd然后输出最简分数形式。

AC代码

#include<bits/stdc++.h>
#define int long long
#define look(x) cout << #x << " ==  " << x << "\n"
using namespace std;
using i64 = long long;
const int N = 2e5 + 10;
const int MOD1 = 1e9 + 7;
const int MOD2 = 998244353;
vector<int> g[N];
bool st[N];
int f[N];
int k = 1;
int ans = 0;
int gcd(int x,int y){
	return y ? gcd(y,x % y) : x;
}
int lcm(int x,int y){
	return x * y / gcd(x,y);
}
void dfs(int i,int x){
	ans = max(ans,x);
	for(auto y : g[i]){
		if(st[y]){
			continue;
		}
		st[i] = true;
		dfs(y,x + f[y]);
		st[i] = false;
	}

}
void solve(){
	int n;
	cin >> n;
	for(int i = 1;i <= n;i ++){
		g[i].clear();
		st[i] = false;
	}
	for(int i = 1;i <= n - 1;i ++){
		int u,v;
		cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	vector<int> p(n + 1);
	//求在相同分母下每个点的分子
	for(int i = 1;i <= n;i ++){
		cin >> p[i];
		f[i] = 15 * k / p[i];
	}
	//dfs跑图
	dfs(1,f[1]);
	//求最大分子之和与分母的gcd
	int res = gcd(ans,k);
	cout << ans / res << "/" << k / res << "\n";
	ans = 0;
}

signed main(){	

	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	for(int i = 1;i <= 15;i ++){
		k = lcm(k,i);
	}
//	look(k);
	int t = 1;
	cin >> t;

	while(t --){
		solve();
	}
	
	return 0;
}

G - URL划分

Problem Description

给定一个 URL 字符串 S,形如 s3://hdu-oj-bucket/problem=1/type=data/

字符串的格式规定具体如下:

  1. 首先是指定使用的传输协议,例如 http, ftp, s3,保证传输协议只由小写英文字母和数字构成。之后紧跟 ://

  2. 接下来会紧跟一个字符串为其网络位置,以 / 为终止,在给定的例子中为 hdu-oj-bucket

  3. 在网络位置之后的路径字符串,每个子结构都会紧跟一个 /,在给定的例子中传输协议后面可以看成是 problem=1/, type=data/ 两个子结构组成的。假如说出现了 / 可以认为一定发生了子结构的切分。保证每个子结构中只会用到小写英文字母,数字,-=/

  4. 在路径字符串的子结构中,如果存在形如 A=B/ 的子结构,那就意味着在路径中声明了一些环境变量的值。在给定的例子中我们指定了 problem=1, type=data。为了方便解析我们保证 A,B 只由小写英文字母和数字构成,同时保证 A 是一个合法的变量命名。

对于一个确定的 URL 字符串 S,小 T 希望你帮忙解析这个字符串,输出这个 URL 所使用的传输协议网络位置,以及其在路径字符串子结构中定义的环境变量

在这里插入图片描述
Input

第一行输入一个正整数 T (1≤T≤100),表示总共有 T 组数据。

对于每一组测试数据,读入一个 URL 字符串 S (1<|S|≤200)。

具体格式满足题面中要求,保证该字符串一定合法。

在这里插入图片描述
Output

对于每一组测试数据,首先输出一行一个字符串表示,所使用的传输协议

接着输出一行一个字符串表示网络位置

接下来对于每一个声明的环境变量,输出 A=B,其中 A 为环境变量名,B 为该环境变量的值。

在这里插入图片描述

解题思路

模拟题,这题就是让我们把字符串解析后输出传输协议,网络位置,以及其在路径字符串子结构中定义的环境变量即可。需要注意的是环境变量里必须有=才输出,否则不不需要输出(我就被这个地方单杀了,鉴定为题目理解不到位。)

AC代码

#include<bits/stdc++.h>
#define look(x) cout << #x << " ==  " << x << "\n"
using namespace std;
using i64 = long long;
const int N = 2e5 + 10;
const int MOD1 = 1e9 + 7;
const int MOD2 = 998244353;

void solve(){
	string s;
	cin >> s;
	string res = "";
	vector<string> ans;
	for(int i = 0;i < s.size();i ++){
		if(s[i] == '/' || s[i] == ':'){
			ans.push_back(res);
			res = "";
		}else{
			res += s[i];
		}
	}
	int k = 0;
	for(auto x : ans){
		if(x != "" && k >= 2 && x.find('=') != -1){
			cout << x << "\n";
		}else if(x != "" && k < 2){
			cout << x << "\n";
			k ++;
		}
		
	}
}

int main(){	

	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	
	int t = 1;
	cin >> t;

	while(t --){
		solve();
	}
	
	return 0;
}

J - 女神的睿智

Problem Description

Alice在玩一款名叫《闪耀!优俊少女》的游戏,在这个游戏中,有一个重要的道具叫做女神的睿智。

为了获得女神的睿智,需要按顺序将8个知识碎片两两合成为结晶,再将4个结晶合成成为大结晶,最后将2个大结晶合成得到女神的睿智。

女神的睿智有着三种颜色,知识碎片和结晶也有着三种颜色。

在合成的碎片颜色相同时,结晶的颜色与碎片相同,在颜色不同时,结晶的颜色与左侧碎片相同。

在合成的结晶颜色相同时,大结晶的颜色与结晶相同,在颜色不同时,大结晶的颜色与左侧结晶相同。

在合成的大结晶颜色相同时,女神的睿智的颜色与大结晶相同,在颜色不同时,我们比较两种颜色的碎片在女神的睿智中出现的数量,选择出现数量多的碎片的颜色作为女神的睿智的颜色,碎片数量相同时,随机选择一个大结晶颜色作为女神的睿智的颜色。

Alice想要知道最后能获得的女神的睿智的颜色。

在这里插入图片描述

Input

第一行包含一个整数T(1≤T≤10000),表示数据组数。

接下来的T行,每行8个字符(“R”,“G”,“B”),表示每组数据的碎片颜色。

在这里插入图片描述
Output

一共T行,每行一个字符,表示最后获得的女神的睿智的颜色,如果不能确定,输出"N"。

在这里插入图片描述

解题思路

模拟。因为每行字符串只有8个字符,所以我们可以直接从左往右按顺序合并就OK了,但其实稍微观察我们可以发现,最后合成的两个大结晶的颜色其实就是第1个和第5个字符串,然后我们判断最后合并后剩下的两个字符是否相等,相等就直接输出,否则输出次数出现多的那个。

AC代码

#include<bits/stdc++.h>
#define look(x) cout << #x << " ==  " << x << "\n"
using namespace std;
using i64 = long long;
const int N = 2e5 + 10;
const int MOD1 = 1e9 + 7;
const int MOD2 = 998244353;

void solve(){
	string s;
	cin >> s;
	s = '?' + s;
	map<char,int> mp;
	string res = "";
	for(int i = 1;i <= 8;i ++){
		mp[s[i]] ++;
	}
	for(int i = 1;i <= 8;i += 2){
		res += s[i];		
	}
	s = '?' + res;
	res = "";
	for(int i = 1;i <= 4;i += 2){
		res += s[i];
	}
	s = res;
//	look(s);
	if(s[0] == s[1]){
		cout << s[0] << "\n";
		return;
	}
	if(mp[s[0]] == mp[s[1]]){
		cout << "N\n";
	}else{
		if(mp[s[0]] > mp[s[1]]){
			cout << s[0] << "\n";
		}else{
			cout << s[1] << "\n";
		}
	}
}

int main(){	

	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	
	int t = 1;
	cin >> t;

	while(t --){
		solve();
	}
	
	return 0;
}

K - 在 A 里面找有 C 的 B

Problem Description

小 T 最近上班碰到了一个问题,怎么样才能用 Vscode 快速的在 A 里面找有 C 的 B

他想要在代码库的一个文件 A 中,找到有哪些在 A 中使用到的函数 B 间接地调用了小 T 所指定的语句 C。由于小 T 的需求比较简单,所以这里不考虑间接调用的函数中的调用关系,只需要看 B 的声明中 B′ 是否包含 C 即可。

手动处理这个问题还是有相当大的重复工作量的,但是你是一个经验丰富的SQL BOY/GIRL,你对小 T 说:“闹麻了,这不就是把 A 里面包含的 B,和包含 C 的 B JOIN 一下就行了吗。”

虽然是的,但是这两张表哪来呢?

为了圆你吹下的牛,现在你需要手写一个字符串解析器,用来在 A 里面找有 C 的 B

接下来我们会给定一个长字符串 A,一个短字符串 C,以及 n 个短字符串 {Bn},以及其声明 {B′n}。

我们定义一个字符串 S 在字符串 T 中当且仅当存在一个 T 的子串 p 满足:S=p。

在这里插入图片描述
Input

第一行输入一个正整数 T (1≤T≤10),表示总共有 T 组数据。

对于每一组测试数据,首先是一个正整数 n (1≤n≤105)。

接下来是两个由小写字母构成的字符串 A,C (1≤|A|≤105,1≤|C|≤104)

接下来 n 行,每行读入两个由小写字母构成的字符串 Bi,B′i (1≤|Bi|≤104,1≤|B′i|≤105)

每组数据中有额外限制如下:∑|Bi|≤105,∑|B′i|≤5×105

在这里插入图片描述
Output

对于每一组测试数据,输出一行整数 i 满足 Bi 在 A 中,同时 B′i 中包含 C,相邻的整数之间用空格分割,注意行末不保留空格

假如答案集合为空,则输出一个空行。

在这里插入图片描述

解题思路

好像是个AC自动机的模板题,还不是很熟悉AC自动机,先贴个代码。

AC代码

#include<bits/stdc++.h>
#define int long long
#define look(x) cout << #x << " ==  " << x << "\n"
using namespace std;
using i64 = long long;
const int N = 2e5 + 10;
const int MOD1 = 1e9 + 7;
const int MOD2 = 998244353;
int n,m,o;
int ne1[N],ne2[N],jk[N];
int v[N],in[N],idx,mp[N];
string s1,s2;
struct cs{
	int son[30],fail,flag,ans;
	void clear(){
		memset(son,0,sizeof(son));
		fail = flag = ans = 0;
	}
}tire[N];
queue<int> q;
void insert(string s,int num){
	int u = 1;
	for(int i = 0;s[i];i ++){
		int v = s[i] - 'a';
		if(!tire[u].son[v]){
			tire[u].son[v] = ++ idx;
		}
		u = tire[u].son[v];
	}
	if(!tire[u].flag){
		tire[u].flag = num;
	}
	mp[num] = tire[u].flag;
}
void getfail(){
	for(int i = 0;i < 26;i ++){
		tire[0].son[i] = 1;
	}
	q.push(1);
	while(!q.empty()){
		int u = q.front();
		q.pop();
		int Fail = tire[u].fail;
		for(int i = 0;i < 26;i ++){
			int v = tire[u].son[i];
			if(!v){
				tire[u].son[i] = tire[Fail].son[i];
				continue;
			}
			tire[v].fail = tire[Fail].son[i];
			in[tire[v].fail] ++;
			q.push(v);
		}
	}
}
void tp(){
	for(int i = 1;i <= idx;i ++){
		if(in[i] == 0){
			q.push(i);
		}
	}
	while(!q.empty()){
		int u = q.front();
		q.pop();
		v[tire[u].flag] = tire[u].ans;
		int v = tire[u].fail;
		in[v] --;
		tire[v].ans += tire[u].ans;
		if(in[v] == 0){
			q.push(v);
		}
	}
}
void query(string s){
	int u = 1,len = s.size();
	for(int i = 0;i < len;i ++){
		u = tire[u].son[s[i] - 'a'];
		tire[u].ans ++;
	}
}
void solve(){
	cin >> n;
	cin >> s1 >> s2;
	idx = 1;
	int l2 = s2.size();
	s2 = '?' + s2;
	for(int i = 2,j = 0;i <= l2;i ++){
		while(j && s2[i] != s2[j + 1]){
			j = ne2[j];
		}
		if(s2[i] == s2[j + 1]){
			j ++;
		}
		ne2[i] = j;
	}
	for(int i = 1;i <= n;i ++){
		jk[i] = 0;
		string a,b;
		cin >> a >> b;
		insert(a,i);
		int n2 = b.size();
		b = '?' + b;
		for(int ii = 1,j = 0;ii <= n2;ii ++){
			while(j && b[ii] != s2[j + 1]){
				j = ne2[j];
			}
			if(b[ii] == s2[j + 1]){
				j ++;
			}
			if(j == l2){
				jk[i] = 1;
				break;
			}
		}
	}
	getfail();
	query(s1);
	tp();
	for(int i = 1;i <= n;i ++){
		if(v[mp[i]] && jk[i]){
			cout << i << " ";
		}
	}
	cout << "\n";
	for(int i = 1;i <= idx;i ++){
		tire[i].clear();
	}
}

signed main(){	

	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int t = 1;
	cin >> t;

	while(t --){
		solve();
	}
	
	return 0;
}
;