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


    前言

    我只想说,我都氪金考试了,能不能不掉分。

    每天一遍: YCC 是 SB。

    还有,孙土蛋同学的代码是真的上头。

    T1

    40pts

    对于每个(leq p - 1) 的数,在乘以每个 (z(leq q))的情况下,得到的数当做节点,以花费的代价作为边权,建边跑弗洛伊德算法。

    /*
    Date:2021.10.4
    Source:牛客 模拟赛 
    knowledge:  
    */
    #include <iostream>
    #include <cstdio>
    #include <map>
    #include <cstring>
    #define orz cout << "AK IOI" << "
    "
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    #define int long long 
    
    using namespace std;
    const int mod = 998244353;
    const int maxn = 2010; 
    
    inline int read()
    {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    inline void print(int X)
    {
    	if(X < 0) X = ~(X - 1), putchar('-');
    	if(X > 9) print(X / 10);
    	putchar(X % 10 ^ '0');
    }
    int dis[maxn][maxn], p, t, ans;
    int Abs(int a) {return a > 0 ? a : -a;}
    void floyd()
    {
    	for(int i = 0; i < p; i++) dis[i][i] = 0;
    	for(int k = 0; k < p; k++)
    		for(int i = 0; i < p; i++)
    			for(int j = 0; j < p; j++)
    				dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
    }
    int pow(int a, int b)
    {
    	int ret = 1;
    	while(b)
    	{
    		if(b & 1) ret = ret * a % mod;
    		b >>= 1;
    		a = (a * a) % mod;
    	}
    	return ret % mod;
    }
    signed main()
    {
    	p = read(), t = read();
    	memset(dis, 0x3f, sizeof dis);
    	for(int i = 1; i < p; i++)
    	{
    		for(int j = 1; j < p; j++)
    		{
    			int v = i * j % p;
    			dis[i][v] = min(dis[i][v], Abs(i - j));
    		}
    	}
    	floyd();
    	for(int i = 1; i < p; i++)
    		for(int j = 1; j < p; j++)
    			ans = (ans + dis[i][j] * pow(t, (i - 1) * (p - 1) + j - 1) % mod) % mod;
    	print(ans);
    	return 0;
    }
    

    由于评测机跑的太快于是就有了70分的好成绩。

    100 pts

    对于每一个 (i) 为起点, 跑 $ P - 1$ 次 dij,直接跑的复杂度是 (O(p ^3))​。

    考虑如何优化,通过打表可以发现 (ans(i, j)) 都不会大于 (17),那么用点 (u) 去更新的时候只需要转移到

    ([max(1,u − lim),min(P−1,u + lim)])​ 范围内的点,这样边数就被优化到了(O(P lim))​。

    memset 改成了 for 循环后,一份 TLE 的代码就 AC 了。

    /*
    Date:2021.10.4
    Source:牛客 模拟赛 
    knowledge:  
    */
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    #include <cmath>
    #define orz cout << "AK IOI" << "
    "
    #define int long long 
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    #define Abs(a) ((a) > 0 ? (a) : (-a)) 
    
    using namespace std;
    const int mod = 998244353;
    const int maxn = 4e6 + 10; 
    
    inline int read()
    {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    inline void print(int X)
    {
    	if(X < 0) X = ~(X - 1), putchar('-');
    	if(X > 9) print(X / 10);
    	putchar(X % 10 ^ '0');
    }
    int p, t, ans, power[maxn], dis[maxn];
    bool vis[maxn];
    struct node{
    	int u, v, w, nxt;
    }e[maxn << 1];
    int js, head[maxn];
    void add(int u, int v, int w)
    {
    	e[++js] = (node){u, v, w, head[u]};
    	head[u] = js;
    }
    void init()
    {
    	power[0] = 1;
    	for(int i = 1; i <= p * p; i++) power[i] = power[i - 1] * t % mod;
    }
    struct edge{
    	int now, w;
    	bool operator < (const edge &x) const
    	{
    		return w > x.w;
    	}
    };
    
    void dij(int s)
    {
    	priority_queue<edge> q;
    	for(int i = 0; i <= p; i++) dis[i] = 0x3f3f3f3f, vis[i] = 0;
    	dis[s] = 0;
    	q.push((edge){s, 0});
    	while(!q.empty())
    	{
    		int u = q.top().now;
    		q.pop();
    		if(vis[u]) continue;
    		vis[u] = 1;
    		for(int i = head[u]; i; i = e[i].nxt)
    		{
    			int v = e[i].v;
    			if(vis[v]) continue;
    			if(dis[v] > dis[u] + e[i].w)
    			{
    				dis[v] = dis[u] + e[i].w;
    				q.push((edge){v, dis[v]});
    			}
    		}
    	}
    }
    signed main()
    {
    	p = read(), t = read();
    	init();
    	for(int i = 1; i <= p - 1; i++)
    	{
    		int l = Max(1ll, i - 18), r = Min(p - 1, i + 18);//优化 
    		for(int j = l; j <= r; j++)
    		{
    			int v = i * j % p;
    			add(i, v, abs(i - j));
    		} 
    	}
    	for(int i = 1; i <= p - 1; i++)
    	{
    		dij(i);
    		for(int j = 1; j <= p - 1; j++)
    			ans = (ans + dis[j] * power[(i - 1) * (p - 1) + j - 1] % mod) % mod;
    	}
    	printf("%lld
    ", ans);
    	return 0;
    }
    

    T2

    10 pts

    枚举全排列,计算代价,对比代价,得出答案,时间复杂度 (O(n!))​。

    暴力写挂了,我是真nb。

    /*
    Date:
    Source:
    knowledge:
    */
    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #define orz cout << "AK IOI" << "
    "
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    
    using namespace std;
    const int maxn = 2010;
    const int mod = 998244353;
    
    inline int read()
    {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    inline void print(int X)
    {
    	if(X < 0) X = ~(X - 1), putchar('-');
    	if(X > 9) print(X / 10);
    	putchar(X % 10 ^ '0');
    }
    int n, minn = 0x3f3f3f3f, a[maxn], b[maxn], id[maxn], map[8000000];
    int getl(int x)
    {
    	for(int i = x - 1; i >= 1; i--) if(a[i] == 0) return i;
    	return 0;
    }
    int getr(int x)
    {
    	for(int i = x + 1; i <= n + 1; i++) if(a[i] == 0) return i;
    	return n + 1;
    }
    int main()
    {
    	//freopen(".in","r",stdin);
        //freopen(".out","w",stdout);
    	n = read();
    	for(int i = 1; i <= n; i++) a[i] = read(), b[i] = a[i], id[i] = i;
    	
    	while(next_permutation(id + 1, id + n + 1))
        {
        	for(int i = 1; i <= n; i++) a[i] = b[i];
        	int Ans = 0;
        	for(int x = 1; x <= n; x++)
        	{
        		a[id[x]] = 0;
        		int flag = -0x3f3f3f3f;
        		int l = getl(x);
        		for(int j = l; j < x; j++) flag = Max(flag, a[j]);
    			Ans += flag;
    			flag = -0x3f3f3f3f;
    			int r = getr(x);
    			for(int j = x + 1; j <= r; j++) flag = Max(flag, a[j]);
    			Ans += flag;
    		}
    		map[Ans]++;
    		minn = Min(minn, Ans);
    	}
    	printf("%d", minn);
    	//fclose(stdin);
    	//fclose(stdout);
    	return 0;
    }
    

    20pts

    状态压缩,计算代价,对比代价,得出答案。

    50pts

    在对第 (x)​​ 个进行操作后,(x)​​​​ 两边的区间就可以独立了,这两段不会对另一段产生影响,可以进行区间 dp。

    (f_{l, r})​​ 为对 ([l, r])​​ 区间操作产生的最小贡献, (g_{l, r})​​​ 为最小代价的操作序列数量,枚举​ (m)​​, 若 (f_{l, r})​​ 由 (f_{l, m} f_{m + 1, r})​​​​ 转移而来,则(g_{l,r}+=g_{l,m} imes g_{m+1,r} imes C(r-l,m-l))​​​,时间复杂度 (O(n^3))​​。

    70pts

    实际上每次先操作区间最大值是最优的,因此没有必要对区间的所有数都进行枚举,而只枚举区间最大值,时间复杂度 (O(n^3))

    100pts

    对于一段区间 ([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 C(r-l+1,i-l+1))​​。
    因此当碰到两个最大值连续出现时,直接将整个区间划分为两段,最大值不连续则仍然枚举所有最大值。
    从而时间复杂度降至(O((frac{n}{2})^3))​​。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #include<queue>
    #define int long long
    
    using namespace std;
    const int MAXN = 1e3+50;
    const int INF = 1e18+7;
    const int mod = 998244353;
    
    int n;
    int a[MAXN], pos[MAXN][MAXN], fac[MAXN], inv[MAXN], nxt[MAXN], pre[MAXN];
    int f[MAXN][MAXN], g[MAXN][MAXN], Max[MAXN][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);
            g[l][r] = (g[l][r] + g[l][i - 1] * g[i + 1][r] % mod * C(r - l, i - l) % mod) % mod;
        }
    
    }
    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) 
    	{
    	    pos[i][i] = i;
    	    for(int j = i + 1; j <= n; ++j) 
    		{
    	        if(a[pos[i][j - 1]] < a[j]) pos[i][j] = j;
                else  pos[i][j] = pos[i][j - 1];
            }
        }
        dfs(1, n);
        printf("%lld
    ", g[1][n]);
        return 0;
    }
    

    T3

    20pts

    按题意模拟一下。

    100pts

    感觉算是一个数学题了吧,真的懒得写了。

    粘一下隔壁大佬的代码吧

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #include<queue>
    #define int long long
    #define orz cout<<"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;
    }
    
    

    T4

    20pts

    按题意模拟,但是数据构造时出了点问题,就可以有95分的好成绩,但是我的只有 72.5 分。

    /*
    Date:
    Source:
    knowledge:
    */
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #define orz cout << "AK IOI" << "
    "
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    
    using namespace std;
    const int maxn = 1510;
    
    inline int read()
    {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    inline void print(int X)
    {
    	if(X < 0) X = ~(X - 1), putchar('-');
    	if(X > 9) print(X / 10);
    	putchar(X % 10 ^ '0');
    }
    int n, m, k, ans, map[maxn][maxn], vis[110], js;
    void work(int x, int y, int kk)
    {
    	js = 0;
    	memset(vis, 0, sizeof vis);
    	for(int i = x; i <= x + kk - 1; i++)
    		for(int j = y; j <= y + kk - 1; j++)
    		{
    			if(!vis[map[i][j]]) {vis[map[i][j]] = 1; js++;}
    			if(js > k) break;
    		}	
    	if(js == k) ans++;
    }
    int main()
    {
    	//freopen(".in","r",stdin);
        //freopen(".out","w",stdout);
    	n = read(), m = read(), k = read();
    	for(int i = 1; i <= n; i++)
    		for(int j = 1; j <= m; j++) map[i][j] = read();
    	for(int i = 1; i <= n; i++)
    		for(int j = 1; j <= m; j++)
    			for(int x = 1; x <= min(n - i + 1, m - j + 1); x++)
    			{
    				work(i, j, x);
    				if(js > k) break;
    			}
    	print(ans);		
    	//fclose(stdin);
    	//fclose(stdout);
    	return 0;
    }
    
    
    

    40pts

    在暴力枚举的基础上,对数字进行压缩, 把 (a_i) 压缩成 (2 ^ {a_i})​, 每次边长扩展 1 就或上一个数。时间复杂度 (O(n ^3))

    100pts

    在边长扩展的过程中,不同数量的数只会增加不会减少,具有单调性,可以二分找到不同数量 (< k) 的最大边长 (x) 和不同数量 $ leq k$ 的最大边长 (y), (y - x) 即为固定一个左上角符合条件的正方形的数量。这样需要处理 (O(n^2log n))​ 个查询,每个查询一个正方形内不同颜色的数量,由于是正方形,故可以采用二维st 表来做。

    /*
    Date:
    Source:
    konwledge:
    */
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<bitset>
    #define LL long long
    #define orz cout << "AK IOI" << "
    "
    #define Min(x, y) ((x) < (y) ? (x) : (y))
    #define Max(x, y) ((x) > (y) ? (x) : (y))
    
    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];
    
    inline int read()
    {
    	int x = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    	return x * f;
    }
    inline void print(int X)
    {
    	if(X < 0) X = ~(X - 1), putchar('-');
    	if(X > 9) print(X / 10);
    	putchar(X % 10 ^ '0');
    }
    
    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;
    			}     
    			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;
    }
    

    总结

    1. 交代码前一定再次测样例,避免 sb 错误。
    2. 对于 操作可以进行无数次 思考建图操作建图。
    3. 可以通过打表寻找特殊的性质。
  • 相关阅读:
    html,css,js实现的一个钟表
    CSS子元素在父元素中水平垂直居中的几种方法
    JavaScript去除字符串中的空格
    JavaScript判断数据类型的4中方法
    数据库的创建和删除
    MySql数据类型及对应存储空间
    配置hibernate常见问题
    java 获取系统变量(环境变量和设置变量)
    IDEA创建Web项目(基于Maven多模块)
    spring boot快速搭建
  • 原文地址:https://www.cnblogs.com/yangchengcheng/p/15383158.html
Copyright © 2020-2023  润新知