1. 程式人生 > 其它 >Codeforces Global Round 1~3

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,那麼白色必勝。

白色下1號點,黑色必須去堵一邊。白色再下另一邊,還至少剩下兩邊,黑色只能堵一邊,所以白色必勝。

接下來就可以考慮節點度數為3、為2的情況。
一個度數為3的節點,如果有兩個子節點不是葉子節點。那麼白色肯定也必勝。

白色先手下1號,接下來無論黑色下在哪,白色長度為2的鏈都有兩個方向可以擴充成長度為3的鏈

如果所有節點度數為2(除葉子節點),顯然是一個平手。

所以重點還是在度數為3、連著兩個葉子節點的點。
假設有且僅有兩個這樣的點。

這種情況下,白色下1號點,黑色只能下在6或者2,那麼白色往反方向擴充,就會出現一個有兩個方向可擴充的長度為2的鏈,白色必勝。


而這種情況下無論怎樣都不會出現兩個方向可擴充的長度為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