• 牛客小白月赛#27 题解


    思路主要参考自官方题解,以及网友的博客分享,十分感谢~

    A-巨木之森 #树的直径

    题目链接

    暂时咕咕咕,等我考完期末考

    B-乐**对 #记忆化搜索 #线性DP

    题目链接

    分析:

    详解

    比赛时我的思路是逆序贪心,但一直都过不了,赛后发现数据(n=12,a[]={6,6,6,6,6,6,6,1,1,1,1,1}),它的正确分组应该为:(6,6,6,6,6,6,6∣1∣1∣1∣1∣1)

    最保险的方法应该是DP,预先升序排序,然后状态转移方程为:

    [dp[i] = egin{cases} dp[i-1], \ dp[i - a[i]] + 1 (若i-a[i]+1存在) end{cases} ]

    但注意最终答案应该选择(dp[n] = dp[n - a[n]] +1),这样是为了保证最后的人能够进队,比如 3 1 1 3

    #include <cmath>
    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    #include <cstring>
    #include <vector>
    #include <string>
    using namespace std;
    const int MAXN = 1e5 + 5;
    const int INF = 0x3f3f3f3f;
    int dp[MAXN], a[MAXN], n;
    int dfs(int cur){
        if(cur <= 0) //抵达递归基
            return 0;
        else if(dp[cur] > -1)
            return dp[cur];
        else if(cur == n) //已保证前提
            return dp[n] = 1 + dfs(cur - a[cur]); //因为最大值不能自成
        else{
            int mymax = -1;
            if(cur - a[cur] >= 0) //[ (cur - a[cur]), cur]自成一队
                mymax = max(mymax, dfs(cur - a[cur]) + 1);
            mymax = max(mymax, dfs(cur - 1)); //与cur-1属于同一队
            return dp[cur] = mymax;
        }
    }
    int main(){
        scanf("%d", &n);
        for (int i = 1; i <= n; i++){
            scanf("%d", &a[i]);
            dp[i] = -1;
        }
        sort(a + 1, a + n + 1);
        if(a[n] > n)
            printf("-1
    ");
        else
            printf("%d
    ", dfs(n));
        return 0;
    }
    

    C-光玉小镇 #搜索 #状态压缩DP

    暂时咕咕咕,等我考完期末考

    D-巅峰对决 #线段树

    题目链接

    分析:

    本题卡的地方,在于如何用(O(logn))的时间复杂度,判断任意区间的每个值递增连续,判断区间长度 是否 等于最大值与最小值之差 即可。

    #include <cmath>
    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    #include <cstring>
    #include <vector>
    #include <string>
    using namespace std;
    const int MAXN = 1e5 + 5;
    int n, q, mymax[MAXN << 2], mymin[MAXN << 2];
    void PushUp(int rt){
        mymax[rt] = max(mymax[rt << 1], mymax[rt << 1 | 1]);
        mymin[rt] = min(mymin[rt << 1], mymin[rt << 1 | 1]);
    }
    void Build(int lo, int hi, int rt){
        if(lo == hi){
            int val;
            scanf("%d", &val);
            mymax[rt] = mymin[rt] = val;
            return;
        }
        int mid = (lo + hi) >> 1;
        Build(lo, mid, rt << 1);
        Build(mid + 1, hi, rt << 1 | 1);
        PushUp(rt);
    }
    void UpdateForDot(int idx, int X, int lo, int hi, int rt){
        if(lo == hi) {
            mymax[rt] = mymin[rt] = X;
            return;
        }
        int mid = (lo + hi) >> 1;
        if(idx <= mid)
            UpdateForDot(idx, X, lo, mid, rt << 1);
        else
            UpdateForDot(idx, X, mid + 1, hi, rt << 1 | 1);
        PushUp(rt);
    }
    pair<int, int> Query(int L, int R, int lo, int hi, int rt){
        if(L <= lo && hi <= R)
            return{mymax[rt], mymin[rt]};
        int mid = (lo + hi) >> 1;
        pair<int, int> ans{-1e9-5, 1e9+5}, tmp;
        if(L <= mid) {
            tmp = Query(L, R, lo, mid, rt << 1);
            ans.first = max(ans.first, tmp.first);
            ans.second = min(ans.second, tmp.second);
        }
        if(mid < R){
            tmp = Query(L, R, mid + 1, hi, rt << 1 | 1);
            ans.first = max(ans.first, tmp.first);
            ans.second = min(ans.second, tmp.second);
        }
        return ans;
    }
    int main(){
        scanf("%d%d", &n, &q);
        Build(1, n, 1);
        while(q--){
            int opt, lo, hi;
            scanf("%d%d%d", &opt, &lo, &hi);
            if(opt == 1){
                UpdateForDot(lo, hi, 1, n, 1);
            }
            else if(opt == 2){
                pair<int, int> ans = Query(lo, hi, 1, n, 1);
                if(ans.first - ans.second == hi - lo)
                    printf("YES
    ");
                else
                    printf("NO
    ");
            }
        }
    }
    

    E-使徒袭来 #基本不等式

    题目链接

    分析

    知道乘积,用pow函数就能出求和了$frac{a_1+a_2+...+a_n}{n} ge sqrt[n]{a_1a_2...a_n} $

    F-核弹剑仙 #bitset #拓扑排序

    题目链接

    分析

    题目要求偏序关系的计数,因而拓扑排序是本题的核心。如何计数呢?起初我以为直接用cnt[]存储次数,转移该节点时,对其所有上一节点的结果进行累加。然而,对于下图的情况,会将上一条路径重复累加,导致最终结果偏大

    如何不遗漏分叉的路径,又不重复累加重合的路径呢?很明显这样的计数是取并集的过程,我们想到STLbitset支持两个串的01进行按位或运算——即两串取并集。也就是说,我们为每一节点都定义个01串,这个01串的某个位若为1,记录该数位代表的顶点比当前节点先序。转移时,先将上一顶点的01串对当前顶点的01串进行按位或运算,然后再将上一顶点所代表的数位取1。最终,将每个节点的01串中含1的数量输出来即可。

    #include <string>
    #include <cstdio>
    #include <iostream>
    #include <vector>
    #include <string>
    #include <queue>
    #include <cstring>
    #include <bitset>
    using namespace std;
    typedef long long ll;
    const int MAXN = 1e3 + 5;
    queue<int> myque;
    bitset<MAXN> bitstr[MAXN];
    int n, m, H[MAXN], tot = 0, InD[MAXN];
    struct Edge{
        int to, nextNbr;
    } E[MAXN << 1];
    int cnt[MAXN];
    void AddEdge(int u, int v){
        tot++;
        E[tot].to = v; E[tot].nextNbr = H[u]; H[u] = tot;
    }
    int main(){
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++) H[i] = -1;
        for (int i = 1; i <= m; i++){
            int tmpu, tmpv;
            scanf("%d %d", &tmpu, &tmpv);
            AddEdge(tmpu, tmpv);
            InD[tmpv]++;
        }
        for (int i = 1; i <= n; i++)
            if(InD[i] == 0)
                myque.push(i);
        while(!myque.empty()){
            int u = myque.front();
            myque.pop();
            for (int i = H[u]; i >= 0; i = E[i].nextNbr){
                int v = E[i].to;
                InD[v]--;
                if(InD[v] == 0){
                    myque.push(v);
                }
                bitstr[v] |= bitstr[u];
                bitstr[v][u] = 1;
            }
        }
        for (int i = 1; i <= n; i++)
            printf("%d
    ", (int)bitstr[i].count());
    }
    

    H-社*游戏 #二维前缀和 #二分

    题目链接

    分析

    注意题干中,是保证子正方形中所有字母都不超过k

    #include <string>
    #include <cstdio>
    #include <iostream>
    #include <vector>
    #include <string>
    #include <queue>
    #include <cstring>
    #include <bitset>
    using namespace std;
    typedef long long ll;
    const int MAXN = 505;
    int G[MAXN][MAXN], n, m, k;
    int sum[29][MAXN][MAXN];
    int ans[MAXN][MAXN];
    string str[MAXN];
    int Cal(int t, int x2, int y2, int x1, int y1) {
        return sum[t][x2][y2] - sum[t][x2][y1 - 1] - sum[t][x1 - 1][y2] + sum[t][x1 - 1][y1 - 1];
    }
    bool Judge(int x2, int y2, int x1, int y1) {
        for (int i = 1; i <= 27; i++) { //保证所有字母不超过!!!
            if (Cal(i, x2, y2, x1, y1) > k) return 0;
        }
        return 1;
    }
    int main() {
        scanf("%d%d%d", &n, &m, &k);
        for (int i = 1; i <= n; i++) cin >> str[i];
        for (int t = 1; t <= 27; t++) {
            for (int i = 1; i <= n; i++) {
                for (int j = 0; j < m; j++) {
                    int cur = str[i][j] - 'a' + 1;
                    sum[t][i][j + 1] = (t == cur) + sum[t][i - 1][j + 1] + sum[t][i][j - 1 + 1] - sum[t][i - 1][j - 1 + 1];
                }
            }
        }
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                int lo = 1, hi = min(n - i + 1, m - j + 1), len = 1;
                while (lo <= hi) {
                    int mid = (lo + hi) >> 1;
                    if (Judge(i + mid - 1, j + mid - 1, i, j)) {
                        lo = mid + 1;
                        len = mid;
                    }
                    else {
                        hi = mid - 1;
                    }
                }
                ans[i][j] = len;
            }
        }
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                printf("%d%c", ans[i][j], (j == m) ? '
    ' : ' ');
    }
    
    

    I-名作之壁 #双指针 #单调队列

    题目链接

    分析

    观察数据范围,我们不可能将任意区间枚举出来,一定需要观察题目性质。注意到,由于b,c均为正数,a[i]的值随i值递增。我们利用右指针(定义为i)尝试遍历整个数组。若a[lo, hi]的最值之差一旦大于k时,那么a[lo, hi+1]a[lo, hi+2]、…的最值之差一定也满足大于k,因为最小值不变,最大值增大嘛。由此,我们一旦碰到条件满足,不需要继续枚举右端点i。此时,对于右端点为i且满足题意的区间有n-i+1

    在当前右端点i下,我们又逐渐枚举左端点j,直到a[j, i]区间最值之差不满足题意时,我们再将i向前移动。

    如何在(O(1))的时间复杂度内,得出一定区间的两个最值呢?使用两个单调队列,分别维护窗口最大值、最小值,代码中直接使用deque去模拟单调队列。

    #include <string>
    #include <cstdio>
    #include <iostream>
    #include <vector>
    #include <queue>
    #include <deque>
    using namespace std;
    typedef long long ll;
    const int MAXN = 1e7 + 5;
    const int MOD = 1e9;
    int n, k;
    ll a[MAXN], c, b;
    deque<int> mymax, mymin;
    int main() {
        scanf("%d%d", &n, &k);
        scanf("%lld%lld%lld", &a[0], &b, &c);
        for (int i = 1; i <= n; i++){
            a[i] = (a[i - 1] * (ll)b + (ll)c) % MOD;
        }
        ll ans = 0;
        for (int i = 1, j = 1; i <= n; i++){
            //单调队列基本操作
            while(!mymax.empty() && a[mymax.back()] <= a[i]) mymax.pop_back();
            mymax.push_back(i);
            while(!mymin.empty() && a[mymin.back()] >= a[i]) mymin.pop_back();
            mymin.push_back(i);
            while(!mymin.empty() && !mymin.empty() && a[mymax.front()] - a[mymin.front()] > k){ //对于特定的j,它们有相应的(可拓展的)右端点i
                ans += (n - i + 1); 
                j++;
                //保证单调队列维护[j, i]的最值
                if(mymin.front() < j) mymin.pop_front(); 
                if(mymax.front() < j) mymax.pop_front();
            }
        }
        printf("%lld
    ", ans);
    }
    
  • 相关阅读:
    天才绅士少女助手克里斯蒂娜「推柿子」
    sum「莫队」
    simple,跳楼机,[同余系最短路]
    ceoi「chase」
    jzoj5195 数的划分
    lcis
    rectangle
    w
    v·y「状压dp」
    分手是住院「期望dp」
  • 原文地址:https://www.cnblogs.com/J-StrawHat/p/13557875.html
Copyright © 2020-2023  润新知