2024-08-21-ABC367
A - Shout Everyday
高桥从B睡到C,如果在A时刻时,他醒着就会尖叫,问他是不是会尖叫。
说白就是问你A在不在BC两个时刻之间。
#include<bits/stdc++.h>
using namespace std;
int A, B, C;
int main() {
cin >> A >> B >> C;
if ((A - B + 24) % 24 > (C - B + 24) % 24) {
cout << "Yes\n";
} else {
cout << "No\n";
}
return 0;
}
也可以从B循环到C。
B - Cut .0
给定一个小数,去除后导0,如果删完小数部分则还要把小数点删了。
#include <bits/stdc++.h>
using namespace std;
int main() {
string s;
cin >> s;
while (s.back() == '0')
s.pop_back();
if (s.back() == '.')
s.pop_back();
cout << s << '\n';
return 0;
}
C - Enumerate Sequences
要求输出所有符合要求的数组a,要求数组的和是k的倍数, 并且每个数的范围是1~ri。
枚举
#include <bits/stdc++.h>
using namespace std;
const int N = 201000;
int n, k, r[N], b[N];
void dfs(int x, int s) {
if (x == n + 1) {
if (s == 0) {
for (int i = 1; i <= n; i++) printf("%d ", b[i]);
puts("");
}
} else {
for (int i = 1; i <= r[x]; i++) {
b[x] = i;
dfs(x + 1, (s + i) % k);
}
}
}
int main() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) scanf("%d", &r[i]);
dfs(1, 0);
return 0;
}
D - Pedometer
环形湖上有n个点,给定ai表示从第i个点顺时针走到第i+1个点的时间。
问有多少对(s,t),满足顺时针从s走到t的时间是m的倍数。
思路
先考虑s<=t的情况,那么就是问有几段a数组的和是m的倍数,我们可以对前缀和取模,那么前缀和的模相等的点之间的和肯定是模m为0的,也就是问统计前缀和模相等的对数,具体做法:开一个cnt数组用于记数前面前缀和的模,然后枚举终点,把cnt里有多少个起点加进ans里即可。
那环形怎么办,可以小技巧:把环解成链,比如1234的环做成12341234,然后做一个类似于滑动窗口的操作,我们在走到第五位的时候把第一位在cnt里删掉,那么我们统计的就是第二位到第五位的合法s和t。
#include <bits/stdc++.h>
#define LL long long
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<int> a(n);
for (auto& x : a)
cin >> x;
vector<int> cnt(m, 0);
LL ans = 0;
LL presum = 0;
for (int i = 0; i < n; ++i) {
ans += cnt[presum % m];
cnt[presum % m]++;
presum += a[i];
}
LL sum = presum;
presum = 0;
for (int i = 0; i < n; ++i) {
cnt[presum % m]--;
ans += cnt[sum % m];
sum += a[i];
presum += a[i];
}
cout << ans << '\n';
return 0;
}
E - Permute K times
给定数组x和a,一共进行k次操作,每次操作做一个新数组bi=axi,然后把a数组替换为b数组,问k此操作之后的数组。
思路
需要知道的常识:如果x是一个1~n的排列的话,对于像这样的变化,如果把x的每次操作画成图的话,x一定是由很多环构成的,每个环的大小必定不大于n。
不是排列的话,那么他其实是一个基环树森林,但我们不用管他,因为那些经过一次操作到达相同点的点以后所有的操作都是一样的,我们不用处理。
这题其实是一道非常模板的倍增,k非常大,我们处理一个二维数组run[64][n]
,其中run[i][j]
表示第j个位置走\(2^i\)步之后会到哪里,这个数组非常好处理,因为第j个点走\(2^i\)步等于他从先走\(2^{i-1}\)步到达的地方再走\(2^{i-1}\)步,写成转移就是run[i][j] = run[i - 1][run[i - 1][j]]
,这就是倍增的基本思想。
同样,根据我们这个先走几步,再走几步的思想,我们直接把k分解成二进制位,就能直接调用run数组每次走2的整次幂倍的步数,找到每个点k步后的目标了。
#include <bits/stdc++.h>
#define LL long long
using namespace std;
int run[64][200005], a[200005];
int main() {
int n;
LL k;
cin >> n >> k;
for (int i = 0; i < n; i++) {
int v;
cin >> v;
--v;
run[0][i] = v;
}
for (int i = 0; i < n; i++)
cin >> a[i];
for (int i = 1; i < 64; i++) {
for (int j = 0; j < n; j++) {
run[i][j] = run[i - 1][run[i - 1][j]];
}
}
for (int i = 0; i < n; i++) {
LL cnt = k;
int cur = i;
for (int j = 0; j < 64; j++) {
if (cnt & (1LL << j)) {
cur = run[j][cur];
}
}
cout << a[cur] << " ";
}
return 0;
}
F - Rearrange Query
给定两个长度为n的数组a和b,并且有Q次查询,第i次查询给定li,ri,Li,Ri,问a和b里的对应区间能不能通过重新排列的方式使两者匹配。
如果要两段一样,那么两段的和必须一样,如果只用前缀和判断两段和是否一样会被构造的数据卡掉,所以我们对每一个数都给他随机映射到另一个数上,这样求和的方式就不容易被卡掉了。
这就是哈希,冲突率在这题里是不高的。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
mt19937_64 mrand(random_device{}());
const int N=201000;
int n,q;
ll hs[N],sa[N],sb[N];
int main() {
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++) hs[i]=mrand();
for(int i=1;i<=n;i++) {
int a;
scanf("%d",&a);
sa[i]=sa[i-1]+hs[a];
}
for(int i=1;i<=n;i++) {
int a;
scanf("%d",&a);
sb[i]=sb[i-1]+hs[a];
}
while(q--){
int l1,r1,l2,r2;
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
puts((sa[r1]-sa[l1-1]==sb[r2]-sb[l2-1])?"Yes":"No");
}
return 0;
}