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;
    }
    
    
  • 相关阅读:
    LVS NAT模式
    lvs部署-DR模式
    Lvs原理
    iOS判断UIScrollView的滚动方向
    Swift开发必备技巧:内存管理、weak和unowned
    Swift2.0异常处理
    Swift中的延迟加载(懒加载)
    Swift和OC混编时, 关于@objc的作用
    Swift函数的定义建议
    Swift隐式可选型简单介绍
  • 原文地址:https://www.cnblogs.com/Arielzz/p/15391779.html
Copyright © 2011-2022 走看看