Codeforces Global Round 1~3

CF1110C

回答\(q\)个关于\(a\)的询问,每次询问求:\(f(a)=max_{0<b<a} gcd(a \bigoplus b,a\&b)\)\(a<2^{25}\)

假设\(a\)\(k\)位,对\(a\)取个反,即\(b=\overline a\)就会发现\(gcd(a \bigoplus b,a \& b)={2^k}-1\)达到最大。注意这里的取反指的是在a的位数以内取反。但是如果\(a={2^k}-1\),因为\(b>0\)所以就不能这么求。不妨直接暴力对每个\(k\),求解\(f(2^k-1)\),打个表出来。时间复杂度\(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\)\(\{i-1,i,i+1\}\),枚举上一次的状态然后暴力转移,最后牌\(i\)剩余下来的牌都拿来出三个一样的。

CF1110E

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

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

CF1110F

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

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

点击查看代码
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\)。假设是\(w\)\(u,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]\)内的子串最多。\(n \le 2000, l,r \le 10^{800}\)

感觉上是个AC自动机,把\([l,r]\)的数字都扔进AC自动机里,然后跑个动态规划。
然后因为状态太多就成为了TLE自动机...
这个TLE自动机里会出现大量的满十叉树。
满十叉树就是所有可能的状态都会出现,那么对于这里面每一个终止节点,它们的权值都是相同的,没有必要去走遍所有节点。当走到一个满十叉树的根的时候,就可以提前算出对于该满十叉树一个基础权值base,后面无论跟什么都会有base的权值,然后跳出这个满十叉树去别的节点里获取更多特殊权值。
那么这些满十叉树都怎么识别呢?就要用到数位DP的思想了。数位DP里面把\(r\)按每位决策分成一棵树,其中,脱离了\(r\)数位限制的就可以随意取值,也就是满十叉树了。那么就可以把r扔进去,按数位DP决策那棵树构造Trie,每个叶子权值为1,再把\(l-1\)扔进去,按数位DP决策树构造Trie,权值赋为-1。
接下来考虑dp。
\(dp[i][u]\)表示长度为i,当前节点为u的最大权值。一般来说dp形式应该是\(dp[i][u] = dp[i-1][v] + val[u]\)。然而这里面有满十叉树的情况下,要跳过当前十叉树,就得把未来可能所有的长度都算进去,也就是一个\(g[u][k]\),表示在u节点,往前走k步得到的权值。这个\(g[u][k]\)就要靠AC自动机构建来求啦。那么dp方程就是\(dp[i][u] = dp[i-1][v]+\sum_{k=0}^{n-i}g[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 @ 2022-12-05 17:19  KSYImba  阅读(37)  评论(0)    收藏  举报