• 2021牛客OI赛前集训营-提高组(第一场)补题


    目录

    比赛地址

    得分:(80 + 20 + 0 + 37.5 = 137.5)
    排名:(51)

    第一题 (n^3) 能骗 (80) 是我没想到的,第四题暴力最多能到 (95) 也是我没想到的。

    A

    本来以为是什么牛逼数论做法

    发现 (P le 2000),考虑把所有 (i o i * j mod p) 连一条边权为 (mid i-j mid) 的边,然后跑最短路。然后 (O(n^3)) 预处理后就可以 (n^2) 遍历一遍求出答案。

    正解是你通过打表发现 (P in [1,2000)),所有的 (ans(i,j)) 都没有超过 (17),然后枚举 (j) 的时候只需要在 ([i-20,i+20]) 之间枚举就好了。此时跑最短路建议 Dij or SPFA。

    /*
    Work by: Suzt_ilymics
    Problem: 不知名屑题
    Knowledge: 垃圾算法
    Time: O(能过)
    */
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #include<queue>
    #define int long long
    #define orz cout<<"lkp AK IOI!"<<endl
    
    using namespace std;
    const int MAXN = 4e6+500;
    const int INF = 1e9+7;
    const int mod = 998244353;
    
    struct edge {
        int to, w, nxt;
    }e[MAXN];
    int head[2020], num_edge = 1;
    
    struct node {
        int id, val;
        bool operator < (const node &b) const { return val > b.val; }
    };
    
    int P, t, ans = 0;
    int dis[2020], Pow[MAXN];
    bool vis[2020];
    
    int read(){
        int s = 0, f = 0;
        char ch = getchar();
        while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
        while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
        return f ? -s : s;
    }
    
    void add_edge(int from, int to, int w) { e[++num_edge] = (edge){to, w, head[from]}, head[from] = num_edge; }
    
    void SPFA(int S) {
        for(int i = 0; i <= P; ++i) dis[i] = 0x3f3f3f3f3f3f3f3f, vis[i] = false;
        priority_queue<node> q;
        q.push((node){S, 0}), dis[S] = 0;
        while(!q.empty()) {
            int u = q.top().id; q.pop();
            if(vis[u]) continue;
            vis[u] = true;
            for(int i = head[u]; i; i = e[i].nxt) {
                int v = e[i].to;
                if(dis[v] > dis[u] + e[i].w) {
                    dis[v] = dis[u] + e[i].w;
                    if(!vis[v]) q.push((node){v, dis[v]});
                }
            }
        }
    }
    
    signed main()
    {
    	P = read(), t = read();
    	Pow[0] = 1;
    	for(int i = 1; i <= P * P; ++i) Pow[i] = Pow[i - 1] * t % mod;
    //	for(int i = 0; i <= P * P; ++i) cout<<Pow[i]<<"
    ";
    	for(int i = 1; i < P; ++i) {
    	    int l = max(1ll, i - 20), r = min(P - 1, i + 20);
    	    for(int j = l; j <= r; ++j) {
    	        int v = i * j % P;
    	        add_edge(i, v, abs(i - j));
            }
        }
        for(int i = 1; i < P; ++i) {
    //        cout<<i<<"
    ";
            SPFA(i);
            for(int j = 1; j < P; ++j) {
                ans = ans + dis[j] * Pow[(i - 1) * (P - 1) + j - 1] % mod;
                ans %= mod;
            }
        }
        printf("%lld
    ", ans);
        return 0;
    }
    

    B

    发现这个操作就相当于选一个点然后把序列分成两段。

    所以考虑区间 DP。

    (f_{l,r}) 表示操作 ([l,r]) 区间的最小代价,(g_{l,r}) 来计数,(m_{l,r}) 表示 ([l,r]) 区间的最大值。有转移方程:

    [f_{l,r} = min_{l le k le r} { f_{l,k-1} + f_{k+1,r} + m_{l,k-1} + m_{k+1,r}} ]

    [g_{l,r} = sum_k g_{l,k} imes g_{k+1,r} imes inom{r-l}{k-l} ]

    时间复杂度 (mathcal O(n^3))

    实际上每次先操作区间最大值是最优的,因此没有必要对区间的所有数都进行枚举,而只枚举区间最大值。会得到常数上的优化,随机数据下跑的很快,时间复杂度 (mathcal O( ext{能过}))

    实际上还有一个发掘性质来进行的优化可以将复杂度降到 (mathcal O((frac{n}{2})^3))

    对于一段区间 ([l,r]),如果存在 ({a_i=a_{i+1}=maxlimits_{i=l}^r(a_i)(iin[l,r))})
    此时 ({i,i+1}) 谁先选择没有关系,因此有 ({g_{l,r}=g_{l,i} imes g_{i+1,r} imes inom{r-l}{i-l}})
    因此当碰到两个最大值连续出现时,直接将整个区间划分为两段,最大值不连续则仍然枚举所有最大值。

    然而我没补这个做法。

    /*
    Work by: Suzt_ilymics
    Problem: 不知名屑题
    Knowledge: 垃圾算法
    Time: O(能过)
    */
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #include<queue>
    #define int long long
    #define orz cout<<"lkp AK IOI!"<<endl
    
    using namespace std;
    const int MAXN = 1e3+50;
    const int INF = 1e18+7;
    const int mod = 998244353;
    
    int n;
    int a[MAXN];
    int f[MAXN][MAXN], g[MAXN][MAXN], Max[MAXN][MAXN];
    int pos[MAXN][MAXN];
    int fac[MAXN], inv[MAXN];
    int nxt[MAXN], pre[MAXN];
    bool vis[MAXN][MAXN];
    
    int read(){
        int s = 0, f = 0;
        char ch = getchar();
        while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
        while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
        return f ? -s : s;
    }
    
    void Init() {
        fac[0] = fac[1] = inv[0] = inv[1] = 1;
        for(int i = 2; i <= 1000; ++i) {
            fac[i] = fac[i - 1] * i % mod;
            inv[i] = (mod - mod / i) * inv[mod % i] % mod;
        }
        for(int i = 2; i <= 1000; ++i) {
            inv[i] = inv[i] * inv[i - 1] % mod;
        }
    }
    
    int C(int n, int m) {
        if(n < m) return 0;
        return fac[n] * inv[m] % mod * inv[n - m] % mod;
    }
    
    void dfs(int l, int r) {
        if(l >= r) {
            f[l][r] = 0, g[l][r] = 1;
            return ;
        }
        if(vis[l][r]) return ;
        vis[l][r] = true;
        f[l][r] = INF, g[l][r] = 0;
        for(int i = pos[l][r]; i <= r; i = nxt[i]) {
            dfs(l, i - 1), dfs(i + 1, r);
    //        f[l][r] = min(f[l][r], f[l][i - 1] + Max[l][i - 1] + f[i + 1][r] + Max[i + 1][r]);
            g[l][r] = (g[l][r] + g[l][i - 1] * g[i + 1][r] % mod * C(r - l, i - l) % mod) % mod;
        }
    //    for(int i = l; i <= r; ++i) {
    //        if(f[l][r] == f[l][i - 1] + Max[l][i - 1] + f[i + 1][r] + Max[i + 1][r]) {
    //        }
    //    }
    }
    
    signed main()
    {
        Init();
    	n = read();
    	for(int i = 1; i <= n; ++i) a[i] = read();
    	for(int i = 1; i <= n + 1; ++i) pre[i] = n + 1;
    	for(int i = n; i >= 1; --i) {
    	    nxt[i] = pre[a[i]];
    	    pre[a[i]] = i;
        }
    	for(int i = 1; i <= n; ++i) {
    //	    Max[i][i] = a[i];
    	    pos[i][i] = i;
    	    for(int j = i + 1; j <= n; ++j) {
    	        if(a[pos[i][j - 1]] < a[j]) {
    //	            Max[i][j] = a[j];
    	            pos[i][j] = j;
                } else {
                    pos[i][j] = pos[i][j - 1];
                }
            }
        }
        dfs(1, n);
        printf("%lld
    ", g[1][n]);
        return 0;
    }
    

    C

    直接粘题解吧,这个方向应该挺好找。

    /*
    Work by: Suzt_ilymics
    Problem: 不知名屑题
    Knowledge: 垃圾算法
    Time: O(能过)
    */
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #include<queue>
    #define int long long
    #define orz cout<<"lkp AK IOI!"<<endl
    
    using namespace std;
    const int MAXN = 1e7+5;
    const int INF = 1e9+7;
    const int mod = 998244353;
    const int Inv2 = 499122177;
    const int Max = 1e7;
    
    char s[MAXN];
    int c;
    int f[MAXN];
    
    int read(){
        int s = 0, f = 0;
        char ch = getchar();
        while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
        while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
        return f ? -s : s;
    }
    
    int Calc(int x, int y, int n) {
        return (x * n % mod + y * n % mod * (n + mod - 1) % mod * Inv2 % mod) % mod;
    }
    
    signed main()
    {
        f[0] = 1;
        for(int i = 1; i <= Max; ++i) f[i] = (f[i - 1] << 1) % mod; 
    	int T = read();
    	while(T--) {
    	    cin >> s + 1; c = read();
    	    int len = strlen(s + 1);
    	    --c;
    	    if(!c) {
    	        int ans = 0;
    	        for(int i = 1; i <= len; ++i) ans = ((ans << 1) + s[i] - '0') % mod;
    	        ans = ans * (ans + 1) % mod * Inv2 % mod;
    	        printf("%lld
    ", ans);
    	        continue;
            }
            if(c & 1) {
                puts("0");
                continue;
            }
            int p = 0;
            while(c % 2 == 0) ++p, c /= 2;
            int ans = 0;
            for(int t = 0; t < len; ++t) {
                int g = max(0ll, t + 1 - p);
                if(t < len - 1) {
                    ans = (ans + f[g] * Calc(f[t], f[g], f[t + 1 - g] - f[t - g] + mod) % mod) % mod;
                } else {
                    int tot = 0;
                    for(int i = 1; i <= len - g; ++i) tot = ((tot << 1) + s[i] - '0') % mod;
                    tot = (tot + 1 - f[t - g] + mod) % mod;
                    ans = (ans + f[g] * Calc(f[t], f[g], tot)) % mod;
                    int lst = (f[t] + (tot + mod - 1) * f[g] % mod) % mod;
                    int l = 0;
                    for(int i = 1; i <= len; ++i) l = ((l << 1) + s[i] - '0') % mod;
                    int r = (lst + f[g] + mod - 1) % mod;
                    ans = (ans - (r - l + mod) % mod * lst % mod + mod) % mod;
                }
            }
            printf("%lld
    ", ans);
        }
        return 0;
    }
    

    D

    我们考虑 ST表 + bitset。

    因为对于每个点可以二分找到不同种类数超过 (k) 的位置。

    然后我们枚举左上角的点利用 ST表 二分就得到了 (le k) 的情况有多少种,在跑一遍 (le k-1) 的情况数相减即可。

    /*
    Work by: Suzt_ilymics
    Problem: 不知名屑题
    Knowledge: 垃圾算法
    Time: O(能过)
    */
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #include<queue>
    #include<bitset>
    #define LL long long
    #define orz cout<<"lkp AK IOI!"<<endl
    
    using namespace std;
    const int MAXN = 1505;
    const int INF = 1e9+7;
    const int mod = 1e9+7;
    
    int n, m, K;
    int Log[MAXN];
    bitset<100> f[11][MAXN][MAXN];
    
    int read(){
        int s = 0, f = 0;
        char ch = getchar();
        while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
        while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
        return f ? -s : s;
    }
    
    void ST() {
        for(int k = 1, M = max(n, m); (1 << k) <= M; ++k) {
            for(int i = 1; i + (1 << k) - 1 <= n; ++i) {
                for(int j = 1; j + (1 << k) - 1 <= m; ++j) {
                    f[k][i][j] = f[k - 1][i][j] | f[k - 1][i + (1 << k - 1)][j] | 
                    f[k - 1][i][j + (1 << k - 1)] | f[k - 1][i + (1 << k - 1)][j + (1 << k - 1)];
                }
            }
        }
    }
    
    int Query(int sx, int sy, int ex, int ey) {
        int k = Log[ex - sx + 1];
        return (f[k][sx][sy] | f[k][ex - (1 << k) + 1][sy] | 
        f[k][sx][ey - (1 << k) + 1] | f[k][ex - (1 << k) + 1][ey - (1 << k) + 1]).count();
    }
    
    LL Calc(int Mid) {
        if(!Mid) return 0;
        LL ans = 0;
        for(int i = 1; i <= n; ++i) {
            for(int j = 1; j <= m; ++j) {
                int l = 1, r = min(n - i + 1, m - j + 1), res = 0;
                while(l <= r) {
                    int mid = (l + r) >> 1;
                    if(Query(i, j, i + mid - 1, j + mid - 1) <= Mid) {
                        res = mid, l = mid + 1;
                    } else {
                        r = mid - 1;
                    }
                }
    //            cout<<"ck: "<<i<<" "<<j<<" "<<l<<"
    ";
                ans += res;
            }
        }
        return ans;
    }
    
    signed main()
    {
    	for(int i = 2; i <= 1500; ++i) Log[i] = Log[i >> 1] + 1;
    	n = read(), m = read(), K = read();
    	for(int i = 1; i <= n; ++i) {
    	    for(int j = 1; j <= m; ++j) {
    	        f[0][i][j][read() - 1] = true;
            }
        }
        ST();
        printf("%lld
    ", Calc(K) - Calc(K - 1));
        return 0;
    }
    
  • 相关阅读:
    导出报ora-31634、ora-31664
    A significant part of sql server process memory has been paged out
    解决ora-02429:无法用于删除强制唯一/主键的索引
    更改数据库表中有数据的字段类型NUMERIC(18,2)为NUMERIC(18,6)
    GHOST CLEANUP Process
    oracle查看执行计划explain plan FOR
    ORA-01502: 索引或这类索引的分区处于不可用状态
    mysql 游标循环,嵌套游标循环
    MSSQL FOR XML PATH ('')用法
    Mysql CHARINDEX用法
  • 原文地址:https://www.cnblogs.com/Silymtics/p/test-NK2021TG1.html
Copyright © 2020-2023  润新知