• [CodeForces]Educational Round 52


    幸好我没有打这场,我VP的时候在C题就卡死了,我果然还是太菜了。

    A Vasya and Chocolate

    题意:一个巧克力(c)元,买(a)(b),一共有(n)元,问能买几个巧克力。

    小学数学题。

    B Vasya and Isolated Vertices

    题意:(n)个点(m)条边的无向简单图最多和最少有几个不和任意点联通的点。

    最少很好算,让每条边连接两个独立的点,答案就是(n-2m)

    最多的话,就是让联通的点尽量组成完全图,这样消耗的边数最多,枚举有几个点在完全图中,看是否合法就行。

    C Make It Equal

    题意:有(n)个柱子,第(i)的柱子由(a_i)个方块组成,定义操作为把高度大于(H)的方块去掉,定义一个操作是好的当且仅当去掉的方块不超过(K),求最少要多少次操作才能使所有柱子都一样。

    可以先排一次序,然后枚举(H),看是否可行。但是我写挂了(7)次……

    D Three Pieces

    题意:在一个(N imes N(Nle 10))的棋盘中,每个格子里有标号,要求按标号顺次遍历每个点,你只有车,马,相这三种棋子(国际象棋),每次要么是移动一个棋子,要么是将棋子换成其他种类,求最小的移动数和在此移动数下最小的交换数。

    暴力搜索?

    先预处理出状态((x,y,p))间的最短路,再DP。

    不过这个题比较码农,很难写难调,浪费了我好几个小时,还是要加强写码农题的能力。

    E Side Transmutations

    题意:定义操作为:将字符串(S)的前(k)个字符和后(k)个字符对称交换((kin B)),两个字符串相等当且仅当两个字符串的可以经过若干次操作后一样,求不同的字符串数。

    找规律?

    可以发现,操作两次相当于没操作,所以可以把操作划分为((0,B_1),(B_1,B_2),..,(B_{n-1},B_{n}))这些段,然后就可以组合计数了,设(cnt_i)为长度为(i)的字符串对((x,y))(xle y)的数量,那么,第(i)段能填的字符串数就是(cnt_{len_i})。以及没有被覆盖的部分能填的字符串有(|A|^L)种。(cnt)就是所有的字符串对加上相等的字符串对再除以2。所以,最终答案是:

    [cnt_{b_1}cdot prod_{i=2}^n cnt_{b_i-b_{i-1}}cdot |A|^{L-2b_{n}}, cnt_i = frac{|A|^{2i}+|A|^i}{2} ]

    F Up and Down the Tree

    题意:给定一个有根树,每次可以进行如下操作:若在非叶子节点,走到该节点子树中的一个叶子节点,否则,向上不超过(K)下,问从根出发能到达多少个叶子。

    树上贪心?DP?

    首先计算(drev(v)),表示我们在必须回到点(v)的情况下,在(v)的子树中的收益和能到达的最小的高度。一个简单的树形DP就行了。之后,我们需要计算不必要回到点(v)的收益,这时,考虑每个孩子,容易发现答案是先走其他孩子,然后再找一个孩子走向不归路,这里在实现的时候还有一个补集的思想。

    G Fibonacci Suffix

    题意:定义斐波那契字符串序列为:

    [F_0=mbox{0},F_1 =mbox{1}, F_i = F_{i-2}+F_{i-1} ]

    其中,(+)代表连接两个字符串,求第(n(nle 200))个斐波那契字符串的字典序第(k(kle 10^{18}))小后缀的前(m(mle 200))位。

    字符串神仙题。

    首先,再不考虑时空限制的情况下,我们可以把(F(n))的每个后缀塞到Trie树里暴力跑。

    但是,现实不总是那么美好的,我们需要优化这个思路。注意到(F)是递归的,所以可能不用塞每个后缀就可以表示所有后缀,我们每次可以在(F_{i-2})的后缀树上的每个关键节点后面挂一个(F_{i-1})的后缀树,可以借鉴AC自动机的思想重复利用节点。

    题解的思路是进行(m)次计算,每次确定答案的下一位。

    核心函数build(string& s)有4部分:

    1. 计算snext数组
    2. 利用next数组计算s的Trie图
    3. 将Trie图复制一份作为Fibonacci自动机的底版并预处理辅助自动机
    4. 构建Fibonacci自动机和一个辅助自动机

    Fibonacci自动机F[i][j]表示在一个Trie图上,i节点之后增加(F_j)后转移到的节点。

    辅助自动机Cnt[i][j]表示(S_{i..|S|})(F_j)构成的原始Trie树上的孩子数,初值为Cnt[s.len][s.end]=1,然后在Fibonacci自动机上跑一个小DP算出来Cnt

    主过程如下:(设答案为(M)(k=0)(|M|=m)时退出)

    1. build(M)
    2. 如果(M)就是(F_n)的一个后缀,k--。具体体现为A3[0][n] == M.len,意义是(F_n)的最后(|M|)位是(M)
    3. build(M+'0'),即钦定向'0'走。
    4. 如果M+'0'在Trie树上的孩子不少于(k)个,说明应该向'0'走,否则向'1'走且k-=Cnt[0][n]

    这个题还是放一下代码吧。

    #include <algorithm>
    #include <iostream>
    #include <string>
    using std::string;
    
    typedef long long ll;
    const int N = 210;
    const ll MAX = (ll)1e18;
    
    int Trie[N][2], nxt[N], F[N][N], n, m;
    ll Cnt[N][N], k;
    
    ll add(ll a, ll b) { return std::min(a + b, MAX); }
    
    void build(const string &s) {
        int len = s.length();
        // 1.
        nxt[0] = 0;
        for (int i = 1; i < len; ++i) {
            int j = nxt[i - 1];
            while (j > 0 && s[j] != s[i]) j = nxt[j - 1];
            if (s[j] == s[i]) j++;
            nxt[i] = j;
        }
        // 2.
        for (int i = 0; i <= len; ++i) {
            for (int j = 0; j < 2; ++j) {
                if (i < len && s[i] == '0' + j)
                    Trie[i][j] = i + 1;
                else if (!i)
                    Trie[i][j] = 0;
                else
                    Trie[i][j] = Trie[nxt[i - 1]][j];
            }
        }
        // 3.
        for (int i = 0; i <= len; ++i) {
            for (int j = 0; j < 2; ++j) {
                F[i][j] = Trie[i][j];
                Cnt[i][j] = (Trie[i][j] == len ? 1 : 0);
            }
        }
        // 4.
        for (int i = 2; i <= n; ++i) {
            for (int j = 0; j <= len; ++j) {
                F[j][i] = F[F[j][i - 2]][i - 1];
                Cnt[j][i] = add(Cnt[j][i - 2], Cnt[F[j][i - 2]][i - 1]);
            }
        }
    }
    
    int main() {
        std::cin >> n >> k >> m; // 读入顺序
        string M = "";
        for (int i = 0; i < m; ++i) {
            // 1.
            if (M != "") build(M);
            // 2.
            int x = 0;
            if (M != "" && F[0][n] == i) x = 1;
            if (k == 1 && x == 1) break;  // 如果答案长度不到m的话就提前停止
            k -= x;
            // 3.
            build(M + '0');
            // 4.
            ll ls = Cnt[0][n];
            if (ls >= k) {
                M += '0';
            } else {
                k -= ls;
                M += '1';
            }
        }
        std::cout << M << std::endl;
        return 0;
    }
    

    Note

    这套题不愧是educational round,实在是太有教育意义了233。

    A没啥说的。

    B要注意检查自己的结论是否正确,别想了个错的就开始写,到头来发现自己gg了。

    C这种模拟题noip一定会出的,而且细节肯定巨多,像这次我对答案是否加1处理的不是很好,然后提交了8次才过,noip可没有8次机会啊!

    D一道可怕的图论题,建图码农到爆炸,如果不是有pair这种神器可能码量还会增加,noip的时候还是要活用stl啊。以及码农题一定要仔细仔细再仔细,这次就是一个*写成了+调了30min左右,noip的时候可没有几个30min啊!!

    E数学无处不在,像这种计数问题一定是组合数学题orDP题

    F又是一道树形DP,分成两步求解答案的思路值得借鉴

    G字符串神仙题,大大加深了我对字符串特别是自动机的理解。

  • 相关阅读:
    给字符数组赋值的方法
    linux服务之varnish
    Java实现第八届蓝桥杯承压计算
    Java实现第八届蓝桥杯承压计算
    Java实现第八届蓝桥杯迷宫
    Java实现第八届蓝桥杯纸牌三角形
    Java实现第八届蓝桥杯纸牌三角形
    Java实现第八届蓝桥杯分巧克力
    Java实现第八届蓝桥杯迷宫
    Java实现第八届蓝桥杯分巧克力
  • 原文地址:https://www.cnblogs.com/wyxwyx/p/cfer52.html
Copyright © 2020-2023  润新知