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
View Code1 #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 }
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
View Code1 #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 }
•重理解
前几天做了一下这道题【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
View Code1 #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 }
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
View Code1 #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 }
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
View Code1 #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 }