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

目录

比赛地址

得分:\(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}{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\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;
}
posted @ 2021-10-07 07:51  Suzt_ilymtics  阅读(143)  评论(1编辑  收藏  举报