Codeforces Global Round 1~3

CF1110C

回答q个关于a的询问,每次询问求:f(a)=max0<b<agcd(ab,a&b)a<225

假设ak位,对a取个反,即b=a就会发现gcd(ab,a&b)=2k1达到最大。注意这里的取反指的是在a的位数以内取反。但是如果a=2k1,因为b>0所以就不能这么求。不妨直接暴力对每个k,求解f(2k1),打个表出来。时间复杂度O(1),速度巨快如雷电。

CF1110D

打麻将,n种牌,每种牌a[i]个,出牌可以出三个一样的{A,A,A},也可以出个顺子{A,A+1,A+2}。求最多能出多少次牌。

首先可以敲定如果我们出了大于等于三次顺子,可以转化为出了三次三个一样的牌。那么不妨就枚举出顺子的次数,0,1,2三次。注意到当前牌的剩余还跟上一张牌的顺子方案有关,那么还得记录上一张牌的顺子状态。
dp[i][x][y]表示第i张牌出了x{i,i+1,i+2},出了y{i1,i,i+1},枚举上一次的状态然后暴力转移,最后牌i剩余下来的牌都拿来出三个一样的。

CF1110E

有N堆石头,每堆是c[i]个,每一次操作可以将某个c[i]变成c[i1]+c[i+1]c[i],问最终是不是能变成数组t

神秘构造题。一次操作相当于交换了差分数组相邻两项...求一下c数组的差分数组、t数组的差分数组,排个序看看是不是一样的就行了

CF1110F

把一棵有根树,根节点是1,按dfs序拍平,每次询问问dfs序在区间[l,r]内的叶子到某节点u的最短距离。1不算叶子

题目明示dfs序拍平。考虑节点u到叶子节点的距离,发现分为两种,一种是u子树内的叶子结点称之为A,一种是u子树外的叶子节点称之为B,点u的父亲是v。发现uA中所有节点的距离等于vA中节点距离减去dist[u][v],而uB中所有节点距离等于vB中节点距离加上dist[u][v]。那么u到各个叶节点距离就可以从v中递推过来。并且注意到A实际上对应了u在dfs序里面一段区间,就可以利用线段树区间修改,快速地从v中递推。
所以先求根节点到叶子节点的距离,存在线段树里,然后dfs一遍树,每dfs到一个节点就在线段树上利用区间修改去递推。时间复杂度O(nlog2n)
顺便测了一下我的线段树模板...

点击查看代码
void dfs(int u, int fa, int &stamp)
{
    dfn[u] = ++stamp, size[u] = 1;
    for (auto [v, w] : G[u])
        if (v != fa)
        {
            dist[v] = dist[u] + w;
            dfs(v, u, stamp);
            size[u] += size[v];
        }
    init[dfn[u]].mn = (size[u] == 1) ? dist[u] : 1e18;
}
 
void dfs(int u, int fa, SegmentTree &Tree)
{
    for (int x : Q[u])
        qry[x].ans = Tree.query(qry[x].l, qry[x].r);
    for (auto [v, w] : G[u])
        if (v != fa)
        {
            Tag delta[2] = {-w, w};
            Tree.apply(dfn[v], dfn[v] + size[v] - 1, delta[0]);
            if (dfn[v] - 1 >= 1)
                Tree.apply(1, dfn[v] - 1, delta[1]);
            if (size[1] >= dfn[v] + size[v])
                Tree.apply(dfn[v] + size[v], size[1], delta[1]);
            dfs(v, u, Tree);
            Tree.apply(dfn[v], dfn[v] + size[v] - 1, delta[1]);
            if (dfn[v] - 1 >= 1)
                Tree.apply(1, dfn[v] - 1, delta[0]);
            if (size[1] >= dfn[v] + size[v])
                Tree.apply(dfn[v] + size[v], size[1], delta[0]);
        }
}

CF1110G

在树上玩井字棋游戏。白先手,有一些点已经涂成白色了,问是白赢还是黑赢。

首先井字棋先手必胜,后手求和。然后还有一些点已经涂成白色了,黑色真招谁惹谁了...
不如先考虑空树的情况吧。
首先如果有个节点的度数大于3,那么白色必胜。
image
白色下1号点,黑色必须去堵一边。白色再下另一边,还至少剩下两边,黑色只能堵一边,所以白色必胜。

接下来就可以考虑节点度数为3、为2的情况。
一个度数为3的节点,如果有两个子节点不是叶子节点。那么白色肯定也必胜。
image
白色先手下1号,接下来无论黑色下在哪,白色长度为2的链都有两个方向可以扩充成长度为3的链

如果所有节点度数为2(除叶子节点),显然是一个平手。

所以重点还是在度数为3、连着两个叶子节点的点。
假设有且仅有两个这样的点。
image
这种情况下,白色下1号点,黑色只能下在6或者2,那么白色往反方向扩充,就会出现一个有两个方向可扩充的长度为2的链,白色必胜。

image
而这种情况下无论怎样都不会出现两个方向可扩充的长度为2的链,因此是平手。
所以问题在于是否在度为3的节点处能出现长度为2的链。当白色下在一个点时,黑色要堵一边。两个度数为3的节点u,v,白色先下在u>v链上离u最近的点,黑色必然要堵住u一边。那么白色选择隔一个位置往v的方向下,黑色必须要堵中间。这样黑白交替着,如果白色能下到节点v前面的那个点,那么白色下一步下在v就又会形成一个长度为2的两个方向可走的链。假设下了k手,那么u,v的距离就是2k+1(算上u,v两个点)。因此u,v距离为奇数,白色胜,否则平手。

接下来考虑有3个度为3的节点u,v,w。假设是wu,v链上。那么dist[u][v]=dist[u][w]+dist[w][v]1,如果dist[u][w]dist[w][v]都是偶数,那么dist[u][v]是奇数,先手必胜;反之则也有距离也奇数的两个度为3的节点,因此先手必胜。
所以当度为3的节点大于3个的时候,先手必胜。
所以总结一下,有度数大于3的节点,先手必胜;度数等于3的节点,只有一个相邻的叶子节点,先手必胜;度数等于3且有两个相邻的叶子节点的点,如果有大于2个,先手必胜,如果有2个且距离为奇数,先手必胜。剩下的情况都是平局。

那么考虑如果预先放置了白色节点怎么做。可以等价于一个空的树,要在一个节点上放白色,那么黑色必然要放在一个位置去堵他,把这个黑色的点刨去就是我们面临的原树了。要构造这样的case,就要用到度数等于3的那种情况。把这个已经涂成了白色的点置空,再连一个度数为3、连着两个叶子节点的点。白色肯定喜欢下这样的节点,而且黑色必然去堵上这个度数为3的节点,连着的两个叶子,白色也不会去考虑它们,对原树不会造成影响。

这样改造完之后就成为空树的井字棋问题了。实现一下就好了。空间开4倍。

点击查看代码
void Main()
{
    int n;
    scanf("%d", &n);
    for (int i = 1; i < n; ++i)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        G[u].push_back(v), G[v].push_back(u);
        deg[u]++, deg[v]++;
    }
    scanf("%s", str + 1);
    int m = n;
    for (int i = 1; i <= n; ++i)
        if (str[i] == 'W')
        {
            G[i].push_back(m + 1), G[m + 1].push_back(i);
            G[m + 1].push_back(m + 2), G[m + 2].push_back(m + 1);
            G[m + 1].push_back(m + 3), G[m + 3].push_back(m + 1);
            deg[i]++, deg[m + 1] += 3, deg[m + 2]++, deg[m + 3]++;
            m += 3;
        }
    std::function<void()> CLEAR = [m]()
    {
        for (int i = 1; i <= m; ++i)
            G[i].clear(), deg[i] = 0;
    };
    for (int i = 1; i <= m; ++i)
        if (deg[i] >= 4)
            return printf("White\n"), CLEAR();
    int vert[3] = {0, 0};
    for (int i = 1, size = 0; i <= m; ++i)
        if (deg[i] >= 3)
        {
            ++size;
            if (size < 3)
                vert[size] = i;
            else
                return printf("White\n"), CLEAR();
        }
    for (int i = 1; i <= 2; ++i)
        if (int u = vert[i])
        {
            int size = 0;
            for (int v : G[u])
                if (deg[v] >= 2)
                    size++;
            if (size >= 2)
                return printf("White\n"), CLEAR();
        }
    std::function<bool(int, int, int, int)> dfs = [&](int u, int fa, int len, int target) -> bool
    {
        if (u == target)
            return len % 2 == 0;
        bool res = false;
        for (int v : G[u])
            if (v != fa)
                res |= dfs(v, u, len + 1, target);
        return res;
    };
    if (vert[1] && vert[2])
        if (dfs(vert[1], 0, 0, vert[2]))
            return printf("White\n"), CLEAR();
    printf("Draw\n"), CLEAR();
}

CF1110H

构造一个长度为n的数字串,使得出现的数字子串的值在[l,r]内的子串最多。n2000,l,r10800

感觉上是个AC自动机,把[l,r]的数字都扔进AC自动机里,然后跑个动态规划。
然后因为状态太多就成为了TLE自动机...
这个TLE自动机里会出现大量的满十叉树。
满十叉树就是所有可能的状态都会出现,那么对于这里面每一个终止节点,它们的权值都是相同的,没有必要去走遍所有节点。当走到一个满十叉树的根的时候,就可以提前算出对于该满十叉树一个基础权值base,后面无论跟什么都会有base的权值,然后跳出这个满十叉树去别的节点里获取更多特殊权值。
那么这些满十叉树都怎么识别呢?就要用到数位DP的思想了。数位DP里面把r按每位决策分成一棵树,其中,脱离了r数位限制的就可以随意取值,也就是满十叉树了。那么就可以把r扔进去,按数位DP决策那棵树构造Trie,每个叶子权值为1,再把l1扔进去,按数位DP决策树构造Trie,权值赋为-1。
接下来考虑dp。
dp[i][u]表示长度为i,当前节点为u的最大权值。一般来说dp形式应该是dp[i][u]=dp[i1][v]+val[u]。然而这里面有满十叉树的情况下,要跳过当前十叉树,就得把未来可能所有的长度都算进去,也就是一个g[u][k],表示在u节点,往前走k步得到的权值。这个g[u][k]就要靠AC自动机构建来求啦。那么dp方程就是dp[i][u]=dp[i1][v]+k=0nig[u][k]. 后面是个前缀和,预处理一下就行了。
最后dfs一个方案就行了。实现时我把g[u][k]里的u按数位DP决策树父节点的决策换了一种表达,这样AC自动机状态就少了一点。

点击查看代码
const int MAXN = 2000, MAXR = 800;
 
struct state
{
    int trans[10], fail;
} acam[MAXR * 2 + 5];
 
#define trans(u, x) acam[u].trans[x]
#define fail(u) acam[u].fail
int cnt = 0, base[MAXR * 2 + 5][MAXN + 5][10];
 
void insert(char *s, int val)
{
    int u = 0, len = strlen(s);
    for (int i = 0; i < len; ++i)
    {
        int x = s[i] - '0';
        for (int j = (u == 0); j < x; ++j)
            base[u][len - i][j] += val;
        if (i == len - 1)
            base[u][len - i][x] += val;
        if (!trans(u, x))
            trans(u, x) = ++cnt;
        u = trans(u, x);
    }
}
 
void build()
{
    std::queue<int> q;
    for (int i = 0; i < 10; ++i)
        if (trans(0, i))
            q.push(trans(0, i));
    while (!q.empty())
    {
        int u = q.front();
        q.pop();
        for (int x = 0; x < 10; ++x)
            if (int &v = trans(u, x))
                q.push(v), fail(v) = trans(fail(u), x);
            else
                v = trans(fail(u), x);
    }
}
 
char L[MAXR + 5], R[MAXR + 5];
int n;
bool vis[MAXN + 5][MAXR * 2 + 5];
ll dp[MAXN + 5][MAXR * 2 + 5];
 
void Main()
{
    scanf("%s%s", L + 1, R + 1);
    scanf("%d", &n);
    int l = strlen(L + 1), r = strlen(R + 1);
    for (int i = l; i >= 1; --i)
    {
        if (L[i] == '0')
            L[i] = '9';
        else
        {
            L[i]--;
            break;
        }
    }
    if (L[1] == '0')
    {
        for (int i = 1; i <= l; ++i)
            L[i] = L[i + 1];
        L[l--] = 0;
    }
    if (l)
        insert(L + 1, -1);
    insert(R + 1, 1);
    build();
    for (int i = std::max(1, l); i < r; ++i)
        for (int j = 1; j < 10; ++j)
            base[0][i][j]++;
    for (int i = 1; i <= cnt; ++i)
        for (int j = 1; j <= n; ++j)
            for (int k = 0; k <= 9; ++k)
                base[i][j][k] += base[fail(i)][j][k];
    for (int i = 0; i <= cnt; ++i)
        for (int j = 2; j <= n; ++j)
            for (int k = 0; k <= 9; ++k)
                base[i][j][k] += base[i][j - 1][k];
    memset(dp, 0xcf, sizeof(dp));
    dp[0][0] = 0;
    for (int i = 0; i < n; ++i)
        for (int u = 0; u <= cnt; ++u)
        {
            if (dp[i][u] < 0)
                continue;
            for (int k = 0; k <= 9; ++k)
            {
                int v = trans(u, k);
                dp[i + 1][v] = std::max(dp[i + 1][v], dp[i][u] + base[u][n - i][k]);
            }
        }
    // for (int i = 0; i <= n; ++i)
    //     for (int u = 0; u <= cnt; ++u)
    //         printf("dp[%d][%d] = %lld\n", i, u, dp[i][u]);
    ll ans = 0;
    for (int u = 0; u <= cnt; ++u)
        if (dp[n][u] > ans)
            ans = dp[n][u];
    printf("%lld\n", ans);
    std::vector<int> best;
    std::function<bool(int, int)> dfs = [&](int i, int u) -> bool
    {
        if (i == n && dp[i][u] == ans)
            return true;
        if (vis[i][u])
            return false;
        vis[i][u] = true;
        for (int k = 0; k <= 9; ++k)
        {
            int v = trans(u, k);
            if (dp[i + 1][v] == dp[i][u] + base[u][n - i][k])
                if (dfs(i + 1, v))
                    return best.push_back(k), true;
        }
        return false;
    };
    dfs(0, 0);
    for (int i = best.size() - 1; i >= 0; --i)
        printf("%d", best[i]);
    printf("\n");
}

GlobalRound2:什么牛马构造题专场……

未完待续
CF1119C

CF1119D

CF1119E

CF1119F

CF1119G

CF1119H
这题先咕了,不会多项式...


CF1148C

CF1148D

CF1148E

CF1148F

CF1148G

CF1148H

posted @   KSYImba  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示