比赛速览
本场比赛比较吃经验
- A. if-else
- B. 模拟(有小坑)
- C. 思维题
- D. 模拟 + vis优化
- E. DP(带有不规则上限的数位组合问题)
- F. DFS序 + 线段树/BIT
- G. DP + 斜率优化
A - Not Acceptable
给定AAA点BBB分,和CCC点DDD分两个时间,判断第一个时间是否晚于第二个时间。
参考程序
数学小技巧: 直接转成分钟来比较大小。
#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 ≤ N1≤i≤N),他会将当前显示的数字乘以一个正整数AiA_iAi。但计算器的显示屏最多只能显示 KKK 位数字。
如果乘法结果的数字位数 超过 KKK 位(即 ≥K+1≥ K+1≥K+1 位),则显示屏会显示 111;
否则,计算结果会正常显示。
- 1≤K≤181 \leq K \leq 181≤K≤18
- 首先要开
long long。 - 其次,如果直接按照题意模拟,即每次乘完再判断是否超出长度,则会溢出
long long。
处理的方法有两种:
-
方法一:
每次先看一下两个数字的长度,如果长度之和>19>19>19则可以直接赋值为111。因为xxx位数与yyy位数相乘,结果可能是x+y−1x+y-1x+y−1位数或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]>10k−1 的判断条件改为除法判断。即 S>(10k−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−N1-N1−N的排列,求其中波浪子段的数量,波浪子段AAA的四个要求:
- 长度 >=4>= 4>=4
- A1<A2A_1 < A_2A1<A2
- 只存在唯一1个AiA_iAi 满足 Ai−1<Ai>Ai+1A_{i-1} < A_i > A_{i+1}Ai−1<Ai>Ai+1
- 只存在唯一1个AiA_iAi 满足 Ai−1>Ai<Ai+1A_{i-1} > A_i < A_{i+1}Ai−1>Ai<Ai+1
4≤N≤3×1054\leq N \leq 3\times 10^54≤N≤3×105
本题略有思维难度,需要找好规律才好写。
如果我们随手画一个符合这个要求的波浪序列,一定长成这个样子。

其中BBB、CCC两个点最为重要。一旦BBB和CCC固定住,那么左右两端点AAA和DDD可以移动,在两个上升段上都是合法的。
对于一个一般的序列,如图:

每一对相邻的山峰点BBB和山谷点CCC, 都可以形成一些合法序列。对应的AAA与DDD只要在相邻的两段上升段即可。
由此我们可以找出所有的上升段,只要计算出每个上升段的长度即可。
参考程序
代码技巧: 流式处理。
在设计代码的时候最好设计成无后效性的处理逻辑,假设数据是一个一个过来,我们对数据依次处理。在当前处理第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
给定一个HHH行WWW列的地图,再给出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^51≤H,W,N,Q≤2×105
本题的地图很大,因此不能模拟。但是可以直接用邻接表存储每一行有垃圾点,和每一列有哪些垃圾点, 在删除的时候标记一下哪些行和列被删除了,在统计的时候忽略就可以了。
因为每个点最多被删除2次,因此整体的复杂度是O(2N)O(2N)O(2N), 但是有一个坑大家需要注意,就是同一行会被删除多次,对于已经被删除的行不要重复的扫描处理。
E - Popcount Sum 3
给定NNN和KKK, 求1−N1-N1−N的所有正整数中,二进制中111的个数恰好为KKK的数字总和是多少。
- 1≤N≤2601 \leq N \leq 2^{60}1≤N≤260
- 1≤K≤601 \leq K \leq 601≤K≤60
- 多组数据评测: T≤100T\leq 100T≤100
首先考虑如果没有N的限制,只有二进制的长度限制该怎么做?
很明显,如果只有长度限制,那么对于第iii二进制位可以考虑它在总结果中贡献多少次,即有多少个数字这一位是111。在总和中2i2^i2i就贡献多少次。
现在有了NNN的限制,不能单纯的直接组合数了,考虑当前位是否为111还需要考虑它的前面和后面。但是考虑贡献依然是我们的核心方向。
这是一个非常典型的带有不规则上限的数位组合问题。通常的做法就是从最高位开始考虑。
- 对于NNN的当前位,如果为111,则本位可以选择000或111;
- 当前位为111,选择放000,则此后的所有低位是一个无上限的数位组合问题。直接可以求出种类数。
- 当前位为111,选择放111,则后面的低位无法确定方案数,需要继续向后考虑。
- 当前位为000,只能选择放000
对于无上限的数位问题:
ll dp[65][65];表示iii位二进制数中恰好有jjj个111的数字的个数。ll S[65][65];表示iii位二进制数中有恰好jjj个111的数字总和。
由此我们可以写出一个预处理的函数来求解上述两个结果。并以此来递归考虑每一位放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 x w给xxx点权重增加www2 y假设删除第yyy条边,求产生的两个子树之间的权重和之差。1≤N,Q≤3×1051\leq N, Q \leq 3\times 10^51≤N,Q≤3×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-N1−N,并且给出了每个物品的坐标位置XiX_iXi。
你初始在000位置,你需要按照1−N1-N1−N的顺序获得每个物品。
你可以选择自己移动,每单位长度代价为CCC,也可以选择让物品移动,每单位长度代价为DDD。
问最小代价。1≤N≤1051\leq N \leq 10^51≤N≤105
−105≤Xi≤105-10^5 \leq X_i \leq 10^5−105≤Xi≤105
本题属于比较模版的斜率优化问题。先来考虑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[i−1][k]+C×∣k−j∣+D×∣Xi−j∣)
这个式子对于学过斜率优化的同学来说已经很直白了,不做赘述,剩下的就是模版了。
赛后交流
群号: jikecheng11,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。
1105

被折叠的 条评论
为什么被折叠?



