ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2021-10-07 08:00:23  阅读:132  来源: 互联网

标签:ch OI int long 集训营 read 补题 include define


目录

比赛地址

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

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

A

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

发现 \(P \le 2000\),考虑把所有 \(i \to i * j \bmod 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]<<"\n";
	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<<"\n";
        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\n", 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} \times g_{k+1,r} \times \binom{r-l}{k-l} \]

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

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

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

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

然而我没补这个做法。

/*
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\n", 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\n", 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\n", 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<<"\n";
            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\n", Calc(K) - Calc(K - 1));
    return 0;
}

标签:ch,OI,int,long,集训营,read,补题,include,define
来源: https://www.cnblogs.com/Silymtics/p/test-NK2021TG1.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有