AtCoder-ABC-406 题解

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

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

A - Not Acceptable

给定AAABBB分,和CCCDDD分两个时间,判断第一个时间是否晚于第二个时间。

参考程序

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

#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

有一个计算器,初始显示数字 111
他将对这个计算器执行 NNN 次操作。
在第 iii 次操作中(1≤i≤N1 ≤ i ≤ N1iN),他会将当前显示的数字乘以一个正整数AiA_iAi

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

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

处理的方法有两种:

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

    • long long 的极大值是9×10189 \times 10^{18}9×1018
    • unsigned long long 的极大值是1×10191\times 10^{19}1×1019
  • 方法二:(思路来源: Rui同学)
    S×a[i]>10k−1S \times a[i] > 10^k - 1S×a[i]>10k1 的判断条件改为除法判断。即 S>(10k−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−N1-N1N的排列,求其中波浪子段的数量,波浪子段AAA的四个要求:

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

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

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

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

406-c

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

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

406-c

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

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

参考程序

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

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

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

给定一个HHHWWW列的地图,再给出NNN个格子中有垃圾,QQQ次询问:

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

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

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

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

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

E - Popcount Sum 3

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

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

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

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

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

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

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

对于无上限的数位问题:

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

由此我们可以写出一个预处理的函数来求解上述两个结果。并以此来递归考虑每一位放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

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

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

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

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

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

Ans=Sum[1,N]−Sum[in[v],out[v]]Ans = Sum[1, N] - Sum[in[v], out[v]]Ans=Sum[1,N]Sum[in[v],out[v]]

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

G - Travelling Salesman Problem

数轴上有NNN个物品,编号为1−N1-N1N,并且给出了每个物品的坐标位置XiX_iXi
你初始在000位置,你需要按照1−N1-N1N的顺序获得每个物品。
你可以选择自己移动,每单位长度代价为CCC,也可以选择让物品移动,每单位长度代价为DDD
问最小代价。

1≤N≤1051\leq N \leq 10^51N105
−105≤Xi≤105-10^5 \leq X_i \leq 10^5105Xi105

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

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

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

dp[i][j]=Mink=−105105(dp[i−1][k]+C×∣k−j∣+D×∣Xi−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,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值