AtCoder-ABC-406 题解

比赛速览
本场比赛比较吃经验

  • A. if-else
  • B. 模拟(有小坑)
  • C. 思维题
  • D. 模拟 + vis优化
  • E. DP(带有不规则上限的数位组合问题
  • F. DFS序 + 线段树/BIT
  • G. DP + 斜率优化

A - Not Acceptable

给定 A A A B B B分,和 C C C D D D分两个时间,判断第一个时间是否晚于第二个时间。

参考程序

数学小技巧: 直接转成分钟来比较大小。

#include<stdio.h>
int main(){
  int a,b,c,d;
  scanf("%d %d %d %d",&a,&b,&c,&d);
  printf(((a*60+b-c*60-d)>0)?"Yes":"No");
}

B - Product Calculator

有一个计算器,初始显示数字 1 1 1
他将对这个计算器执行 N N N 次操作。
在第 i i i 次操作中( 1 ≤ i ≤ N 1 ≤ i ≤ N 1iN),他会将当前显示的数字乘以一个正整数 A i A_i Ai

但计算器的显示屏最多只能显示 K K K 位数字。
如果乘法结果的数字位数 超过 K K K 位(即 ≥ K + 1 ≥ K+1 K+1 位),则显示屏会显示 1 1 1
否则,计算结果会正常显示。

  • 1 ≤ K ≤ 18 1 \leq K \leq 18 1K18
  1. 首先要开long long
  2. 其次,如果直接按照题意模拟,即每次乘完再判断是否超出长度,则会溢出long long

处理的方法有两种:

  • 方法一:
    每次先看一下两个数字的长度,如果长度之和 > 19 >19 >19则可以直接赋值为 1 1 1。因为 x x x位数与 y y y位数相乘,结果可能是 x + y − 1 x+y-1 x+y1位数或 x + y x+y x+y位数。因此如果 x + y > 19 x+y>19 x+y>19 则结果至少是个 19 19 19位数。注意,此时计算结果需要使用unsigned long long

    • long long 的极大值是 9 × 10 18 9 \times 10^{18} 9×1018
    • unsigned long long 的极大值是 1 × 10 19 1\times 10^{19} 1×1019
  • 方法二:(思路来源: Rui同学)
    S × a [ i ] > 10 k − 1 S \times a[i] > 10^k - 1 S×a[i]>10k1 的判断条件改为除法判断。即 S > ( 10 k − 1 ) / a [ i ] S > (10^k-1)/a[i] S>(10k1)/a[i]

参考程序 - 方法一 (侍行远同学)

#include<bits/stdc++.h>
using namespace std;
unsigned long long n,m;
unsigned long long res=1;
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		unsigned long long x;
		cin>>x;
		string t=to_string(x);
		string s1=to_string(res);
		if((s1.size()+t.size()-1)>18) {
			res=1;
			continue;
		}
		res*=x;
		string s=to_string(res);
		if(s.size()>m){
			res=1;
		}
	}
	cout<<res;
	return 0;
}

参考程序 - 方法二

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n,k;
    long long a,x=1,y=1;
    cin >> n >> k;
    for(int i=0;i<k;i++){
        y*=10;
    }
    y--;
    for(int i=0;i<n;i++){
        cin >> a;
        if(x>(y/a)){
            x=1;
        } else {
            x*=a;
        }
    }
    cout << x << endl;
}

C - 波浪序列

给定一个 1 − N 1-N 1N的排列,求其中波浪子段的数量,波浪子段 A A A的四个要求:

  1. 长度 > = 4 >= 4 >=4
  2. A 1 < A 2 A_1 < A_2 A1<A2
  3. 只存在唯一1个 A i A_i Ai 满足 A i − 1 < A i > A i + 1 A_{i-1} < A_i > A_{i+1} Ai1<Ai>Ai+1
  4. 只存在唯一1个 A i A_i Ai 满足 A i − 1 > A i < A i + 1 A_{i-1} > A_i < A_{i+1} Ai1>Ai<Ai+1

4 ≤ N ≤ 3 × 10 5 4\leq N \leq 3\times 10^5 4N3×105

本题略有思维难度,需要找好规律才好写。

如果我们随手画一个符合这个要求的波浪序列,一定长成这个样子。

406-c

其中 B B B C C C两个点最为重要。一旦 B B B C C C固定住,那么左右两端点 A A A D D D可以移动,在两个上升段上都是合法的。

对于一个一般的序列,如图:

406-c

每一对相邻的山峰点 B B B和山谷点 C C C, 都可以形成一些合法序列。对应的 A A A D D D只要在相邻的两段上升段即可。

由此我们可以找出所有的上升段,只要计算出每个上升段的长度即可。

参考程序

代码技巧: 流式处理。
在设计代码的时候最好设计成无后效性的处理逻辑,假设数据是一个一个过来,我们对数据依次处理。在当前处理第 i i i个数字的时候不考虑 i i i后面的数据,形成一种单向考虑的结构。

本题的代码中,我们需要统计所有上升段的长度。核心代码如下

int cnt = 1; // 表示第一个数字
cin >> a[1];
for(int i = 2; i <= n; i++) {
    cin >> a[i];
    if(a[i] > a[i-1]) cnt++; // 持续累加
    else {
       // 一段结束
       if(cnt > 1) X.push_back(cnt - 1);
       cnt = 1; // 下一段开始
    }
}

D - Garbage Removal

给定一个 H H H W W W列的地图,再给出 N N N个格子中有垃圾, Q Q Q次询问:

  • 询问1 x,回答第 x x x行有多少垃圾,并移除第 x x x行所有垃圾。

  • 询问2 y,回答第 y y y列有多少垃圾,并移除第 y y y列所有垃圾。

  • 1 ≤ H , W , N , Q ≤ 2 × 10 5 1 \leq H, W, N, Q \leq 2\times 10^5 1H,W,N,Q2×105

本题的地图很大,因此不能模拟。但是可以直接用邻接表存储每一行有垃圾点,和每一列有哪些垃圾点, 在删除的时候标记一下哪些行和列被删除了,在统计的时候忽略就可以了。

因为每个点最多被删除2次,因此整体的复杂度是 O ( 2 N ) O(2N) O(2N), 但是有一个坑大家需要注意,就是同一行会被删除多次,对于已经被删除的行不要重复的扫描处理。

E - Popcount Sum 3

给定 N N N K K K, 求 1 − N 1-N 1N的所有正整数中,二进制中 1 1 1的个数恰好为 K K K的数字总和是多少。

  • 1 ≤ N ≤ 2 60 1 \leq N \leq 2^{60} 1N260
  • 1 ≤ K ≤ 60 1 \leq K \leq 60 1K60
  • 多组数据评测: T ≤ 100 T\leq 100 T100

首先考虑如果没有N的限制,只有二进制的长度限制该怎么做?

很明显,如果只有长度限制,那么对于第 i i i二进制位可以考虑它在总结果中贡献多少次,即有多少个数字这一位是 1 1 1。在总和中 2 i 2^i 2i就贡献多少次。

现在有了 N N N的限制,不能单纯的直接组合数了,考虑当前位是否为 1 1 1还需要考虑它的前面和后面。但是考虑贡献依然是我们的核心方向。

这是一个非常典型的带有不规则上限的数位组合问题。通常的做法就是从最高位开始考虑。

  1. 对于 N N N的当前位,如果为 1 1 1,则本位可以选择 0 0 0 1 1 1
  2. 当前位为 1 1 1,选择放 0 0 0,则此后的所有低位是一个无上限的数位组合问题。直接可以求出种类数。
  3. 当前位为 1 1 1,选择放 1 1 1,则后面的低位无法确定方案数,需要继续向后考虑。
  4. 当前位为 0 0 0,只能选择放 0 0 0

对于无上限的数位问题:

  • ll dp[65][65]; 表示 i i i位二进制数中恰好有 j j j 1 1 1的数字的个数。
  • ll S[65][65]; 表示 i i i位二进制数中有恰好 j j j 1 1 1的数字总和。

由此我们可以写出一个预处理的函数来求解上述两个结果。并以此来递归考虑每一位放0/1, 并记录总的贡献。

参考程序

#include <bits/stdc++.h>
using namespace std;

#define mkpair(a, b) make_pair(a, b)

typedef long long ll;

const int MOD     = 998244353;
const int MAXN    = 2000005;

ll a[MAXN], b[MAXN], vis[MAXN];
int n, m;
ll N; int K;

ll dp[65][65]; // 表示前i位中恰好有j个1的数字的个数。
ll S[65][65]; // 表示前i位中有恰好j个1的数字总和。

ll Ans = 0;

// 从高到低的第k位,已有j个1,这些1的总和是sum
void DFS(int k, int j, ll sum) {
    if(j >= K) {
        Ans = (Ans + sum) % MOD;
        return;
    }
    if(k == 0) return ;

    if(N>>(k-1)&1) {
        Ans = (Ans + sum * dp[k-1][K-j] % MOD + S[k-1][K-j]) % MOD;
        DFS(k-1, j+1, (sum + a[k-1])%MOD);
    }
    else {
        DFS(k-1, j, sum%MOD);
    }
}


int main(){
    for(int i = 0; i <= 60; i++) a[i] = (1LL << i) % MOD;
    dp[0][0] = 1;
    for(int i = 1; i <= 60; i++) {
        dp[i][0] = dp[i][i] = 1;
        S[i][0] = 0;
        S[i][i] = a[i] - 1;
        for(int j = 1; j <i; j++) {
            dp[i][j] = (dp[i-1][j] + dp[i-1][j-1]) % MOD;
            S[i][j] = (S[i-1][j] + S[i-1][j-1] + dp[i-1][j-1] * a[i-1] % MOD) % MOD;
            // cout << i << " " << j << " --> " << dp[i][j] << " " << S[i][j] << endl;
        }
    }

    int T;
    cin >> T;
    while(T--) {
        cin >> N >> K;
        Ans = 0;
        DFS(60, 0, 0LL);
        cout << Ans << endl;
    }


    return 0;
}

F - Chord Crossing

给定一个无根树,初始每个点的点权都是 1 1 1,有 Q Q Q次操作包括以下两种。

  1. 1 x w x x x点权重增加 w w w
  2. 2 y 假设删除第 y y y条边,求产生的两个子树之间的权重和之差。

1 ≤ N , Q ≤ 3 × 10 5 1\leq N, Q \leq 3\times 10^5 1N,Q3×105

本题学过**《算法E-线段树》**的同学其实都应该会做,如果我们使用DFS序的知识将树问题转化为区间问题就做完了。

当一条边 ( u , v ) (u, v) (u,v)被删除,形成了两个子树,一棵是以 v v v为根的子树,一棵是剩余所有点。转换为区间问题以后:

A n s = S u m [ 1 , N ] − S u m [ i n [ v ] , o u t [ v ] ] Ans = Sum[1, N] - Sum[in[v], out[v]] Ans=Sum[1,N]Sum[in[v],out[v]]

单点修改,区间求和。线段树可解,树状树组亦可解。太过简单,代码略去。

G - Travelling Salesman Problem

数轴上有 N N N个物品,编号为 1 − N 1-N 1N,并且给出了每个物品的坐标位置 X i X_i Xi
你初始在 0 0 0位置,你需要按照 1 − N 1-N 1N的顺序获得每个物品。
你可以选择自己移动,每单位长度代价为 C C C,也可以选择让物品移动,每单位长度代价为 D D D
问最小代价。

1 ≤ N ≤ 10 5 1\leq N \leq 10^5 1N105
− 10 5 ≤ X i ≤ 10 5 -10^5 \leq X_i \leq 10^5 105Xi105

本题属于比较模版的斜率优化问题。先来考虑DP。

由于需要按照1-N的顺序来获取物品,因此完全没有贪心的空间。并且对于总代价最优的目标,单一物品的获取,可能是人和物品一起移动比较优。目的是为了下一个物品构造一个更低的移动代价。因此获取完一个物品i,可能停留在任意的j位置。

dp[i][j] 表示获取完前i个物品,位置处于j位置的最小代价。转移方程显而易见:

d p [ i ] [ j ] = M i n k = − 10 5 10 5 ( d p [ i − 1 ] [ k ] + C × ∣ k − j ∣ + D × ∣ X i − j ∣ ) dp[i][j] = Min_{k=-10^5}^{10^5}(dp[i-1][k] + C\times |k - j| + D\times |X_i - j|) dp[i][j]=Mink=105105(dp[i1][k]+C×kj+D×Xij)

这个式子对于学过斜率优化的同学来说已经很直白了,不做赘述,剩下的就是模版了。


赛后交流

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值