“战疫杯”在线邀请赛——第五场题解
题目详情 - 1 感染源在哪里 (pintia.cn)
通过最近的一次核酸检测,疫情防控小组检测到了若干个阳性人员,通过调取行程码数据,防控小组获取到了这些病例曾经去过的地点。现在希望对这些地点做源头可能性分析,来找出哪些地点更有可能是本次疫情的源头。
对于一个病例,如果其曾到过a,b,c三个地点,那么这三个地点都会在其源头可能得分上增加1/3,如果某个病例曾经去过a,c两个地点,那么这两个地点作为源头的得分都会增加1/2,如果只去过一个地点的病例,那么该地点作为源头的概率很高,我们给这个地点的得分增加1。
简而言之,如果一个病例去过n个地点,那么这n个地点都会得到1/n的得分。现在给你所有病例去过的地点的情况,你能否分析出每个地点作为源头的可能性大小顺序?
输入格式:
第一行一个整数n(1≤n≤1000),表示记录的条数
其后n行,每行两个不为空的字符串a,b(1≤∣a∣,∣b∣≤10),仅包含小写字母。其中a代表病例的名字,b代表地点。
注意:当出现有人多次去同一地点时,只计算一次
输出格式:
输出若干行,每行代表一个地点名,代表所有地点作为源头的得分从高到低排序的结果。如果有多个地点得分相同,地点名字典序小的优先。
输入样例:
6
xiaoa adian
xiaob adian
xiaob bdian
xiaoc adian
xiaoc bdian
xiaoc cdian
输出样例:
adian
bdian
cdian
问题解析
(注:本题方法容易被hack)
这里的主要问题就是分配分数,如果一个人去了n个地方,那么这个地方会得到1/n的分数,本人试过用double来算每个地方的分数但是wa了一发,然后一气之下换了个非常粗暴危险的方法。
假设此题一共有m个不同的地方,那么一个人最多只会去到m个地方,可能分配分数的方式就是1/1、1/2、1/3……1/m。我直接用一个longlong变量ans来当总分,我们不一定要算的是1/m,可以是ans/m,也就是说对于一个人去到地点数的不同,我们可以分成ans/1,ans/2,ans/2,……,ans/m。那么只要这个ans可以被1……m的数整除,这问题就会变得非常简单了。
我用一个map容器res来记录不同地点的数量,然后用ans=1从1一直乘到res.size()为止,这样ans必然会被1……res.size()的任意数整除。用mymap来记录每个人走过的地方的情况,cnt是防止重复记录一个人去的地方(比如a去了两次bdian,我们只记录一次,第二次就不记录了)。
然后我们根据每个人去的地点来分配分数,再根据地点的分数进行排序后输出。
(longlong最多可以存的数大概是1e19这样,也就是说要是有20个不同的地方我这个方法就寄了)
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include<numeric>
#include<string>
#include<string.h>
#include<iterator>
#include<map>
#include<unordered_map>
#include<stack>
#include<list>
#include<queue>
#include<iomanip>
#define endl '\n';
typedef long long ll;
typedef pair<ll, ll>PII;
const int N = 3e5 + 50;
bool cmp(pair<ll, string>& a, pair<ll, string>& b)
{
if (a.first != b.first)return a.first > b.first;
return a.second < b.second;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
string s1, s2;
cin >> n;
map<string, vector<string>>mymap;
map<string, map<string, int>>cnt;
map<string, ll>res;
for (int i = 0; i < n; i++)
{
cin >> s1 >> s2;
if (cnt[s1][s2] != 0)continue;
mymap[s1].push_back(s2);
cnt[s1][s2] = 1;
res[s2] = 0;
}
ll ans = 1;
for (int i = 1; i <= res.size(); i++)
{
ans *= i;
}
for (auto i : mymap)
{
int len = i.second.size();
for (auto j : i.second)
{
res[j] += ans / len;
}
}
vector<pair<ll, string>>v;
for (auto i : res)
{
v.push_back({ i.second,i.first });
}
sort(v.begin(), v.end(), cmp);
int len = v.size();
for (int i = 0; i < len; i++)
{
cout << v[i].second;
if (i != n - 1)cout << endl;
}
return 0;
}
题目详情 - 2 病毒序列 (pintia.cn)
时至至今,疫情还没结束,战斗并未停止!现如今,疫情肆虐全球,疫情的传播加速了病毒的变异,α病毒,β病毒,δ病毒,相继出现…
而小A对病毒的变异来了兴趣,带着高中学过的生物知识,小A了解到病毒可以表示为由A,U,C,G四种碱基组成的基因序列,而病毒的变异便来自于不同基因序列结合。在此,我们将两个不同的基因序列的结合定义为:两个基因序列上下排布,通过一定的错位,使得两个基因序列的部分碱基可以对应起来,若分属两个基因序列的A与U对应则形成3个氢键,若分属两个序列的C与G对应会形成2个氢键,如图所示:
顽固的病毒为了保证自己结构的稳定性,在变异过程中总会选择形成氢键个数最大的方式进行结合。现在给定两个基因序列,他们结合后的氢键个数是多少?
PS:题目描述的“结合”仅仅为本题目描述所定义,不一定符合真实的生物学性质。
输入格式:
两行,每行一个字符串,分别是两个待结合的基因序列。(保证字符串只有A,U,C,G组成,且字符串的长度小于等于5000).
输出格式:
一行一个整数,表示结合后的最大氢键个数。
输入样例:
AGC
UUCG
输出样例:
7
样例解释
共有6种可能的结合方式
AGC
UUCG
AGC
UUCG
AGC
UUCG
AGC
UUCG
AGC
UUCG
AGC
UUCG
其中形成氢键的个数分别为:3,0,0,7,0,0
则病毒序列会选择氢键个数为7的方式进行结合
问题解析
这个样例解释的很清楚,就是说两个字符串以不同位置开始匹配,然后看从哪个位置开始匹配分数最高。
拿样例来说,我们可以这么匹配:
AGC ————>s1
UUCG ————>s2
AGC
UUCG
AGC
UUCG
AGC
UUCG
AGC
UUCG
AGC
UUCG
我们可以通过如上流程,先从s2的屁股开始匹配,然后位置逐渐往前移,当s1的屁股到达s2的头部时,就是最后一步了,然后我们在此过程中维护最大的分数,最后输出分数即可。为了方便,我是在s2的尾部加上了相当于s1长度的无用字符,然后我们以尾巴对尾巴的方式来匹配,每次匹配完后去掉s2的尾部字符,当s2被删干净后,匹配结束,模拟一下就是:
AGC
UUCG**
AGC
UUCG*
AGC
UUCG
AGC
UUC
AGC
UU
AGC
U
个人认为这样比较方便,当然要是有更适合自己的方法是更好。
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include<numeric>
#include<string>
#include<string.h>
#include<iterator>
#include<map>
#include<unordered_map>
#include<stack>
#include<list>
#include<queue>
#include<iomanip>
#define endl '\n';
typedef long long ll;
typedef pair<ll, ll>PII;
const int N = 3e5 + 50;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
string s1, s2;
cin >> s1 >> s2;
int res = 0, ans = 0;
for (int i = 0; i < s1.size(); i++)
s2 += '*';
int l = 0, r = 0;
while (s2.size() != 0)
{
l = s1.size(), r = s2.size();
ans = 0;
while (l >= 0 && r >= 0)
{
if (s1[l] == 'A' && s2[r] == 'U')
{
ans += 3;
}
else if (s1[l] == 'U' && s2[r] == 'A')
{
ans += 3;
}
else if (s1[l] == 'C' && s2[r] == 'G')
{
ans += 2;
}
else if (s1[l] == 'G' && s2[r] == 'C')
{
ans += 2;
}
l--, r--;
}
res = max(res, ans);
s2.pop_back();
}
cout << res;
return 0;
}
题目详情 - 3 奇奇怪怪的形状 (pintia.cn)
小凡有一张边长为2的正方形白纸,上面画了2×2个边长为1的格子,每个格子上可以放置棱长为1的立方体。小凡手中有若干个棱长为1的立方体,他随机地在每个格子上放置一些立方体,求由这些立方体组成的几何体的表面积。
输入格式:
输入两行,每行两个不超过100的正整数,代表从俯视的角度看,白纸所画每个格子上立方体的数量。
输出格式:
输出一行,包含一个整数,表示几何体的表面积。
输入样例:
上图对应的输入数据如下:
2 1
1 1
输出样例:
20
问题解析:
这题其实相当暖心了,由于条件限制这题就显得很简单。
首先题目确保了这个摆出的物体底部是2*2的。
我们根据输入的数字用变量abcd来接收:
a:2 b:1
c:1 d:1
表面积就是漏在外面能直接被我们看到部分的面积,然后我们看向它的6个方向:上下左右前后.
首先由于它底部是固定2*2,那么不管怎么样,从上往下看和从下往上看能看到的图形都是一个2 *2的正方形,所以底部的表面积和顶部的表面积都是4,现在表面积是8。
然后我们从前往后看,看到的图形就是:
表面积为:3,现在表面积是11.
从后往前看得到的图形其实和从后往前看是一样的(只是反过来):
表面积依然为3:现在表面积是14.
从左往右看得到的图形是:
表面积为:3,现在表面积是17.
从右往左看到的图形和从左往右看到的图形是相反的:
表面积为:3,现在表面积是20.
所以算表面积,顶部和底部的4*2点已经固定了,至于前后左右看取决于它摆放的高度如何。
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include<numeric>
#include<string>
#include<string.h>
#include<iterator>
#include<map>
#include<unordered_map>
#include<stack>
#include<list>
#include<queue>
#include<iomanip>
#define endl '\n';
typedef long long ll;
typedef pair<ll, ll>PII;
const int N = 3e5 + 50;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int a, b, c, d;
cin >> a >> b >> c >> d;
int res = 8;
//这是从前往后和从后往前得到的面积
res += (max(a, c) + max(b, d))*2;
//这是从左往右和从右往左得到的面积
res += (max(d, c) + max(a, b))*2;
cout << res;
return 0;
}