zoukankan      html  css  js  c++  java
  • 2021牛客OI赛前集训营-提高组(第一场)

    前言:

    pts:25 + 20 + 0 + 30 = 75

    最惨淡的一场。

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

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

    今后考试策略:

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

    考试一定要带脑子 !!!

    T1 牛表

    题目描述

    题面

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

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

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

    ({sumlimits_{i=1}^{P-1}sumlimits_{j=1}^{P-1}ans(i,j)*t^{(i-1)*(P-1)+j-1}mod 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 eq 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} imes g_{k +1, r} imes 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
    ", g[1][n]);
       puts("");
       return 0;
    }
    
    

    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 - 1} imes g_{i + 1, r} imes C_{r - l + 1}^{i - l + 1})

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

    T3 与巨

    题面

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

    定义函数 ({G(x)=minlimits_{f_ige 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})

    ({sumlimits_{i=0}^ndp_{c,i}}mod 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<<"
    ";
       }
       puts("");
       return 0;
    }
    

    100pts

    (G(i) = 2^{t + 1} - 1) ((G(x))必然是这样的形式,其中 (t)(i) 二进制的最高位),则 (x & G(i)) 相当于 (xmod 2^{t + 1}),则:((i imes c) & G(i) = i ightarrow (i imes c) mod 2^{t + 1} = i ightarrow i imes (c - 1) mod 2^{t + 1} = 0)

    (c - 1 = 2^p imes 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 imes 2^g(2^t + (x + 1) imes 2^g = 2^{t + 1})), 这是个 (x + 1) 项的等差数列,很容易求和,同时每个数对答案贡献了 (2^g) 次。

    (t = m - 1) 时,由于 (2^{t + 1} > n), 我们需要满足,(2^t + x imes 2^g leq n ightarrow x = lfloor frac{n}{2^g} floor - 2^{t - g}),对这个 (x + 1) 项的等差数列计算贡献后,由于最后一项被计数了 (2^g) 次(计数范围为([2^t + x imes 2^g, 2^t + (x + 1) imes 2^g - 1])),此时多计数了 (2^t + (x + 1) imes 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
    ", 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
    ", Ans); 
       }
       puts("");
       return 0;
    }
    
    

    100pts

    T4 矩阵学说

    题面

    题目描述

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

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

    数据范围

    (1 leq k leq n imes 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;
    }
    
    
  • 相关阅读:
    BZOJ 3132: 上帝造题的七分钟 树状数组+差分
    PAT Advanced 1006 Sign In and Sign Out (25 分)
    PAT Advanced 1011 World Cup Betting (20 分)
    PAT Basic 1032 挖掘机技术哪家强 (20 分)
    PAT Basic 1028 人口普查 (20 分)
    PAT Basic 1004 成绩排名 (20 分)
    大数据数据库HBase(二)——搭建与JavaAPI
    PAT Advanced 1009 Product of Polynomials (25 分)(vector删除元素用的是erase)
    PAT Advanced 1002 A+B for Polynomials (25 分)(隐藏条件,多项式的系数不能为0)
    PAT Basic 1041 考试座位号 (15 分)
  • 原文地址:https://www.cnblogs.com/Arielzz/p/15391779.html
Copyright © 2011-2022 走看看