比赛速览
- A. if-else
- B. 暴力模拟
- C. 前缀和优化(不可暴力)
- D. BFS + 打印路径 + 逆向思维
- E. 思维 + 枚举 + 组合数
- F. 问题转化->LCA (区间数据结构做法欢迎投稿)
- G. 莫队 + 分块暴力
A - Is it rated?
题意
ARC比赛分为两个组别Div. 1 1 1 和Div. 2 2 2, 给定选手的当前分数 R R R与参赛组别Div. X X X,判断选手是否参与积分排名。参与积分排名的规则是:
- 对于Div. 1 1 1 比赛,分数在 [ 1600 , 2999 ] [1600, 2999] [1600,2999]区间内。
- 对于Div. 2 2 2 比赛,分数在 [ 1200 , 2399 ] [1200, 2399] [1200,2399]区间内。
参考程序
#include<cstdio>
int r,x;
int main(){
scanf("%d%d",&r,&x);
printf((x==1&&1600<=r&&r<=2999)||(x==2&&1200<=r&&r<=2399)?"Yes":"No");
return 0;
}
B - Not All
给定一个长度为N的整数序列A,和一个正整数M。你可以做如下的操作任意多次:
操作:删除A序列的最后一个元素。
求最少的操作次数,使得序列A不满足:A中包含 1 ∼ M 1\sim M 1∼M中的每个数字。
- 1 ≤ M ≤ N ≤ 100 1≤M≤N≤100 1≤M≤N≤100
- 1 ≤ A i ≤ M 1≤A_i≤M 1≤Ai≤M
由于N比较小,可以暴力Check此时A中是否包含 1 ∼ M 1\sim M 1∼M中的每个数字。也可以用桶维护。
参考程序
#include<iostream>
using namespace std;
int a[105],b[105];
int main()
{
int n,m;
cin>>n>>m;
for(int i = 1;i<=n;i++)
{
cin>>a[i];
b[a[i]]++;
}
for(int i = 1;i<=m;i++)
{
if(b[i]==0)
{
cout<<'0';
return 0;
}
}
int c = 0;
for(int i = n;i>=1;i--)
{
c++;
b[a[i]]--;
if(b[a[i]]==0)
{
break;
}
}
cout<<c;
return 0;
}
C - Sum of Product
给定一个长度为N的整数序列A, 求 ∑ 1 ≤ i < j ≤ N A i A j \sum_{1\leq i < j \leq N} {A_iA_j} ∑1≤i<j≤NAiAj
- 2 ≤ N ≤ 3 × 10 5 2≤N≤3×10^5 2≤N≤3×105
- 1 ≤ A i ≤ 10 4 1≤A_i≤10^4 1≤Ai≤104
由于N很大,不能直接写两层循环模拟,但是我们发现,结果中是每个 A i A_i Ai都会乘上自己后面每个数字,因此直接求后缀和与 A i A_i Ai相乘即可。反过来对于每个 A j A_j Aj都是乘上自己的前面,即前缀和。
参考程序
#include <bits/stdc++.h>
using namespace std;
int n;
long long a[300005];
long long s[300005];
long long ans;
int main() {
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> a[i];
s[i] = s[i-1] + a[i];
}
for(int i = 1; i < n; i++) {
ans += a[i] * (s[n]-s[i]);
}
cout << ans;
return 0;
}
D - Escape Route
给定一个 H H H行 W W W列的地图,其中
.
表示空地可以通过,#
表示障碍无法通过,E
表示出口。可以上下左右移动,请你求出每个空地到距离最近的出口的路线。
输出每个位置应该向哪个方向移动才能到达最近的出口,方向是(^, >, v, <)
- 1 ≤ H , W ≤ 1000 1 \leq H, W \leq 1000 1≤H,W≤1000
本题与白老师讲过的BFS题目《小明和他的朋友们》非常相似,只是多了一个打印路径。
- 首先不能从每个空地开始做BFS,因为这样复杂度过高,每个点都要跑一次是 O ( ( H W ) 2 ) O((HW)^2) O((HW)2)。可以选择从所有的E出发(即将所有的E在一开始都丢到队列中作为起点),每次搜到一个空地点就是这个空地点最近能到达的E。
- 第二个问题就是:如何记录路径?
我们直接在BFS的时候记录搜索到这个点时的方向即可。因为我们等于从终点E搜索到起点空地,那么只要记录每个点的来的方向,就可以一路走到E。
参考程序
#include <iostream>
#include <queue>
#include <string>
using namespace std;
int main() {
int H, W;
cin >> H >> W;
vector<string> S(H);
for (auto& s : S) cin >> s;
queue<pair<int, int>> Q;
for (int i = 0; i < H; i++) {
for (int j = 0; j < W; j++) {
if (S[i][j] == 'E') Q.emplace(i, j);
}
}
int dx[] = {1, 0, -1, 0};
int dy[] = {0, 1, 0, -1};
auto ok = [&](int i, int j) { return 0 <= i and i < H and 0 <= j and j < W; };
string A = "^<v>";
while (!Q.empty()) {
auto [i, j] = Q.front();
Q.pop();
for (int k = 0; k < 4; k++) {
int ni = i + dx[k];
int nj = j + dy[k];
if (!ok(ni, nj)) continue;
if (S[ni][nj] != '.') continue;
S[ni][nj] = A[k];
Q.emplace(ni, nj);
}
}
for (auto& s : S) cout << s << "\n";
}
E - Fruit Lineup
给定四种水果 a a a、 b b b、 c c c、 d d d各有 A A A、 B B B、 C C C、 D D D个,我们需要排列这些水果满足:
- a a a水果全在 c c c的左边
- a a a水果全在 d d d的左边
- b b b水果全在 d d d的左边
同类的水果是完全一致的,没有差异。
- 1 ≤ A , B , C , D ≤ 10 6 1 \leq A, B, C, D \leq 10^6 1≤A,B,C,D≤106
对于这种有 4 4 4个变量的题目,经验告诉我们只要考虑 2 2 2个应该就可以了。 a a a的条件比较多,可以从它下手。
- 所有的 a a a最靠右只能放到 A + B A+B A+B位置,因为后面的 C + D C+D C+D至少要保证,否则违反 1 1 1和 2 2 2两条规则。
- 如果最靠右侧的 a a a 我们确定在位置 i i i,那么 c c c只能在这个位置的右侧 , a ,a ,a只能在这个位置的左侧。
- 如果 a a a和 c c c的位置都确定了,剩余的空位置只能是先放所有的 b b b,再放所有的 d d d。因此只要确定了 a a a和 c c c的方案就可以了。 b b b和 d d d不用考虑。
综上,我们可以枚举最后一个 a a a的位置为 i i i,并在 i i i左侧的 i − 1 i-1 i−1个位置中选择 A − 1 A-1 A−1个位置放 a a a,在右侧 N − i N-i N−i个位置中选择 C C C个位置放 c c c即可。
A n s = ∑ i = A A + B C ( i − 1 , A − 1 ) C ( N − i , C ) Ans = \sum_{i = A}^{A+B} C(i-1, A-1)C(N-i, C) Ans=i=A∑A+BC(i−1,A−1)C(N−i,C)
参考程序
#include<bits/stdc++.h>
#define int long long
#define N 1000010
#define tN 3000000
#define mod 998244353
using namespace std;
int a,b,c,d,ans,fac[3*N],inv[3*N];
int qp(int a,int n){
int fac=1;
while(n){
if(n&1) fac=fac*a%mod;
a=a*a%mod,n>>=1;
}
return fac;
}
int C(int n,int m){return fac[n]*inv[m]%mod*inv[n-m]%mod;}
signed main(){
cin>>a>>b>>c>>d;
fac[0]=1;
for(int i=1;i<=tN;i++) fac[i]=fac[i-1]*i%mod;
inv[tN]=qp(fac[tN],mod-2);
for(int i=tN;i;i--) inv[i-1]=inv[i]*i%mod;
for(int x=0;x<=b;x++) (ans+=C(a-1+b-x,a-1)*C(c+d+x,d+x)%mod)%=mod;
cout<<ans<<"\n";
return 0;
}
F - Chord Crossing
有2N个点编号为 1 ∼ 2 N 1\sim 2N 1∼2N在一个圆环上顺时针均匀分布,给出M条连线,每条连线连通一组偶数编号的点对 A i A_i Ai和 B i B_i Bi, 保证这些边互不交叉, 且 A i ≠ B i A_i \neq B_i Ai=Bi。
接下来有 Q Q Q个查询,每次查询给出一组奇数编号的点对 C i C_i Ci和 D i D_i Di并画上连线, 请你求出在给出的M条连线中,与新增连线之间一共会有多少个交点。每次查询给出的连线不会影响下一次查询,即查询新增的连线不会留在图中。
- 2 ≤ N ≤ 10 6 2 \leq N \leq 10^6 2≤N≤106
- 1 ≤ Q ≤ 10 5 1 \leq Q \leq 10^5 1≤Q≤105
样例输入
4 2 2 4 6 8 3 1 3 3 7 1 5
样例输出
1
2
0
如图:
重要的问题转化基础:
圆上点之间的两条连线(a, b) 与(c, d)之间如果有交点,则必须满足a < c < b < d。(令a<b, c<d)
本题圆上问题,我们可以考虑在一条直线上,问题可以完全等价成:
在一条长度为 2 N 2N 2N的线段上,均匀分布 2 N 2N 2N个点,存在 M M M个区间,区间的首尾都是偶数,且这些区间之间要么是分离关系,要么是包含关系,不存在交叉关系。
Q Q Q次查询,每次给出一个新的区间,求新区间与原M个区间中多少区间存在交叉关系。
对于这个新问题,着重看其中的包含关系。还可以继续转化模型成为一个树模型。
将 M M M条线段看作是 M M M个点
如果线段 i i i与线段 j j j存在直接包含关系,即不存在区间 k k k满足: i i i包含 k k k, 且 k k k包含 j j j,则从 i i i向 j j j画一条有向边。
在本题中保证了所有 M M M条区间互不相交,因此包含关系是一个严格偏序关系,以此方法连边的到的一定是树结构(无环,无多个父亲),由此我们可以获得一个森林,额外创建一个虚点 0 0 0, 使得其构建成一棵树结构。
那么对于一个查询区间 [ C i , D i ] [C_i, D_i] [Ci,Di],我们找出直接包含点 C i C_i Ci的区间 u u u, 和直接包含点 D i D_i Di的区间 v v v,那么 u u u和 v v v是与查询区间相交的,并且 u u u和 v v v的父亲也可能是和查询区间相交的,可以一直向上找他们的父亲,直到某节点所代表的区间包含了查询区间,很明显,这是一个 L C A LCA LCA的问题。
G - Range Shuffle Query
给定一个长度为 N N N的序列 A , Q A,Q A,Q个查询,每次查询给出 [ L , R , X ] [L, R, X] [L,R,X], 要求将 A A A序列 [ L , R ] [L, R] [L,R]的子段中 < X <X <X的数字都取出来,并做排列,有多少种不同的排列。
例如:对于子段 [ 1 , 2 , 3 , 3 , 1 ] [1,2,3,3,1] [1,2,3,3,1], 当 X = 3 X=3 X=3的时候剩余 [ 1 , 1 , 2 ] [1,1,2] [1,1,2] 排列共有 3 3 3种: [ 1 , 1 , 2 ] , [ 1 , 2 , 1 ] , [ 2 , 1 , 1 ] . [1,1,2], [1, 2, 1], [2, 1, 1]. [1,1,2],[1,2,1],[2,1,1].
- 1 ≤ N , Q ≤ 2.5 × 10 5 1 \leq N, Q \leq 2.5\times 10^5 1≤N,Q≤2.5×105
- 1 ≤ A i ≤ N 1 \leq A_i \leq N 1≤Ai≤N
对于多重集组合数,单纯求一次的复杂度是 O ( N ) O(N) O(N), 所以明显复杂度不支持每次查询单独处理,因此需要考虑查询之间的变化量,所以是个莫队算法。
接下来就是如何快速获知两次查询之间每一种数字数量变化了多少,那么就可以在上一次的结果里乘乘除除搞定。
查询不变化不仅仅是个区间的变化,还有一个数字 X X X的变化。如果仅仅是区间的变化则是一个标准的莫队算法。对于 X X X的变化,我们需要用到分块的思想来解决。
-
很明显我们需要维护一个桶,用来记录每个数字出现的次数。当区间变化的时候,莫队算法可以很容易维护桶的变化。
-
区间变化完成之后 , X ,X ,X的变化会导致原本需要纳入考虑的数字现在不需要计入了( X X X变小了),或者反之;那么当 X X X的变化幅度较大的时候,我们扫描的代价就变大到 O ( N ) O(N) O(N)了,因此我们可以对桶做分块维护,将复杂度降低到 O ( N ) O(\sqrt N) O(N)
-
由此我们单次计算多重集排列数的复杂度为 O ( N ) O(\sqrt N) O(N), 总的计算复杂度为 O ( Q N ) O(Q\sqrt N) O(QN).
-
莫队维护区间变化的总复杂度为 O ( N N ) O(N\sqrt N) O(NN)
-
综上总复杂度为 O ( N N ) O(N\sqrt N) O(NN)
参考程序
自行参考Atcoder提交记录。
赛后交流
WX: jikecheng11,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。