• 2019牛客暑期多校训练营(第一场)


    传送门

    参考资料:

      [1]:官方题解(提取码:t050 )

      [2]:标程(提取码:rvxr )

      [3]:牛客题解汇总

    A.Equivalent Prefixes(单调栈)

    •题意

      定义两个数组 u,v ,并且 u,v 都含有 m 个互不相同的元素;

      如果数组 u,v 任意区间的RMQ(区间最小值)对应的下标都相等,则说这两个数组是 "equivalent";

      先给你包含 n 个不同元素的数组 a,b,求使得 $a[1,2,...,p]$ 与 $b[1,2,....,p]$ 为 "equivalen" 的最大的 p;

    •题解

      定义数组 L1,L1[ i ]表示在 a 数组中,以 ai 为区间最小值最左可以到达的位置;

      即 RMQ( L1[ i ], i ) = ai

      定义数组 L2,含义与 L1 相同,作用于数组 b;

      对于位置 i ,找到第一个不满足 L1[i] == L2[ i ] 的位置 i,那么,答案就为 i-1;

    •Code

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<stack>
     4 using namespace std;
     5 #define ll long long
     6 const int maxn=1e5+50;
     7 
     8 int n;
     9 int a[maxn];
    10 int b[maxn];
    11 int L1[maxn];
    12 int L2[maxn];
    13 stack<int >sta;
    14 
    15 void Work(int *a,int *L)
    16 {
    17     while(!sta.empty())
    18         sta.pop();
    19     for(int i=1;i <= n;++i)
    20     {
    21         while(!sta.empty() && a[sta.top()] >= a[i])
    22             sta.pop();
    23 
    24         L[i]=sta.empty() ? 1:sta.top()+1;
    25         sta.push(i);
    26     }
    27 }
    28 int Solve()
    29 {
    30     Work(a,L1);
    31     Work(b,L2);
    32 
    33     int p=n;
    34     for(int i=1;i <= n;++i)
    35         if(L1[i] != L2[i])
    36         {
    37             p=i-1;
    38             break;
    39         }
    40     return p;
    41 }
    42 int main()
    43 {
    44     while(~scanf("%d",&n))
    45     {
    46         for(int i=1;i <= n;++i)
    47             scanf("%d",a+i);
    48         for(int i=1;i <= n;++i)
    49             scanf("%d",b+i);
    50 
    51         printf("%d
    ",Solve());
    52     }
    53     return 0;
    54 }
    View Code

    E.ABBA(DP or 组合数学)

    •参考资料

      [1]:唐宋元明清(DP)

      [2]:光芒万丈小太阳(DP)

      [3]:rjmgc丁凯朔(组合数学)(待参考)

    •题意

      让你构造一个串 S,并满足串 S 包含 n 个 "AB" 和 m 个 "BA";

      求 S 的总个数对 (109+7) 取模后的结果;

    题解

      比赛结束当天,查看了参考资料[2]的博文,当时出于似懂非懂的状态;

      今天(2019.7.21)重新看了一下这道题,又看了另一篇博文参考资料[1];

      有种恍然大悟的感觉,但任然欠缺点东西,先记录以下,万一那天顿悟了呢。

      定义 dp[ i ][ j ] 表示当前包含 i 个 'A' 和 j 个 'B' 的合法序列的总方案数;

      如果 i+1 ≤ n+j,转移 : dp[i+1][ j ] += dp[ i ][ j ];

      如果 j+1 ≤ m+i,转移 : dp[ i ][ j+1] += dp[ i ][ j ];

      

      

      这两篇博客对比着理解以下,应该更容易些;(tql)

    •Code

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define ll long long
     4 const int MOD=1e9+7;
     5 const int maxn=4e3+50;
     6 
     7 int n,m;
     8 ll dp[maxn][maxn];
     9 
    10 ll Solve()
    11 {
    12     int s=n+m;
    13     for(int i=0;i <= s;++i)
    14         for(int j=0;j <= s;++j)
    15             dp[i][j]=0;
    16     dp[0][0]=1;
    17 
    18     for(int i=0;i <= s;++i)
    19     {
    20         for(int j=0;j <= s;++j)
    21         {
    22             if(i+1 <= n+j)
    23                 dp[i+1][j]=(dp[i+1][j]+dp[i][j])%MOD;
    24             if(j+1 <= m+i)
    25                 dp[i][j+1]=(dp[i][j+1]+dp[i][j])%MOD;
    26         }
    27     }
    28     return dp[s][s];
    29 }
    30 int main()
    31 {
    32     while(~scanf("%d%d",&n,&m))
    33     {
    34         printf("%d
    ",Solve());
    35     }
    36     return 0;
    37 }
    View Code

    •重理解

      前几天做了一下这道题【Codeforces 771D】,感觉,这两道题的 DP 做法大同小异;

      定义 $dp_{i,j}$ 表示用 i 个 'A' 和 j 个 'B' 组成的合法的串的总方案数;

      何为合法的串呢?

      合法的串指的是当前的 i 个 'A' 和 j 个 'B' 组成的串可通过在其后添加剩余的 n+m-i 个 'A' 和 n+m-j 个 'B' 使得其组成的串正好包含 n 个 "AB" 和 m 个 "BA";

      假设当前构造的串中包含 j 个 'B',i 个 'A',那么,i 最大是多少呢?

      这 i 个 'A' 有两个来源:

        (1)"AB" 中的 'A'

        (2)"BA" 中的 'A'

      而易得,n 个 "AB" 最多提供 n 个 'A';

      而当前的 j 个 'B',如果 j ≤ m,那么,这 j 个 'B' 可全部来在 "BA" 的 'B';

      如果 j > m,那么,这 j 个 'B' 有 m 个来自 "BA" 的 'B',其余的来自 "AB" 的 'B';

      所以,这 j 个 'B' 最多可提供 $min(j,m)$ 个 'A',再加上可有由 n 个 "AB" 提供的 n 个 'A';

      所以 $i_{max}=n+min(j,m)$;

      也就是说,对于当前状态 (i 个 'A' , j 个 'B')所组成的串,共有 $dp_{i,j}$ 种合法方式;

      那么,如果 $i+1 leq i_{max}$,也就是说,在当前组成的所有合法的串的后插入一个 'A' 也是合法的;

      那么 $dp[i+1][j] += dp[i][j]$; 

      同理,$j_{max}=m+min(i,n)$;

      如果 $j+1 leq j_{max}$,那么,在当前组成的所有合法的串后插入一个 'B' 也是合法的;

      那么,$dp[i][j+1] += dp[i][j]$;

      而最终答案便是 $dp[n+m][n+m]$;

    •Code

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define ll long long
     4 const int maxn=1e3+50;
     5 const int MOD=1e9+7;
     6 
     7 int n,m;
     8 ll dp[maxn<<1][maxn<<1];
     9 
    10 int main()
    11 {
    12     while(~scanf("%d%d",&n,&m))
    13     {
    14         for(int i=0;i <= n+m;++i)
    15             for(int j=0;j <= n+m;++j)
    16             {
    17                 dp[i][j]=(i+j == 0 ? 1:0);///初始化操作,dp[0][0]=1,其他=0;
    18                 if(i && i <= min(n+m,n+j))///判断在前i-1个'A',j个'B'组成的合法序列的后插入一个'A'是否还为合法序列
    19                     dp[i][j] += dp[i-1][j];
    20                 if(j && j <= min(m+n,m+i))///判断在前i个'A',j-1个'B'组成的合法序列的后插入一个'B'是否还为合法序列
    21                     dp[i][j] += dp[i][j-1];
    22 
    23                 dp[i][j] %= MOD;
    24             }
    25         printf("%lld
    ",dp[n+m][n+m]);
    26     }
    27 }
    View Code

    H.XOR(线性基)

    •参考资料

      [1]:【2019牛客多校第一场】XOR

    •题意

      有一个集合 S,里边有 n 个正整数($a_1,a_2,cdots ,a_n$);

      对于 S 的某子集 s',如果 s' 中所有元素的异或和为 0,就将 |s'| 加入到答案中;

      求解所有的异或为 0 的子集的元素个数之和($ans=sum |s'|$);

      注意,集合 ${2,2}$ 的可构成两个元素为 2 的子集 ${2_1},{2_2}$;

      也就是说子集是否相同和下标有关而与子集的元素是否相同无关;

    •题解(借鉴资料[1])

      首先可以转化问题,不求 &sum |s'|$,而是求每个元素属于子集数的和,也就是统计每个元素对答案的贡献;

      对于第 i 个元素 $a_i$ 如何判断其是否对答案有贡献呢?

      将除第 i 个元素以外的 n-1 个元素构造一个基底 $base$,并判断 $a_i$ 能否插入到 $base$ 中;

      如果能,那么 $a_i$ 是不可能对答案有贡献的;

      如果插入不了,那么,他对答案的贡献就是 $2^{n-|base|-1}$;

      为什么会是这个呢?

      $n-|base|$ 指的是未插入 $base$ 中的元素;

      那么,这些中的任意一个元素都可通过添加一些 S 中的其他元素构成一个异或和为 0 的子集;

      除 $a_i$ 外,还有 $n-|base|-1$ 个这样的可构成异或和为 0 的元素;

      那么,这 $n-|base|-1$ 个元素可形成 $2^{n-|base|-1}$ 个互不相同的集合;

      而 $a_i$ 插入这些集合中同样可以形成异或和为 0 的集合,所以,$a_i$ 对答案的贡献为 $2^{n-|base|-1}$;

      到这儿,问题解决了 99% 了,为什么说是 99% 呢?

      如果单纯的从 1 遍历到 n,每次都判断 $a_i$ 是否对答案有贡献以及贡献是多少,粗略估计一下时间复杂度为 $O(n imes 60^2)$;

      而 ${sum n}_{ max}=2 imes 10^6$;

      考虑到这 n 个数(最大不超过 1018)构成的线性基,最多有 60 个元素;

      那么,我是不是可以将这 n 个数分成两类:

        (1)可以插入到线性基中的数(假设有 x 个,x ≤ 60)

        (2)插入不到线性基中的数(假设有 y 个)

      且 $x+y = n$;

      而对于第(2)类数,每个数对答案的贡献是一样的,为 $2^{y-1}$,这些数对答案的总贡献为 $ycdot 2^{y-1}$;

      那么,我就可以只对有限的 x 个数进行判断其对答案的贡献,是不是好多了呢?

    •Code

      1 #include<bits/stdc++.h>
      2 using namespace std;
      3 #define mem(a,b) memset(a,b,sizeof(a))
      4 #define ll long long
      5 const int maxn=1e5+50;
      6 const int MOD=1e9+7;
      7 
      8 int n;
      9 ll a[maxn];
     10 ll base[70];
     11 ll L[maxn][70];///L[i]:前i个数(1~i)构成的基底
     12 ll R[maxn][70];///R[i]:后n-i+1个数(i~n)构成的基底
     13 bool vis[maxn];///vis[i]:判断a[i]是否在这n个数构成的基底中
     14 
     15 bool Insert(ll x)
     16 {
     17     for(int i=60;i >= 0;--i)
     18     {
     19         if(x>>i&1)
     20         {
     21             if(!base[i])
     22             {
     23                 base[i]=x;
     24                 return true;
     25             }
     26             x ^= base[i];
     27         }
     28     }
     29     return false;
     30 }
     31 ll qPow(ll a,ll b,ll mod)
     32 {
     33     ll ans=1;
     34     a %= mod;
     35     while(b)
     36     {
     37         if(b&1)
     38             ans=ans*a%mod;
     39         a=a*a%mod;
     40         b >>= 1;
     41     }
     42     return ans;
     43 }
     44 ll Solve()
     45 {
     46     mem(base,0);
     47     for(int i=1;i <= n;++i)
     48     {
     49         vis[i]=Insert(a[i]);
     50         memcpy(L[i],base,sizeof(base));
     51     }
     52     
     53     mem(base,0);
     54     mem(R[n+1],0);
     55     for(int i=n;i >= 1;--i)
     56     {
     57         Insert(a[i]);
     58         memcpy(R[i],base,sizeof(base));
     59     }
     60 
     61     int cnt=n;
     62     for(int i=0;i <= 60;++i)
     63         if(base[i])
     64             cnt--;
     65     ///计算出不在基底中的数对答案的贡献
     66     ll ans=cnt*qPow(2,cnt-1,MOD);
     67 
     68     for(int i=1;i <= n;++i)
     69     {
     70         if(!vis[i])
     71             continue;
     72 
     73         ///base:除a[i]外的其他n-1个数构成的基底
     74         memcpy(base,L[i-1],sizeof(L[i-1]));
     75         for(int j=0;j <= 60;++j)
     76             if(R[i+1][j])
     77                 Insert(R[i+1][j]);
     78 
     79         if(Insert(a[i]))///判断a[i]是否还可插入
     80             continue;
     81 
     82         cnt=n;
     83         for(int j=0;j <= 60;++j)
     84             if(base[j])
     85                 cnt--;
     86         ///a[i]对答案的贡献
     87         ans += qPow(2,cnt-1,MOD);
     88         ans %= MOD;
     89     }
     90     return ans;
     91 }
     92 int main()
     93 {
     94     while(~scanf("%d",&n))
     95     {
     96         for(int i=1;i <= n;++i)
     97             scanf("%lld",a+i);
     98 
     99         printf("%lld
    ",Solve()%MOD);
    100     }
    101     return 0;
    102 }
    View Code

    J.Fraction Comparision(亦签到亦数学)

    •题意

      比较 $frac{x}{a}$ 与 $frac{y}{b}$ 的大小;

      其中 $x,y leq 10^{18} and a,bleq 10^9$;

    •题解

      为何说亦签到亦数学呢?

      本题可直接转化为判断 $x imes b$ 与 $y imes a$ 的大小;

      如果直接用 C++ 的 long long 肯定会爆,解决方案是用 __int128 存乘积;

      或者直接用 Java 中的 BigInteger 来存,这道题就很顺利的 AC 了;

      上述两种方式都是无脑操作,所以说这道题为“亦签到”;

      另种解决方案为,将  $frac{x}{a}$ 与 $frac{y}{b}$ 换个样式:

        $frac{x}{a}=lfloor frac{x}{a} floor + frac{x mod a}{a}$,$frac{y}{b}=lfloor frac{y}{b} floor + frac{y mod b}{b}$;

      首先判断整数部分的大小,如果相同,再判断小数部分的大小,因为都对 a 或 b 取模,所以交叉乘积不会爆 long long;

    •Code

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define mem(a,b) memset(a,b,sizeof(a))
     4 #define ll long long
     5 const int maxn=1e5+50;
     6 const int MOD=1e9+7;
     7 
     8 ll x,a,y,b;
     9 
    10 int main()
    11 {
    12     while(~scanf("%lld%lld%lld%lld",&x,&a,&y,&b))
    13     {
    14         ll ans1=x/a;
    15         ll ans2=y/b;
    16         
    17         if(ans1 > ans2)
    18             puts(">");
    19         else if(ans1 < ans2)
    20             puts("<");
    21         else
    22         {
    23             ans1=x%a*b;
    24             ans2=y%b*a;
    25             if(ans1 > ans2)
    26                 puts(">");
    27             else if(ans1 < ans2)
    28                 puts("<");
    29             else
    30                 puts("=");
    31         }
    32     }
    33     return 0;
    34 }
    View Code
  • 相关阅读:
    LeetCode算法题-Trim a Binary Search Tree(Java实现)
    LeetCode算法题-Non-decreasing Array(Java实现)
    LeetCode算法题-Image Smoother(Java实现)
    Node.js 官方文档中文版
    jade 网上看到一个不错的demo 分享 一下 链接
    jade 的 考古
    标题党 数据抓取与管理
    最近面试 有人问 sqlite 用过么 sqlite 不是 嵌入式的 开发 么 难道最近还 web开发 了?
    嗯 想写个demo 苦于没数据
    客户端 jQuery 跨端口 调用 node 服务端
  • 原文地址:https://www.cnblogs.com/violet-acmer/p/11209409.html
Copyright © 2020-2023  润新知