前言:

pts:25 + 20 + 0 + 30 = 75

最惨淡的一场。

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

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

今后考试策略:

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

考试一定要带脑子 !!!

T1 牛表

题目描述

题面

给出三个整数 \({x,y,P(1\le x,y<P)}\), \({P}\) 为素数,可以重复对 \({x}\) 执行如下操作:
选择一个整数 \({z\in[1,P-1]}\) ,花费 \({|x-z|}\) 的牛币,使得 \(x=x\times z\bmod P\)

最小需要花费多少牛币才能使得 \({x=y}\) ?

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

\({\sum\limits_{i=1}^{P-1}\sum\limits_{j=1}^{P-1}ans(i,j)*t^{(i-1)*(P-1)+j-1}\bmod 998244353}\)

数据范围

\(2 \leq P \leq 2000\)

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

solution

对于每一个操作,相当于从 \(x\)\(x * z \mod P\) 建一条边权为 \(|x - z|\) 的边,然后到 \(y\) 跑一个最短路就好了。

30pts

\(P \leq 500\) 直接 \(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\) 个整数 \(a_0, a_1, \dots, a_n, a_{n + 1}\)

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

选择一个整数 \(x\) 满足 \(a_x \neq 0\), 使得 \(a_x\), 令 \(l = \max_{i < x, a_i = 0}(i), r = \min_{i > x, a_i = 0}(i)\)

此次操作的操作为 \(\max\{a_l, a_{l + 1}, \dots, a_{x - 1}\} + \max\{a_{x + 1},\dots, a_{r - 1}, a_r\}\) 牛币

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

数据范围

\(1 \leq T \leq 10\), 每个测试点 \(|n|\) 的和 \(\leq 10000000\)

solution

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

这道题 \(n^3\) 能过也挺反人类的 = =。

正解是个卡常。。。。

10pts

枚举全排列就好了。

20pts

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

70pts(实测 90)

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

\(f_{l, r}\) 表示对 \(l, r\) 区间进行操作的最小代价。

显然 \(f_{l, r} = \min\{f_{l, k - 1} + f_{k + 1, r}\}\)

\(g_{l, r}\)\([l, r]\) 区间内最小操作的序列数量,枚举断点 \(k\)

显然只有当 \(f_{l, r} = f_{l, k - 1} + f_{k +1, r}\) 才可以转移。

\(g_{l, r} = g_{l, r} + g_{l, k - 1} \times g_{k +1, r} \times C_{r - l}^{k - l}\)

因为两个序列之间的操作顺序先后都可,所以就有 \(C_{r - l}^{k - l}\) 种方案。

复杂度: \(n^3\)

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(n^3)\)

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]\),如果存在 \({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 - 1} \times g_{i + 1, r} \times C_{r - l + 1}^{i - l + 1}\)

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

T3 与巨

题面

定义无穷序列 \({f:f_1=1,f_{n}=f_{n-1}*2+1}\)

定义函数 \({G(x)=\min\limits_{f_i\ge x}(f_i})\) \({dp_{c,0}=0,dp_{c,i}=max(dp_{c,i-1},[((i*c)\&G(i))==i]*i)}\)

其中 \({[p]}\) 为真值函数,当 \({p}\) 为真返回 \({1}\),否则返回 \({0}\)

\({\sum\limits_{i=0}^ndp_{c,i}}\bmod 998244353\)

数据范围

\(1 \leq T \leq 10\) 每个测试点 \(|n|\) 的和 \(\leq 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) = 2^{t + 1} - 1\) (\(G(x)\)必然是这样的形式,其中 \(t\)\(i\) 二进制的最高位),则 \(x \& G(i)\) 相当于 \(x\mod 2^{t + 1}\),则:\((i\times c) \& G(i) = i \rightarrow (i \times c) \mod 2^{t + 1} = i \rightarrow i \times (c - 1) \mod 2^{t + 1} = 0\)

\(c - 1 = 2^p \times x\),则 \(i\) 需要含有因子 \(2^{t + 1 - p}\)

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

\(t = m - 1\) 时,由于 \(2^{t + 1} > n\), 我们需要满足,\(2^t + x\times 2^g \leq n \rightarrow x = \lfloor \frac{n}{2^g}\rfloor - 2^{t - g}\),对这个 \(x + 1\) 项的等差数列计算贡献后,由于最后一项被计数了 \(2^g\) 次(计数范围为\([2^t + x\times 2^g, 2^t + (x + 1)\times 2^g - 1]\)),此时多计数了 \(2^t + (x + 1) \times 2^g - 1 - n\) 次,把这个贡献减掉就好了。

时间复杂度 \(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 矩阵学说

题面

题目描述

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

  • \({1\le i\le n,1\le j\le m,1\le x\le min(n-i+1,m-j+1) }\)
  • 矩阵的左上角 \({(i,j)}\) 到右下角 \({(i+x-1,j+x-1)}\) 恰好含有 \({k}\) 个不同的整数。

数据范围

\(1 \leq k \leq n\times m, 1 \leq a[i]\leq 100\)

solution

\(bitset\) 记录颜色种类。

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

这样需要处理 \({O(n^2logn)}\) 个查询,每个查询查询一个正方形内不同颜色的数量,由于是正方形,故可以采用二维 \({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;
}