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。

posted @ 2021-03-22 19:11  Chasing-Dreams  阅读(71)  评论(0)    收藏  举报