zoukankan      html  css  js  c++  java
  • [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字符串神仙题,大大加深了我对字符串特别是自动机的理解。

  • 相关阅读:
    Hdu 5396 Expression (区间Dp)
    Lightoj 1174
    codeforces 570 D. Tree Requests (dfs)
    codeforces 570 E. Pig and Palindromes (DP)
    Hdu 5385 The path
    Hdu 5384 Danganronpa (AC自动机模板)
    Hdu 5372 Segment Game (树状数组)
    Hdu 5379 Mahjong tree (dfs + 组合数)
    Hdu 5371 Hotaru's problem (manacher+枚举)
    Face The Right Way---hdu3276(开关问题)
  • 原文地址:https://www.cnblogs.com/wyxwyx/p/cfer52.html
Copyright © 2011-2022 走看看