2021 ICPC 昆明(TeamVP)
比赛相关信息
比赛信息
比赛名称: 2022年第46届ICPC亚洲区域赛(昆明)
比赛地址: 牛客
榜单回顾: Board全部参赛队伍: 排名至697
金: Rk 35,5题,1731m
银: Rk 105,3题,556m(至4题,529m)
铜: Rk 210,2题,284m(至3题,560m)
比赛过程回顾
A | B | C | D | E | F | G | H | I | J | K | L | M | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
提交次数 | 5 | ||||||||||||
首次提交时间 | 0:25:15 | ||||||||||||
首A时间 | 1:22:15 | ||||||||||||
状态 | 补 | ⚪ | ⚪补 | ⚪ | ⚪补 | ⚪补 | ✔ | ||||||
知识点 | 搜索+模拟 | 状压DP+期望+离散化(容斥) | 期望+微积分 | 构造+组合数学+位运算 | 莫队(板子) | 树+数学规律 | 期望+数学规律 | 计算几何 |
共 1 题,157m,Rk 657。
✔:比赛时通过;⚪:比赛时尝试过;补:已补题。
部分题解与小结
A - Amino Acids
小评
大模拟题,以前没写过这种类型的题目,CF等比赛也不太会考这种,导致写了很久很久。
过了之后再回顾这道题,确实不难,但有很多坑需要注意,也有很多可以优化的地方。闲暇之余还是要适当做点这种大模拟题。
题意
总所周知,氨基酸脱水可以组成肽链,规律是:\(N\) 个氨基酸脱去 \(N-1\) 个水分子,形成肽链;从分子量角度,脱水的过程一共会失去 \((N-1)*18\) 的重量。
现在,给定 \(N \le 10\) 种不同的氨基酸,并且给出能够合成的肽链的最大质量 \(M \le 450\) ,求解一共有多少种不同的脱水连接方式,并且按照连接的氨基酸的缩写的字典序从小到大依次输出。
思路
第一步:读入。在这一步,我使用了最笨的手动一点点输入的方式完成,一共需要:氨基酸结构式、氨基酸重量、氨基酸字典序编号、氨基酸缩写。这一步的坑在于我们需要按照字典序输出,而题目并不是按照字典序来给出信息的,所以需要匹配顺序。
第二步:计算。这一步我们使用到的是 \(\tt{}DFS\) 。
第三步:输出。由于需要分别输出连接数量和实际连接情况,这里需要再用一次 \(\tt{}DFS\) ,使用一个临时数组记录被使用的氨基酸编号,并且进行脱水处理。规律如下:
- 脱水只存在于第三行,并且会新增一个肽键;
- 需要注意氨基酸首位重叠的情况(是否去头/去尾);
AC代码
点击查看代码
// Date: 2022-05-04 15:24:36
// Problem: Amino Acids
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/33953/A
// Memory Limit: 524288 MB
// Time Limit: 2000 ms
// --------By WIDA--------
//A WIDA Project
//#include<bits/stdc++.h>
#include <algorithm> //min, max, swap, sort, reverse, lower_bound, upper_bound
#include <assert.h>
#include <bitset>
#include <cmath>
#include <cstdio> //printf
#include <cstring>
#include <deque>
#include <iomanip> //fixed, setprecision
#include <iostream> //cin, cout, endl
#include <map>
#include <numeric> //iota
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <vector>
using namespace std;
#define LL long long
#define ULL unsigned long long
#define ms(a,b) memset(a,b,sizeof(a))
#define For(i,a) for(auto i:a)
#define FOR(i,a,b) for(int i=(int)(a);i<=(int)(b);i++)
#define FOR2(i,a,b) for(int i=(int)(a);i<=(int)(b);i+=2)
#define FORD(i,b,a) for(int i=(int)(a);i>=(int)(b);i--)
#define sz size() /*eg:"a.sz", mean "a.size()".*/
#define pb push_back /*eg:"a.pb(x), mean "a.push_back(x)*/
#define ALL(a) a.begin(), a.end()
#define rALL(a) a.rbegin(), a.rend()
#define endl "\n"
#define PII pair<int, int>
#define PIII tuple<int, int, int>
#define VI vector<int>
#define VPII vector<PII>
#define Yes cout<<"Yes"<<endl
#define No cout<<"No"<<endl
#define yes cout<<"yes"<<endl
#define no cout<<"no"<<endl
#define YES cout<<"YES"<<endl
#define NO cout<<"NO"<<endl
#define fi first
#define se second
#define RE return;
#define RE0 return 0;
#define RET return true;
#define REF return false;
#define REX exit(0);
#define REL cout.flush(); /*interactive*/
const LL INFF = 0x3f3f3f3f3f3f3f3f;
const int MOD = 998244353;
//const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const double EPS = 1e-7;
void _() { cout << endl; }
void XYZ() { cout << "-->"; }
template<class T, class S>
inline bool equal(T x, S y) { return x - y < EPS && x - y > -EPS; }
template<class T>
inline T mygcd(T x, T y) { return x % y == 0 ? y : mygcd(y, x % y); }
template<class T>
inline T mylcm(T x, T y) { return x / mygcd(x, y) * y; }
template<class T>
inline T mymax(T x, T y) { return x < y ? y : x; }
template<class T>
inline T mymin(T x, T y) { return x > y ? y : x; }
template<class T>
inline void cmax(T &x, T y) { x = mymax(x, y); }
template<class T>
inline void cmin(T &x, T y) { x = mymin(x, y); }
template<class T>
void P(T x) { cout << x << endl; }
template<class T>
void XYZ(T x) { cout << x << " "; }
template<class X0>
void _(X0 x0) { XYZ(); XYZ(x0); _(); }
template<class X0, class X1>
void _(X0 x0, X1 x1) { XYZ(); XYZ(x0); XYZ(x1); _(); }
template<class X0, class X1, class X2>
void _(X0 x0, X1 x1, X2 x2) { XYZ(); XYZ(x0); XYZ(x1); XYZ(x2); _(); }
template<class X0, class X1, class X2, class X3>
void _(X0 x0, X1 x1, X2 x2, X3 x3) { XYZ(); XYZ(x0); XYZ(x1); XYZ(x2); XYZ(x3); _(); }
template<class X0, class X1, class X2, class X3, class X4>
void _(X0 x0, X1 x1, X2 x2, X3 x3, X4 x4) { XYZ(); XYZ(x0); XYZ(x1); XYZ(x2); XYZ(x3); XYZ(x4); _(); }
template<class X0, class X1, class X2, class X3, class X4, class X5>
void _(X0 x0, X1 x1, X2 x2, X3 x3, X4 x4, X5 x5) { XYZ(); XYZ(x0); XYZ(x1); XYZ(x2); XYZ(x3); XYZ(x4); XYZ(x5); _(); }
template<class X0, class X1, class X2, class X3, class X4, class X5, class X6>
void _(X0 x0, X1 x1, X2 x2, X3 x3, X4 x4, X5 x5, X6 x6) { XYZ(); XYZ(x0); XYZ(x1); XYZ(x2); XYZ(x3); XYZ(x4); XYZ(x5); XYZ(x6); _(); }
template<class T>
void _i(T x) { for (auto i : x) XYZ(i); _(); }
template<class T>
void _ii(T x) { for (auto i : x) XYZ(i.fi), XYZ(i.se), XYZ(','); _(); }
//====================
#define int LL
const int N = 1e6 + 7;
string str[10][13] = {{
//Ala
" H H O ",
" | | || ",
"H-N-C-C-O-H",
" | ",
" H-C-H ",
" | ",
" H ",
" ",
" ",
" ",
" ",
" ",
" ",
},{
//Asn
" H H O ",
" | | || ",
"H-N-C-C-O-H",
" | ",
" H-C-H ",
" | ",
" O=C-N-H ",
" | ",
" H ",
" ",
" ",
" ",
" ",
},{
//Asp
" H H O ",
" | | || ",
"H-N-C-C-O-H",
" | ",
" H-C-H ",
" | ",
" O=C-O-H ",
" ",
" ",
" ",
" ",
" ",
" ",
},{
//Cys
" H H O ",
" | | || ",
"H-N-C-C-O-H",
" | ",
" H-C-S-H ",
" | ",
" H ",
" ",
" ",
" ",
" ",
" ",
" ",
},{
//Gln
" H H O ",
" | | || ",
"H-N-C-C-O-H",
" | ",
" H-C-H ",
" | ",
" H-C-H ",
" | ",
" O=C-N-H ",
" | ",
" H ",
" ",
" ",
},{
//Glu
" H H O ",
" | | || ",
"H-N-C-C-O-H",
" | ",
" H-C-H ",
" | ",
" H-C-H ",
" | ",
" O=C-O-H ",
" ",
" ",
" ",
" ",
},{
//Gly
" H H O ",
" | | || ",
"H-N-C-C-O-H",
" | ",
" H ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
},{
//Met
" H H O ",
" | | || ",
"H-N-C-C-O-H",
" | ",
" H-C-H ",
" | ",
" H-C-H ",
" | ",
" S ",
" | ",
" H-C-H ",
" | ",
" H ",
},{
//Ser
" H H O ",
" | | || ",
"H-N-C-C-O-H",
" | ",
" H-C-O-H ",
" | ",
" H ",
" ",
" ",
" ",
" ",
" ",
" ",
},{
//Thr
" H H O ",
" | | || ",
"H-N-C-C-O-H",
" | ",
" H-C-O-H ",
" | ",
" H-C-H ",
" | ",
" H ",
" ",
" ",
" ",
" ",
}};
map<string, int> Name;
int val[] = {89, 132, 133, 121, 146, 147, 75, 149, 105, 119};
int line[] = {6, 8, 6, 6, 10, 8, 4, 12, 6, 8};
int n, m, ans;
VI give, now;
//====================void Force() {}
void OI() {
// _i(now);
int max_line = 0, n = now.sz;
for (auto i : now) cmax(max_line, line[i]);
FOR (l, 0, max_line) {
if (l == 2) { //特判第2行
FOR (j, 0, 7) cout << str[now[0]][l][j];
cout << ((l == 2) ? "-" : " ");
FOR (i, 1, n - 2) {
FOR (j, 1, 7) cout << str[now[i]][l][j];
cout << ((l == 2) ? "-" : " ");
}
FOR (j, 1, 10) cout << str[now[n - 1]][l][j];
cout << endl;
continue;
}
FOR (j, 0, 8) cout << str[now[0]][l][j];
FOR (i, 1, n - 2) FOR (j, 1, 8)
cout << str[now[i]][l][j];
FOR (j, 1, 10) cout << str[now[n - 1]][l][j];
cout << endl;
}
cout << endl;
}
void dfs_for_OI(int cnt, int num) {
if (num < 0) return; //不满足构造
if (cnt >= 2) OI();
for (auto i : give) {
now.pb(i); //加入OI备选表
if (cnt == 0) dfs_for_OI(cnt + 1, num - val[i]);
else dfs_for_OI(cnt + 1, num - val[i] + 18);
now.pop_back();
}
}
void dfs_for_num(int cnt, int num) {
if (num < 0) return; //不满足构造
if (cnt >= 2) ++ ans;
for (auto i : give) {
if (cnt == 0) dfs_for_num(cnt + 1, num - val[i]);
else dfs_for_num(cnt + 1, num - val[i] + 18);
}
}
void Solve() {
Name["Ala"] = 0;
Name["Asn"] = 1;
Name["Asp"] = 2;
Name["Cys"] = 3;
Name["Gln"] = 4;
Name["Glu"] = 5;
Name["Gly"] = 6;
Name["Met"] = 7;
Name["Ser"] = 8;
Name["Thr"] = 9;
cin >> n >> m;
FOR (i, 1, n) {
string s; cin >> s;
give.pb(Name[s]); //给定的氨基酸
}
sort(ALL(give));
dfs_for_num(0, m); cout << ans << endl;
dfs_for_OI(0, m);
}
signed main() {
// cout << fixed << setprecision(12);
// Force();
ios::sync_with_stdio(0); cin.tie(0);
int Case = 1;
// cin >> Case;
while (Case -- > 0) Solve();
return 0;
}
D - Divisions
小评
大构造题。有一些很基础的规律很容易就能推导出来,但是怎么充分利用这些规律去寻找正解是需要增强的关键所在。
题意
定义三个序列 \(S,S_A,S_B\) ,其中 \(S\) 包含 \(N\) 个数字,\(S_A\) 是 \(S\) 的非递减子序列(可以为空),\(S_B\) 是 \(S\) 的非递增子序列(可以为空)。显然,存在多种方式能够将序列 \(S\) 分割为不同的子序列 \(S_A,S_B\) 。
现在给出 \(K(0 \le K \le 10^8)\),你需要输出任意一个长度为 \(N(1 \le N \le 365)\)的序列 \(S\) ,使得恰好有 \(K\) 中方式能够将 \(S\) 分割,且需要保证 \(S\) 中的每一个元素 \(1 \le S_i \le 10^8\) 。
思路
第一步:尝试寻找简单的结论。根据题意,我们可以得到如下结论:
- 若 \(K=2^N\) ,则只需要构建一个长度为 \(N\) 的,元素全部相等的序列;
- 若 \(N\) 的长度没有限制,则对于任意的 \(K\) ,只需要构建一个长度为 \(K-1\) 的递增/递减序列即可;
第二步:尝试总结规律。进一步分析上述第二条小结论推导的过程,当我们构建出一条递增序列时,\(S_B\) 中要么只存在一个元素、要么为空,而此时 \(S_B\) 的不同取法即为 \(K\) 的值,所以此时 \(N\) 与 \(K\) 的关系是$$K=1+N$$其中 \(1\) 代表空集的取法。
此时,再结合第一条小结论,我们依旧可以从 \(S_B\) 的不同取法入手,构建出形如 1 1 1 || 2 2 … 2 || 3 3 … 3
这样的序列,记 \(C_i\) 为序列中 \(i\) 的数量,那么相似的,\(S_B\) 中要么只存在同一个元素(根据组合数学的知识,从大小为 \(N\) 的集合中取出非空集合,一共有 \(2^N-1\) 种取法)、要么为空,此时 \(N\) 与 \(K\) 的关系为:
其中 \(1\) 代表空集的取法。
至此,我们可以通过类二进制表示数字的方式,表示出任意一个序列。
AC代码
点击查看代码
void Solve() {
int k; cin >> k;
if (k == 0) cout << "6\n1 5 3 6 2 4\n";
else if (k == 1) cout << "6\n1 1 4 5 1 4\n";
if (k <= 1) return;
-- k;
int num = 0; vector<int> ans;
for (int i = 30; i >= 1; -- i) {
int x = 1 << i;
while (k >= x - 1) {
++ num;
k -= x - 1;
for (int j = 1; j <= i; ++ j) ans.pb(num);
}
}
cout << ans.size() << endl;
for (auto i : ans) cout << i << " ";
}
F - Find the Maximum
小评
另外一道签到题,其实并没有难度,但是可能由于题面过于吓人的缘故,致使最终也没有多少队伍做出这道题。
题面中出现的“树上”和“任选实数 \(x\) ”到最后均可以被巧妙化解。
题意
给出一棵带点权的树,要求找到一条长度为 \(V\) 的路径( \(V \ge 0\) ),使得这条路径上点的点权 \(w_1,w_2,…,w_n\) 满足 \(\frac{\sum_{i=1}^n (-x^2+W_i*x)}{V}\) 的值最大,直接输出这个最大值。其中,\(x\) 可以取任意实数。
思路
尝试化简所给出的式子:
记 \(\sum'=\sum_{i=1}^n(W_i)\) ,
第一步(1):化简公式。分子即化简为
至此,我们发现,当 \(x=0\) 时上式取得最大值 \(\frac{\sum'^2}{4*V}\) ,且与 \(x\) 无关。故原式最大值为
原题即,在树上找一条路径,使得路径上平均点权值最大。
第一步(2):套公式直接化简(杜老师思路)。原式可以化简为
根据初高中知识,一元二次方程当 \(x=-\frac{b}{2*a}\) 时可以取到最大值
第二步:分析公式。对于上式,有一个结论是:
当取 \(2\) 至 \(3\) 个点时,平均值最大。
换言之,若在一段序列上连续的取数并计算平均值,要使得平均值最大,最多取不超过 \(3\) 个数。
这个结论直接看可能比较玄乎,这里给出一个简单的证明:
点击查看证明
设有点集 \(X(| X | \ge 3)\) ,那么其可以被分割为点集 \(A(|A|=2)\) 和点集 \(B(|B| \ge 2)\),\(\bar A, \bar B\) 中至少有一个大于 \(\bar X\) (如果说 \(\bar A,\bar B \le \bar X\) ,那么 \(\bar X\) 只会更小),所以选择小点集答案必然更优。
第三步:代码实现。根据上述推论,我们只需要进行枚举——
- 对所有相邻的点的点权 \(w_x,w_y\) 代入公式 \(\frac{(w_x+w_y)^2}{16}\) 计算;
- 找到与某一个点 \(w\) 相连的所有点中点权最大 \(w_{max}\) 、次大的点 \(w_{max'}\) ,并带入公式 \(\frac{(w_{max}+w_{max'}+w)^2}{36}\) 计算;
相对应的,由于取了平方,故点权最小、次小的点也要进行一遍计算。
AC代码
点击查看代码
int n;
double a[N], ans;
VI ver[N];
void Solve() {
cin >> n;
FOR (i, 1, n) cin >> a[i];
FOR (i, 1, n - 1) {
int x, y; cin >> x >> y;
ver[x].pb(y);
ver[y].pb(x);
cmax(ans, (a[x] + a[y]) * (a[x] + a[y]) / 16);
}
FOR (i, 1, n) {
auto j = ver[i];
sort(ALL(j), [](int x, int y) {
return a[x] < a[y];
});
if (j.sz < 2) continue;
int x = j[0], y = j[1], m = j.sz;
cmax(ans, (a[x] + a[y] + a[i]) * (a[x] + a[y] + a[i]) / 36);
x = j[m - 1], y = j[m - 2];
cmax(ans, (a[x] + a[y] + a[i]) * (a[x] + a[y] + a[i]) / 36);
}
cout << ans << endl;
}
G - Glass Bead Game
小评
初见以为是记忆化搜索,至多三轮递归搜索可以得解(粗略的计算了下精度问题,发现刚好可以对应题目中的 \(n=100\))写了很久,最后发现因为没有合适的数据结构来储存如此大量且递推变化的下标,只能放弃。补题的时候才知道这道题根本就不用搜索。
题解看了很久,还是有点似懂非懂,这种类型的题目平时训练做的实在太少了,得留个心眼。
题意
\(N\) 颗珠子排成一排,给出每一颗珠子被选中的概率 \(P_i\) ,规定操作:
- 每一轮,按照给出的概率,随机选择一颗珠子,将其移动至最前面;
- 假设选中的是第 \(i\) 颗珠子,那么移动它需要花费 \(i\) ;
现在,不断的按照上述的规定进行操作,当操作轮次趋于 \(\infty\) 时,每一轮操作的花费会趋向于什么?输出这个数字。
思路
(杜老师思路)
我们需要计算的是第 \(\infty\) 轮操作所花费的期望 \(\mathbb{E}(X)\) ,记 \(I\) 为在这一轮中将第 \(i\) 颗珠子移动至最前面的花费。显然的,有 \(\mathbb{E}(X)=\sum_{i=1}^n(P_i*I)\) ,而 \(I\) 的值不仅随 \(i\) 的取值变化,还随轮次而变化,接下去我们只需要找到 \(I\) 与其他变量的联系即可。
假设 \(i\) 已经取定,若
- 存在另一颗珠子 \(j\) 在这一轮被移动至最前面,那么 \(j\) 对于操作花费的贡献即为 \(1*\frac{P_j}{P_i+P_j}\) ,而相对应的,如果
- \(i\) 在这一轮被移动至最前面,那么这一操作对于操作花费的贡献即为 \(0*\frac{P_i}{P_i+P_j}\) ;
所以,我们只需要依次枚举 \(i\) 的位置,再对于每一个 \(i\) ,枚举 \(j\) 对于其的贡献值,这些贡献值之和即为答案。
AC代码
点击查看代码
//A WIDA Project
#include <bits/stdc++.h>
using namespace std;
#define LL long long
double a[N], ans;
int main() {
ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int n; cin >> n;
for (int i = 1; i <= n; ++ i) cin >> a[i];
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= n; ++ j) {
if (i == j) continue;
ans += a[i] * a[j] / (a[i] + a[j]);
}
}
cout << ans << endl;
return 0;
}
K - King of Gamers
小评
\(\mathcal{Consider\ by\ Wcj\ \&\ Hamine}\)
\(\mathcal{Solved\ by\ Wcj\ \&\ Hamine}\)
签到题,写了好久好久……
题意
一共 \(1\le T\le 10^5\) 组样例。一个人参加 \(N\) 局比赛,给出他的期望胜率 \(x=\frac{a}{b}\) ,规定他的胜负条件为:
- 当实际胜率 \(\le x\) 时,他会赢;
- 反之,他会输;
输出这个人一共赢下的局数。
思路
(杜老师思路)
找了五分钟规律,硬找出来的。
(自己思路)
容易发现,第一局这个人必定会赢,答案中必定存在一个 \(+1\) 。
那么,由最基本的概率计算,可以得到:剩下的 \(N-1\) 局,这个人赢的局数为 \((N-1)*\lfloor x \rfloor\) 。
至此,答案得到。
AC代码
点击查看代码
//A WIDA Project
#include <bits/stdc++.h>
using namespace std;
#define LL long long
int main() {
ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int T; cin >> T;
while (T -- > 0) {
int n, a, b;
cin >> n >> a >> b;
cout << (n - 1) * a / b + 1 << endl;
}
return 0;
}
文 / WIDA
2022.05.04 成文
首发于WIDA个人博客,仅供学习讨论