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. 可以通过打表寻找特殊的性质。
原文地址:https://www.cnblogs.com/yangchengcheng/p/15383158.html