比赛速览
本场比赛比较吃经验
- 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 1≤i≤N),他会将当前显示的数字乘以一个正整数 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 1≤K≤18
- 首先要开
long long
。 - 其次,如果直接按照题意模拟,即每次乘完再判断是否超出长度,则会溢出
long long
。
处理的方法有两种:
-
方法一:
每次先看一下两个数字的长度,如果长度之和 > 19 >19 >19则可以直接赋值为 1 1 1。因为 x x x位数与 y y y位数相乘,结果可能是 x + y − 1 x+y-1 x+y−1位数或 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]>10k−1 的判断条件改为除法判断。即 S > ( 10 k − 1 ) / a [ i ] S > (10^k-1)/a[i] S>(10k−1)/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 1−N的排列,求其中波浪子段的数量,波浪子段 A A A的四个要求:
- 长度 > = 4 >= 4 >=4
- A 1 < A 2 A_1 < A_2 A1<A2
- 只存在唯一1个 A i A_i Ai 满足 A i − 1 < A i > A i + 1 A_{i-1} < A_i > A_{i+1} Ai−1<Ai>Ai+1
- 只存在唯一1个 A i A_i Ai 满足 A i − 1 > A i < A i + 1 A_{i-1} > A_i < A_{i+1} Ai−1>Ai<Ai+1
4 ≤ N ≤ 3 × 10 5 4\leq N \leq 3\times 10^5 4≤N≤3×105
本题略有思维难度,需要找好规律才好写。
如果我们随手画一个符合这个要求的波浪序列,一定长成这个样子。
其中 B B B、 C C C两个点最为重要。一旦 B B B和 C C C固定住,那么左右两端点 A A A和 D D D可以移动,在两个上升段上都是合法的。
对于一个一般的序列,如图:
每一对相邻的山峰点 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 1≤H,W,N,Q≤2×105
本题的地图很大,因此不能模拟。但是可以直接用邻接表存储每一行有垃圾点,和每一列有哪些垃圾点, 在删除的时候标记一下哪些行和列被删除了,在统计的时候忽略就可以了。
因为每个点最多被删除2次,因此整体的复杂度是 O ( 2 N ) O(2N) O(2N), 但是有一个坑大家需要注意,就是同一行会被删除多次,对于已经被删除的行不要重复的扫描处理。
E - Popcount Sum 3
给定 N N N和 K K K, 求 1 − N 1-N 1−N的所有正整数中,二进制中 1 1 1的个数恰好为 K K K的数字总和是多少。
- 1 ≤ N ≤ 2 60 1 \leq N \leq 2^{60} 1≤N≤260
- 1 ≤ K ≤ 60 1 \leq K \leq 60 1≤K≤60
- 多组数据评测: T ≤ 100 T\leq 100 T≤100
首先考虑如果没有N的限制,只有二进制的长度限制该怎么做?
很明显,如果只有长度限制,那么对于第 i i i二进制位可以考虑它在总结果中贡献多少次,即有多少个数字这一位是 1 1 1。在总和中 2 i 2^i 2i就贡献多少次。
现在有了 N N N的限制,不能单纯的直接组合数了,考虑当前位是否为 1 1 1还需要考虑它的前面和后面。但是考虑贡献依然是我们的核心方向。
这是一个非常典型的带有不规则上限的数位组合问题。通常的做法就是从最高位开始考虑。
- 对于 N N N的当前位,如果为 1 1 1,则本位可以选择 0 0 0或 1 1 1;
- 当前位为 1 1 1,选择放 0 0 0,则此后的所有低位是一个无上限的数位组合问题。直接可以求出种类数。
- 当前位为 1 1 1,选择放 1 1 1,则后面的低位无法确定方案数,需要继续向后考虑。
- 当前位为 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 x w
给 x x x点权重增加 w w w2 y
假设删除第 y y y条边,求产生的两个子树之间的权重和之差。1 ≤ N , Q ≤ 3 × 10 5 1\leq N, Q \leq 3\times 10^5 1≤N,Q≤3×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 1−N,并且给出了每个物品的坐标位置 X i X_i Xi。
你初始在 0 0 0位置,你需要按照 1 − N 1-N 1−N的顺序获得每个物品。
你可以选择自己移动,每单位长度代价为 C C C,也可以选择让物品移动,每单位长度代价为 D D D。
问最小代价。1 ≤ N ≤ 10 5 1\leq N \leq 10^5 1≤N≤105
− 10 5 ≤ X i ≤ 10 5 -10^5 \leq X_i \leq 10^5 −105≤Xi≤105
本题属于比较模版的斜率优化问题。先来考虑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[i−1][k]+C×∣k−j∣+D×∣Xi−j∣)
这个式子对于学过斜率优化的同学来说已经很直白了,不做赘述,剩下的就是模版了。
赛后交流
群号: jikecheng11,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。