0321周赛
T1 NKOJ 译密码子
DNA是一切细胞生物的遗传物质。它能指导蛋白质的合成,从而控制细胞的新陈代谢和生物的性状。
中心法则(genetic central dogma) 是所有有细胞结构的生物所遵循的法则,它的主要内容是遗传信息从DNA传递给mRNA,再从mRNA传递给蛋白质的转录和翻译的过程。
mRNA是由许多核糖核苷酸组成的链状分子,但这些核糖核苷酸不外乎4种:腺嘌呤核糖核苷酸(A),鸟嘌呤核糖核苷酸(G),胞嘧啶核糖核苷酸(C)和尿嘧啶核糖核苷酸(U)。
mRNA上三个相邻的核糖核苷酸序列叫做密码子,一个密码子可以翻译成一个氨基酸,密码子不重叠。
一条mRNA只能翻译成若干种氨基酸,并且知道决定这些氨基酸的密码子。
给出一条mRNA的核糖核苷酸序列,请你计算出它最多能翻译成多少氨基酸。
思路:直接暴力从前往后匹配即可。
#include <bits/stdc++.h> using namespace std; int n, len, ans; char ch[10005]; char key[105][5]; bool check(int x) { if (x > len - 2) return 0; for (int i = 1; i <= n; i++) { if (key[i][1] == ch[x] && key[i][2] == ch[x + 1] && key[i][3] == ch[x + 2]) return 1; } return 0; } int main() { scanf("%s", ch + 1); len = strlen(ch + 1); while (scanf("%s", key[++n] + 1) != EOF); --n; for (int i = 1; i <= len; i++) { if (check(i)) ans++, i += 2; } printf("%d", ans); }
T2 NKOJ 谁割韭菜
何老板种的韭菜被人割了,他想要找出罪犯。
有n个嫌疑人,其中一部分人参与了割韭菜的操作,他们都清楚哪些人跟他们是一伙的。
何老板对每个人都进行了灵魂拷问:“说,谁割了韭菜?”
如果被问的人是罪犯,他会指着一个无辜的人说:“是他!”
如果被问的人是无辜的,在何老板的逼问下,被吓得瑟瑟发抖,只好随便指着一个人说“是他!”
何老板想知道,根据上述信息,最多有多少人可能参与了割韭菜?
思路:其实并不是很难,只是贪心策略比较奇特:将入度为0的点都当做罪犯,交叉染色,然后再搞环,进入搜索时将其定义成无辜的人。
#pragma GCC optimize(3) #include <bits/stdc++.h> using namespace std; int to[500005], ind[500005], ans; bool vis[500005]; char buf[1 << 23], *p1 = buf, *p2 = buf; #define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++) template <class T> inline void rd(T &a) { char ch = getchar(); T x = 0, f = 1; while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();} while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar(); a = x; } void dfs(int u, int mark) { if (vis[u]) return; vis[u] = 1; ans += mark; if (--ind[to[u]] == 0 || mark) dfs(to[u], !mark); } int main() { int n; rd(n); for (int i = 1; i <= n; i++) rd(to[i]), ind[to[i]]++; for (int i = 1; i <= n; i++) if (ind[i] == 0) dfs(i, 1); for (int i = 1; i <= n; i++) dfs(i, 0); printf("%d", ans); }
T3 NKOJ 英雄回合
在玩DOTA时,你经常会遇到这样尴尬的情况,那就是你的队友全都死了,游戏中只剩下你一个人独自迎战N个敌人。
在游戏中,你操控着一个英雄,每个英雄都有两项属性:生命值(HP)和杀伤力(DPS)。你的英雄有无限的HP值,但是DPS值为1。也就是每一次攻击,你只能让敌方英雄损失1点的生命值。
假设我们玩的DOTA游戏是回合制的。每一个回合,你可以选择攻击敌方的一个英雄,被攻击的敌方英雄的HP值会减1。同时,所有活着的敌方英雄会攻击你,你的HP值减少的数量等于他们的杀伤力的总和。如果一个英雄的HP值降至0,他就死掉了,不会再攻击你了。
你能否赢得这场游戏呢?所以请你计算最少损失多少HP值,才可以消灭所有的敌方英雄。
思路:显然的状压dp,显然一次要杀掉一个英雄。然后记搜即可。
#pragma GCC optimize(3) #include <bits/stdc++.h> using namespace std; typedef long long ll; ll dm[25], hp[25], dps; ll f[(1 << 20) + 1], n, v[(1 << 20) + 1]; bool mark[(1 << 20) + 1]; ll dp(ll s) { if (mark[s]) return f[s]; for (ll i = 1; i <= n; i++) { if (s & (1 << i - 1)) f[s] = min(f[s], dp(s ^ (1 << i - 1)) + (dps - v[s ^ 1 << i - 1]) * hp[i]); } return mark[s] = 1, f[s]; } main() { scanf("%lld", &n); for (int i = 1; i <= n; i++) scanf("%lld%lld", &dm[i], &hp[i]), dps += dm[i]; memset(f, 0x3f, sizeof f); f[0] = 0; int tot = 1 << n; for (int s = 1; s < tot; s++) { for (ll i = 1; i <= n; i++) if (s & (1 << i - 1)) v[s] += dm[i]; } printf("%lld", dp(tot - 1)); }
T4 NKOJ 路径异或
给你一棵树,共n个节点,每个节点有一个权值。
对于树中任意一个点对(x,y),它的得分为从x到y路径上所有经过的点的点权的进行异或运算。我们称为该点对的路径分。
何老板拜托你帮忙求出该树中所有点对的路径分总和。
注意,(x,x)也算一个点对。
思路:二进制分组,将一棵树按位分为22棵树,每一棵树都记录了当前这一位为1的节点的个数,于是转移即可。
#include <bits/stdc++.h> using namespace std; typedef long long ll; ll ans, sum; struct node { ll to, nxt; }e[200005]; ll head[100005], tot, val[100005], f[100005][30], cnt[100005]; inline void add_e(ll u, ll v) {e[++tot].to = v; e[tot].nxt = head[u]; head[u] = tot;} char buf[1 << 23], *p1 = buf, *p2 = buf; #define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++) template <class T> inline void rd(T &a) { char ch = getchar(); T x = 0, f = 1; while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();} while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar(); a = x; } void dfs(ll u, ll fa) { cnt[u] = 1; for (ll i = 0; i <= 25; i++) f[u][i] = (val[u] >> i) & 1; for (ll i = head[u]; i; i = e[i].nxt) { ll v = e[i].to; if (v == fa) continue; dfs(v, u); for (ll j = 0; j <= 25; j++) { ans += (1 << j) * (f[u][j] * (cnt[v] - f[v][j]) + f[v][j] * (cnt[u] - f[u][j])); f[u][j] += (((val[u] >> j) & 1) ? cnt[v] - f[v][j] : f[v][j]); } cnt[u] += cnt[v]; } } main() { // freopen("1.txt", "r", stdin); ll n; rd(n); for (ll i = 1; i <= n; i++) rd(val[i]), ans += val[i]; for (ll i = 1, x, y; i < n; i++) rd(x), rd(y), add_e(x, y), add_e(y, x); dfs(1, 0); printf("%lld", ans); }
T5 NKOJ 队列翻转
每个星期四下午是信竞队的体育活动时间。这次何老板给大家安排的体育活动是做广播体操。
名队员站成一排,何老板发现,同学们的队形并不是按照从左往右以身高由低到高来排列的,当前的队形有碍观感,何老板决定调整一下队形,使得队列中按由低到高顺序排列的区域尽可能长。
何老板调整队列的方法时,在队列中任意选择一个子序列,将子序列中的同学的位置翻转。何老板也比较懒,他只想进行一次翻转操作。他想知道,操作后,能够得到的最长一段按身高由低到高(不下降)排列的子序列有多长?
思路:其实翻转就是将两个点交换,然后交换多少对就等价于翻转多少元素。
#include <bits/stdc++.h> using namespace std; int f[55][55][55][55], a[55]; int dp(int l, int r, int u, int d) { if (l > r || u < d) return 0; if (~f[l][r][u][d]) return f[l][r][u][d]; int ans = max(dp(l, r, u - 1, d), dp(l, r, u, d + 1)); ans = max(ans, dp(l + 1, r, u, d) + (a[l] == d)); ans = max(ans, dp(l, r - 1, u, d) + (a[r] == u)); ans = max(ans, dp(l + 1, r - 1, u, d) + (a[l] == u) + (a[r] == d)); return f[l][r][u][d] = ans; } int main() { int n; scanf("%d", &n); memset(f, -1, sizeof f); for (int i = 1; i <= n; i++) scanf("%d", &a[i]), f[i][i][a[i]][a[i]] = 1; printf("%d", dp(1, n, 50, 1)); }
总结:依旧没能考出很好的水平,B题做不出来确实是证明wtcl。

浙公网安备 33010602011771号