AtCoder-ABC-405 题解

比赛速览

  • A. if-else
  • B. 暴力模拟
  • C. 前缀和优化(不可暴力)
  • D. BFS + 打印路径 + 逆向思维
  • E. 思维 + 枚举 + 组合数
  • F. 问题转化->LCA (区间数据结构做法欢迎投稿)
  • G. 莫队 + 分块暴力

A - Is it rated?

题意

ARC比赛分为两个组别Div.111 和Div.222, 给定选手的当前分数RRR与参赛组别Div.XXX,判断选手是否参与积分排名。参与积分排名的规则是:

  1. 对于Div.111 比赛,分数在[1600,2999][1600, 2999][1600,2999]区间内。
  2. 对于Div.222 比赛,分数在[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∼M1\sim M1M中的每个数字。

  • 1≤M≤N≤1001≤M≤N≤1001MN100
  • 1≤Ai≤M1≤A_i≤M1AiM

由于N比较小,可以暴力Check此时A中是否包含1∼M1\sim M1M中的每个数字。也可以用桶维护。

参考程序

#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≤NAiAj\sum_{1\leq i < j \leq N} {A_iA_j}1i<jNAiAj

  • 2≤N≤3×1052≤N≤3×10^52N3×105
  • 1≤Ai≤1041≤A_i≤10^41Ai104

由于N很大,不能直接写两层循环模拟,但是我们发现,结果中是每个AiA_iAi都会乘上自己后面每个数字,因此直接求后缀和与AiA_iAi相乘即可。反过来对于每个AjA_jAj都是乘上自己的前面,即前缀和。

参考程序

#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

给定一个HHHWWW列的地图,其中.表示空地可以通过,#表示障碍无法通过,E表示出口。可以上下左右移动,请你求出每个空地到距离最近的出口的路线。
输出每个位置应该向哪个方向移动才能到达最近的出口,方向是(^, >, v, <)

  • 1≤H,W≤10001 \leq H, W \leq 10001H,W1000

本题与白老师讲过的BFS题目《小明和他的朋友们》非常相似,只是多了一个打印路径。

  1. 首先不能从每个空地开始做BFS,因为这样复杂度过高,每个点都要跑一次是O((HW)2)O((HW)^2)O((HW)2)。可以选择从所有的E出发(即将所有的E在一开始都丢到队列中作为起点),每次搜到一个空地点就是这个空地点最近能到达的E。
  2. 第二个问题就是:如何记录路径?
    我们直接在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

给定四种水果aaabbbcccddd各有AAABBBCCCDDD个,我们需要排列这些水果满足:

  • aaa水果全在ccc的左边
  • aaa水果全在ddd的左边
  • bbb水果全在ddd的左边

同类的水果是完全一致的,没有差异。

  • 1≤A,B,C,D≤1061 \leq A, B, C, D \leq 10^61A,B,C,D106

对于这种有444个变量的题目,经验告诉我们只要考虑222个应该就可以了。aaa的条件比较多,可以从它下手。

  1. 所有的aaa最靠右只能放到A+BA+BA+B位置,因为后面的C+DC+DC+D至少要保证,否则违反111222两条规则。
  2. 如果最靠右侧的aaa 我们确定在位置iii,那么ccc只能在这个位置的右侧,a,a,a只能在这个位置的左侧。
  3. 如果aaaccc的位置都确定了,剩余的空位置只能是先放所有的bbb,再放所有的ddd。因此只要确定了aaaccc的方案就可以了。 bbbddd不用考虑。

综上,我们可以枚举最后一个aaa的位置为iii,并在iii左侧的i−1i-1i1个位置中选择A−1A-1A1个位置放aaa,在右侧N−iN-iNi个位置中选择CCC个位置放ccc即可。

Ans=∑i=AA+BC(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=AA+BC(i1,A1)C(Ni,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∼2N1\sim 2N12N在一个圆环上顺时针均匀分布,给出M条连线,每条连线连通一组偶数编号的点对AiA_iAiBiB_iBi, 保证这些边互不交叉, 且Ai≠BiA_i \neq B_iAi=Bi

接下来有QQQ个查询,每次查询给出一组奇数编号的点对CiC_iCiDiD_iDi并画上连线, 请你求出在给出的M条连线中,与新增连线之间一共会有多少个交点。每次查询给出的连线不会影响下一次查询,即查询新增的连线不会留在图中。

  • 2≤N≤1062 \leq N \leq 10^62N106
  • 1≤Q≤1051 \leq Q \leq 10^51Q105

样例输入

4 2
2 4
6 8
3
1 3
3 7
1 5

样例输出

1
2
0

如图:

image.png

重要的问题转化基础

圆上点之间的两条连线(a, b) 与(c, d)之间如果有交点,则必须满足a < c < b < d。(令a<b, c<d)

本题圆上问题,我们可以考虑在一条直线上,问题可以完全等价成:

在一条长度为2N2N2N的线段上,均匀分布2N2N2N个点,存在MMM个区间,区间的首尾都是偶数,且这些区间之间要么是分离关系,要么是包含关系,不存在交叉关系。

QQQ次查询,每次给出一个新的区间,求新区间与原M个区间中多少区间存在交叉关系。

ABC-405-G.png

对于这个新问题,着重看其中的包含关系。还可以继续转化模型成为一个树模型。

MMM条线段看作是MMM个点
如果线段iii与线段jjj存在直接包含关系,即不存在区间kkk满足:iii包含kkk, 且kkk包含jjj,则从iiijjj画一条有向边。

在本题中保证了所有MMM条区间互不相交,因此包含关系是一个严格偏序关系,以此方法连边的到的一定是树结构(无环,无多个父亲),由此我们可以获得一个森林,额外创建一个虚点000, 使得其构建成一棵树结构。

那么对于一个查询区间[Ci,Di][C_i, D_i][Ci,Di],我们找出直接包含点CiC_iCi的区间uuu, 和直接包含点DiD_iDi的区间vvv,那么uuuvvv是与查询区间相交的,并且uuuvvv的父亲也可能是和查询区间相交的,可以一直向上找他们的父亲,直到某节点所代表的区间包含了查询区间,很明显,这是一个LCALCALCA的问题。

G - Range Shuffle Query

给定一个长度为NNN的序列A,QA,QA,Q个查询,每次查询给出[L,R,X][L, R, X][L,R,X], 要求将AAA序列[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=3X=3X=3的时候剩余[1,1,2][1,1,2][1,1,2] 排列共有333种:[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×1051 \leq N, Q \leq 2.5\times 10^51N,Q2.5×105
  • 1≤Ai≤N1 \leq A_i \leq N1AiN

对于多重集组合数,单纯求一次的复杂度是O(N)O(N)O(N), 所以明显复杂度不支持每次查询单独处理,因此需要考虑查询之间的变化量,所以是个莫队算法。

接下来就是如何快速获知两次查询之间每一种数字数量变化了多少,那么就可以在上一次的结果里乘乘除除搞定。

查询不变化不仅仅是个区间的变化,还有一个数字XXX的变化。如果仅仅是区间的变化则是一个标准的莫队算法。对于XXX的变化,我们需要用到分块的思想来解决。

  1. 很明显我们需要维护一个桶,用来记录每个数字出现的次数。当区间变化的时候,莫队算法可以很容易维护桶的变化。

  2. 区间变化完成之后,X,X,X的变化会导致原本需要纳入考虑的数字现在不需要计入了(XXX变小了),或者反之;那么当XXX的变化幅度较大的时候,我们扫描的代价就变大到O(N)O(N)O(N)了,因此我们可以对桶做分块维护,将复杂度降低到O(N)O(\sqrt N)O(N)

  3. 由此我们单次计算多重集排列数的复杂度为O(N)O(\sqrt N)O(N), 总的计算复杂度为O(QN)O(Q\sqrt N)O(QN).

  4. 莫队维护区间变化的总复杂度为O(NN)O(N\sqrt N)O(NN)

  5. 综上总复杂度为O(NN)O(N\sqrt N)O(NN)

参考程序

自行参考Atcoder提交记录。


赛后交流

WX: jikecheng11,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值