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\n",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\n",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\n",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\times 60^2)$;

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

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

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

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

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

  且 $x+y = n$;

  而对于第(2)类数,每个数对答案的贡献是一样的,为 $2^{y-1}$,这些数对答案的总贡献为 $y\cdot 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\n",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,b\leq 10^9$;

•题解

  为何说亦签到亦数学呢?

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

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

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

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

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

    $\frac{x}{a}=\lfloor \frac{x}{a} \rfloor + \frac{x\ mod\ a}{a}$,$\frac{y}{b}=\lfloor \frac{y}{b} \rfloor + \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

 

posted @ 2019-07-18 19:12  HHHyacinth  阅读(421)  评论(0编辑  收藏  举报