zoukankan      html  css  js  c++  java
  • NOIP2016 “西湖边超萌小松鼠” 模拟赛


    总的来说,这套题的难度比较接近近些年来Day1的真实难度,认为非常值得一打

    GotoAndPlay

    题目大意

    询问这个图上是否存在一种跳法,能跳到这个图上的每一个点

    题目解析

    犯了个低级错误,双向边忘记*2,最后两个点RE了
    因为题目告知是“跳两次”,所以很容易想到将这个图分成“奇数点”和“偶数点”,那么所有“奇点“和所有“偶点”是能够独立相互到达的。
    换句话来说,只要有一个点既属于“偶点“又属于”奇点”,那整个图都能相互到达了(这就是二分图)

    因此我们先在原图做一遍DFS/BFS,标记初始标号
    如果有一个点我们要对他进行标记两种不同标号,那整个图就完全可以到达了
    如果假的一条边,连通了两个标记不一样的点,那“奇点”就可以到达“偶点”了,及那整个图就完全可以到达了

    #include <cstdio>
    #include <iostream>
    #include <cstring>
    using namespace std;
    
    #define qword long long
    #define seta(x, y) memset(x, y, sizeof(x))
    #define forn(i, n) for (int i = 1; i <= (int)(n); ++ i)
    #define rep(i, s, t) for (int i = (int)(s); i <= (int)(t); ++ i)
    
    const int maxn = 4e5 + 10;
    
    inline void File() {
        const string task = "GotoAndPlay";
        freopen((task + ".in").data(), "r", stdin);
        freopen((task + ".out").data(), "w", stdout);
    }
    
    int n, m;
    int to[maxn], head[maxn], next[maxn];
    int color[maxn];
    bool flag = false;
    
    int cnt = 0;
    
    inline void addEdge(int x, int y) {
        to[++cnt] = y;
        next[cnt] = head[x];
        head[x] = cnt;
    }
    
    inline void dfs(int x, int c) {
    	if (color[x] != -1 && color[x] != c) {flag = 1; return;}
        color[x] = c;
        for (int i = head[x]; i; i = next[i]) {
            if (color[to[i]] == -1) dfs(to[i], (~c) & 1);
            //else if (color[to[i]] == color[i]) flag = true;
        }
    }
    
    inline void query(int x) {
        int a, b;
        scanf("%d %d", &a, &b);
        if (color[a] == color[b] || flag) {
            printf("Yes
    "); return;
        }
        printf("No
    "); return;
    }
    
    int main() {
        //File();
        scanf("%d %d", &n, &m);
        int x, y;
        forn(i, m) {
            scanf("%d %d", &x, &y);
            addEdge(x, y); addEdge(y, x);
        }
        forn(i, n) color[i] = -1; // Init
        dfs(1, 0);
        int p;
        scanf("%d", &p);
        forn(i, p) query(p);
        return 0;
    }
    

    StopAllSounds

    题目分析

    一开始没有条理的乱搞,浪费了5分钟。。。

    因为横只为2,所以我们可以分析出当前这只积木落下后最后的状态。
    因为发现当前积木落下之后,下面所有的积木构成已经对后续没有影响,这满足了无后效性这一DP的基本条件
    于是我们做一个dp,用 $ f[i][j] $ 表示现在最高的积木在 $ i $ 这个高度,最后一个状态是 $ j $ (这里我用1-6表示6中最终状态)
    最后进行转移就行了

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    #include <cstring>
    using namespace std;
    
    #define qword long long
    #define seta(x, y) memset(x, y, sizeof(x))
    #define forn(i, n) for (int i = 1; i <= (int)(n); ++ i)
    #define rep(i, s, t) for (int i = (int)(s); i <= (int)(t); ++ i)
    
    inline void File() {
        const string task = "StopAllSounds";
        freopen((task + ".in").data(), "r", stdin);
        freopen((task + ".out").data(), "w", stdout);
    }
    
    const int mod = 1e9 + 7;
    
    int n;
    long long dp[1001000][7];
    
    int main() {
        File();
        scanf("%d",&n);
        memset(dp, 0, sizeof(dp));
        forn(i, 6) dp[3][i] = 1;
        dp[3][4] = 0; dp[2][4] = 1;
        rep(i, 4, n) {
            dp[i][1] = (dp[i-3][1] + dp[i-2][2] + dp[i-2][3] + dp[i-3][4] + dp[i-2][5] + dp[i-3][6]) % mod;
            dp[i][2] = (dp[i-2][1] + dp[i-3][2] + dp[i-3][3] + dp[i-3][4] + dp[i-3][5] + dp[i-3][6]) % mod;
            dp[i][3] = (dp[i-3][1] + dp[i-2][2] + dp[i-2][3] + dp[i-3][4] + dp[i-2][5] + dp[i-3][6]) % mod;
            dp[i][4] = (dp[i-2][1] + dp[i-2][2] + dp[i-2][3] + dp[i-2][4] + dp[i-2][5] + dp[i-2][6]) % mod;
            dp[i][5] = (dp[i-3][1] + dp[i-3][2] + dp[i-3][3] + dp[i-3][4] + dp[i-3][5] + dp[i-3][6]) % mod;
            dp[i][6] = (dp[i-3][1] + dp[i-2][2] + dp[i-2][3] + dp[i-3][4] + dp[i-1][5] + dp[i-3][6]) % mod;
        }
        qword ans = 0;
        rep(i, 2, n) forn(j, 6)
                ans += dp[i][j] % mod;
        printf("%lld", (ans + 1) % mod);
        return 0;
    }
    

    UpdateAfterEvent

    题目分析

    这道题是这里面唯一一个达到了提高+/省选难度的题

    首先思考如何将问题所求:期望的松鼠对数 转化为我们较为熟悉和擅长入手的东西。
    大家能够发现,尽管可能大家最先想到的计算松鼠对数的方法,是根据树上的松鼠总数 $ x $,通过 $ x * (x - 1) / 2$ 来得到。但不妨考虑一下一个更一般的方法:
    枚举一只松鼠,再枚举另一只松鼠,如果它们在同一棵树上则答案加一。

    从这个方法中能够得到的启示是:松鼠对数这个量实际上是相对独立的,即与这两只松鼠之外的量并无直接的关系。
    这样就避免了我们陷入一味考虑如何计算“松鼠期望总数”的错误方向了。

    相似地,当我们计算期望时,也存在这样一个方法:
    枚举一只松鼠 $ A $ ,再枚举另一只松鼠 $ B $ ,考虑它们同时存在在树i上的情况。
    假设松鼠(A)在树i上的概率为 $ P(A, i) $ , 松鼠B在树i上的概率为 $ P(B, i) $
    则它们同时存在于树i上的概率为 $ P(A, i) * P(B, i) $
    而这一事件构成了“1对结束时在同一棵树上的松鼠”
    因此对答案的贡献是 $ P(A, i) * P(B, i) * 1 $
    我们只需要对于每一对松鼠枚举一下树 $ i $ ,然后对这些东西求和计入答案就可以了。

    考虑如何计算 $ P(A,i) $ 。我们不妨假设 $ f(i, j) $ 为一只松鼠从点i出发,在点j停下的概率。
    当 $ T = 0(时,) f [i][i] $ 均为1.0,每过一个单位时间时,考虑 $ f[i][j] $ 即松鼠当前在 $ j $ 时的概率
    根据题中的描述向 $ j $的相邻点转移。
    这样能够得到所有 $ T le 30 $ 的分数

    观察可知,每次的转移事实上都是一样的。于是我们可以使用矩阵乘法来优化这个转移过程。
    时间复杂度 $ O(n^3logT) $ ,这部分有 $ 30 $ 的数据。

    30%的部分分是给 $ n ^ 2 $ 计算期望的方法。直接枚举两个位置和终点的位置计算是 $ n ^ 3 $ 的,但是我们在枚举两个位置的时候,可以维护一下前缀和,累计的时候直接计算与前缀和的乘积。就可以将计算期望的过程优化到 $ n ^ 2 $ 了。

  • 相关阅读:
    jQuery Mobile动态刷新页面样式
    IE10下阿里旺旺无法快速登录解决办法
    JS复制内容到剪贴板: 兼容IE、Firefox、Chrome、Safari所有浏览器【转】
    python sftp ftp 造轮子,实现多个方法
    synergy ubuntu18.04 windows10
    爬虫之js破解 非常详细
    scrapy的useragent与代理ip
    Xpath的string(.)用法
    selenium cookie 登录
    scrapy爬取迅雷电影天堂最新电影ed2k
  • 原文地址:https://www.cnblogs.com/Alessandro/p/9652374.html
Copyright © 2011-2022 走看看