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

前言:#

pts:25 + 20 + 0 + 30 = 75

最惨淡的一场。

出现的主要问题是没分配好时间,导致 T3 的暴力都没来急写。

出现的最大问题是脑子掉线。。。。

今后考试策略:

拿到题先读完所有的题,然后把所有能写的暴力全敲完之后再来想正解。

考试一定要带脑子 !!!

T1 牛表#

题目描述

题面

给出三个整数 x,y,P(1x,y<P), P 为素数,可以重复对 x 执行如下操作:
选择一个整数 z[1,P1] ,花费 |xz| 的牛币,使得 x=x×zmodP

最小需要花费多少牛币才能使得 x=y ?

ans(i,j) 为当 x=i,y=j 时的答案,为了减少输出,你需要输出

i=1P1j=1P1ans(i,j)t(i1)(P1)+j1mod998244353

数据范围

2P2000

考试的时候真没忘建图那方面想,然后就草草打了个暴力走人了。

solution

对于每一个操作,相当于从 xxzmodP 建一条边权为 |xz| 的边,然后到 y 跑一个最短路就好了。

30pts

P500 直接 Floyed 就好了。

但由于数据水或者牛客神机太快了,这玩意赛时能过 85 ,现在加强了数据,但是还能过 75 分,就挺反人类行为的 = =

code

/*
work by:Ariel_
Sorce:
Knowledge:
Time:
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
#define rg register
#define int long long
using namespace std;
const int mod = 998244353;
const int MAXN = 2020, MAXX = 4e6 + 5;
int read(){
    int x = 0,f = 1; char c = getchar();
    while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
    return x*f;
}
int dis[MAXN][MAXN], Pow[MAXX], p, t, ans;
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));
	}
  }
  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]);
	  }
    }
  }
  Pow[0] = 1;
  for (int i = 1; i <= p * p; i++) Pow[i] = Pow[i - 1] * t % mod;
  for (int i =1; i < p; i++) {
  	for (int j = 1; j < p; j++) {
  	   ans = ans + dis[i][j] * Pow[(i - 1) * (p - 1) + j - 1] % mod;
	   ans %= mod;	
	}
  }
  cout<<ans;
  return 0;
}

T2 牛牛和数组操作#

题面

题目描述

n+2 个整数 a0,a1,,an,an+1

你需要做确切地 n 次操作,每次操作以下形式:

选择一个整数 x 满足 ax0, 使得 ax, 令 l=maxi<x,ai=0(i),r=mini>x,ai=0(i)

此次操作的操作为 max{al,al+1,,ax1}+max{ax+1,,ar1,ar} 牛币

有多少不同的操作方式使得操作花费的牛币最少,两种操作不同当且仅当两种操作的操作序列不同。
答案对 998244353 取模。

数据范围

1T10, 每个测试点 |n| 的和 10000000

solution

这道题我的 n! 过掉了 n=20 的数据就挺反人类的 = =

这道题 n3 能过也挺反人类的 = =。

正解是个卡常。。。。

10pts

枚举全排列就好了。

20pts

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

70pts(实测 90)

设第一个操作的人的编号为 x, 在 x 进行操作 [1,x1][x+1,n] 的操作就独立了,这两段区间进行操作不会对另一段区间的价值产生影响,因此可以进行区间 dp

fl,r 表示对 l,r 区间进行操作的最小代价。

显然 fl,r=min{fl,k1+fk+1,r}

gl,r[l,r] 区间内最小操作的序列数量,枚举断点 k

显然只有当 fl,r=fl,k1+fk+1,r 才可以转移。

gl,r=gl,r+gl,k1×gk+1,r×Crlkl

因为两个序列之间的操作顺序先后都可,所以就有 Crlkl 种方案。

复杂度: n3

code

/*
work by:Ariel_
Sorce:
Knowledge:Dp
Time:
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
#define int long long
#define rg register
using namespace std;
const int MAXN = 2010, INF = 1e18;
const int mod = 998244353;
int read(){
    int x = 0,f = 1; char c = getchar();
    while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
    return x*f;
}
int n, C[MAXN][MAXN], a[MAXN], Max[MAXN][MAXN], f[MAXN][MAXN], g[MAXN][MAXN];
void Pre() {
  for (int i = 1; i <= n; i++){
    Max[i][i] = a[i];
  	for (int j = i + 1; j <= n; j++) 
	  Max[i][j] = max(Max[i][j - 1], a[j]); 
  }
  C[0][0] = 1;
  for (int i = 1; i <= n; i++) {
  	C[i][0] = 1, C[i][i] = 1;
	for (int j = 1; j < i; j++) {
      C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
	}
  }
}
void Dp() {
  for (int len = 0; len <= n; len++) {
  	for (int l = 1; l + len - 1 <= n; l++) {
  	  int r = l + len - 1;
	  if(l >= r) f[l][r] = 0, g[l][r] = 1;
	  else {
	    f[l][r] = INF, g[l][r] = 0;
	    for (int k = l; k <= r; k++) 
	      f[l][r] = min(f[l][r], f[l][k - 1] + f[k + 1][r] + Max[l][k - 1] + Max[k + 1][r]);
	    for (int k = l; k <= r; k++) {
	      if(f[l][r] == f[l][k - 1] + f[k + 1][r] + Max[l][k - 1] + Max[k + 1][r])
	      g[l][r] = (g[l][r] + g[l][k - 1] * g[k + 1][r] % mod * C[r - l][k - l] % mod) % mod;
		}
	  }
	}
  }
}
signed main(){
   n = read();
   for (int i = 1; i <= n; i++) a[i] = read();
   Pre();
   Dp();
   cout<<g[1][n];
   puts("");
   return 0;
}

100pts

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

code

/*
work by:Ariel_
Sorce:
Knowledge:
Time:
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
#define int long long
#define rg register
using namespace std;
const int mod = 998244353, INF = 1e18;
const int MAXN = 1e3 + 50;
int read(){
    int x = 0,f = 1; char c = getchar();
    while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
    return x*f;
}
int inv[MAXN], fac[MAXN], g[MAXN][MAXN], f[MAXN][MAXN], Max[MAXN][MAXN];
int pos[MAXN][MAXN], a[MAXN], n;
int nxt[MAXN], pre[MAXN];
bool vis[MAXN][MAXN];
void Pre() {
  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(){
   Pre();
   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\n", g[1][n]);
   puts("");
   return 0;
}

100pts

对于一段区间 [l,r],如果存在 ai=ai+1=maxi=lr(ai)(i[l,r))

此时 i,i+1 谁先选择没有关系,因此有 gl,r=gl,i1×gi+1,r×Crl+1il+1

因此当碰到两个最大值连续出现时,直接将整个区间划分为两段,最大值不连续则仍然枚举所有最大值。
时间复杂度 O(n3)

T3 与巨#

题面

定义无穷序列 ff1=1,fn=fn12+1

定义函数 G(x)=minfix(fi) dpc,0=0,dpc,i=max(dpc,i1,[((ic)&G(i))==i]i)

其中 [p] 为真值函数,当 p 为真返回 1,否则返回 0

i=0ndpc,imod998244353

数据范围

1T10 每个测试点 |n| 的和 10000000

solution

20pts

直接模拟题意就好。

/*
work by:Ariel_
Sorce:
Knowledge:
Time:
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
#define int long long
#define rg register
using namespace std;
const int mod = 998244353;
const int MAXN = 1e7 + 8;
int read(){
    int x = 0,f = 1; char c = getchar();
    while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
    return x*f;
}
int T, n, c, f[MAXN];
char s[MAXN];
int G(int x) {
  int ret = 0;
  while(x) {ret++, x >>= 1;} 
  return (1 << ret) - 1;
}
signed main(){
   T = read();
   while(T--) {
   	 scanf("%s%lld", s + 1, &c);
   	 n = 0;
   	 for (int i = 1; i <= s[i]; i++) n = n * 2 + s[i] - '0';
   	 int Ans = 0;
   	 for (int i = 1; i <= n; i++) {
       f[i] = f[i - 1];
       if(((i * c) & G(i)) == i) f[i] = i;
	 }
	 for (int i = 1; i <= n; i++)Ans = (Ans + f[i]) % mod;
	 cout<<Ans<<"\n";
   }
   puts("");
   return 0;
}

100pts

G(i)=2t+11 (G(x)必然是这样的形式,其中 ti 二进制的最高位),则 x&G(i) 相当于 xmod2t+1,则:(i×c)&G(i)=i(i×c)mod2t+1=ii×(c1)mod2t+1=0

c1=2p×x,则 i 需要含有因子 2t+1p

m=|n|,我们可以枚举 t, 如果 t<m1[2t,2t+11] 内的所有数最高位都为 t,设 g=t+1pi 含有因子 2gi 的低于 g 位的全为 0,这样的数为 2t,2t+2g,,2t+x×2g(2t+(x+1)×2g=2t+1), 这是个 x+1 项的等差数列,很容易求和,同时每个数对答案贡献了 2g 次。

t=m1 时,由于 2t+1>n, 我们需要满足,2t+x×2gnx=n2g2tg,对这个 x+1 项的等差数列计算贡献后,由于最后一项被计数了 2g 次(计数范围为[2t+x×2g,2t+(x+1)×2g1]),此时多计数了 2t+(x+1)×2g1n 次,把这个贡献减掉就好了。

时间复杂度 O(n)

code

/*
work by:Ariel_
Sorce:
Knowledge:
Time:
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
#define ll long long
#define rg register
using namespace std;
const int MAXN = 1e7 + 5;
const int mod = 998244353;
ll read(){
    ll x = 0,f = 1; char c = getchar();
    while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
    return x*f;
}
ll pow[MAXN], c, T, n, p;
char s[MAXN];
ll calc(ll s, ll d, ll n) {
  return (s * n % mod + d * n % mod * (n + mod - 1) % mod * (mod - mod / 2) % mod) % mod;
}
int main(){
   pow[0] = 1;
   for (int i = 1; i <= MAXN - 1; i++) pow[i] = (pow[i - 1] << 1) % mod;
   T = read();
   while(T--){
   	 scanf("%s%lld", s + 1, &c);
   	 n = strlen(s + 1);
   	 c -= 1;
   	 ll Ans = 0;
   	 if(c == 0){
   	    for (int i = 1; i <= n; i++) Ans = ((Ans << 1) + s[i] - '0') % mod;
   	    Ans = Ans * (Ans + 1) % mod * (mod - mod / 2) % mod;
   	    printf("%lld\n", Ans);
   	    continue;
   	  }
   	 if(c & 1) {puts("0"); continue;}
   	 p = 0;
   	 while(c % 2 == 0) p++, c /= 2;
   	 for (int t = 0; t < n; t++) {
   	    int g = max(0ll, t + 1 - p);
		if(t < n - 1) //正好的时候 
		Ans = (Ans + pow[g] * calc(pow[t], pow[g], (pow[t + 1 - g] + mod - pow[t - g])) % mod);
		else {//计算剩余部分 
		 ll tot = 0;
		 for (int i = 1; i <= n - g; i++) tot = ((tot << 1) + s[i] - '0') % mod;
		 tot = (tot + 1 + mod - pow[t - g]) % mod;
		 Ans = (Ans + pow[g] * calc(pow[t], pow[g], tot)) % mod;
		 ll res = (pow[t] + (tot + mod - 1) * pow[g] % mod) % mod;
		 ll l = 0;
		 for (int i = 1; i <= n; i++) l = ((l << 1) + s[i] - '0') % mod;
		 int r = (res + pow[g] + mod - 1) % mod;
		 Ans = (Ans + mod - (r + mod - l) % mod * res % mod) % mod; 
		}	  
	  }
	  printf("%lld\n", Ans); 
   }
   puts("");
   return 0;
}

100pts

T4 矩阵学说#

题面

题目描述

给出 nm 列的矩阵,第 i 行第 j 列的元素为 ai,j ,找出满足以下条件的三元组{(i,j,x)}(i,j,x)的数量:

  • 1in,1jm,1xmin(ni+1,mj+1)
  • 矩阵的左上角 (i,j) 到右下角 (i+x1,j+x1) 恰好含有 k 个不同的整数。

数据范围

1kn×m,1a[i]100

solution

bitset 记录颜色种类。

在边长扩展的过程中,不同数量的数只会增加不会减少,具有单调性。于是可以二分找到不同数量 <k 的最大边长 x 和不同数量 k 的最大边长 yyx 即为固定一个左上角符合条件的正方形的数量。

这样需要处理 O(n2logn) 个查询,每个查询查询一个正方形内不同颜色的数量,由于是正方形,故可以采用二维 st 表来做。

code

/*
work by:Ariel_
Sorce:
Knowledge:
Time:
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <bitset>
#define ll long long
#define rg register
using namespace std;
const int MAXN = 1510;
int read(){
    int x = 0,f = 1; char c = getchar();
    while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
    return x*f;
}
int n, m, k, lg[MAXN];
bitset<100>f[14][MAXN][MAXN];
void ST(){
  for (int k = 1; (1 << k) <= max(n, 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 + (1 << k - 1)][j + (1 << k - 1)] | f[k - 1][i][j + (1 << k - 1)];	
}
int Query(int x_1, int y_1, int x_2, int y_2) {
  int k = lg[x_2 - x_1 + 1];
  return (f[k][x_1][y_1] | f[k][x_2 - (1 << k) + 1][y_2 - (1 << k) + 1] | f[k][x_1][y_2 - (1 << k) + 1] | f[k][x_2 - (1 << k) + 1][y_1]).count(); 
}
ll work(int k) {
  if(k == 0) 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), ret;
	 while(l <= r) {
   	   int mid = (l + r + 1) >> 1;
	   if(Query(i, j, i + mid - 1, j + mid - 1) <= k) ret = mid, l = mid + 1;
	   else r = mid - 1;
	 }
	 Ans += ret;
   }
  }
  return Ans;
}
int main(){
   for(int i = 1; i < MAXN; i++) lg[i] = lg[i - 1] + (1 << lg[i - 1] == i), lg[i - 1]--;
   n = read(), m = read(), k = read();
   for (int i = 1; i <= n; i++) {
   	 for (int j = 1, x; j <= m; j++) {
   	      x = read();
   	      f[0][i][j][x - 1] = 1;
	  }
   }
   ST();
   printf("%lld", work(k) - work(k - 1));
   puts("");
   return 0;
}

posted @   Dita  阅读(386)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示
主题色彩