比赛链接:http://oj.ecustacm.cn/contest.php?cid=1021
题目总览
题目 | TAG | 难度 | 来源 | 补题链接 |
---|---|---|---|---|
密码 | 思维题 | ☆ | P A C N W 2021 PACNW \ 2021 PACNW 2021 | http://oj.ecustacm.cn/problem.php?id=1821 |
扔小球 | 模拟、 S T L STL STL | ☆☆ | C o d e C h e f CodeChef CodeChef | http://oj.ecustacm.cn/problem.php?id=1822 |
村庄与部落 | 模拟 | ☆☆ | C o d e C h e f CodeChef CodeChef | http://oj.ecustacm.cn/problem.php?id=1823 |
机器人与指令 | 双向搜索、哈希 | ☆☆☆☆ | U S A C O 2022 F e b USACO\ 2022\ Feb USACO 2022 Feb | http://oj.ecustacm.cn/problem.php?id=1824 |
区间开根 | 线段树 | ☆☆☆ | B Z O J 3211 BZOJ\ 3211 BZOJ 3211 | http://oj.ecustacm.cn/problem.php?id=1825 |
A 密码
题意: 小明设计了一套新型密码机制,密码长度为
4
4
4位,包含数字
0
−
9
0-9
0−9。
小明设计了两个原始密码
a
a
a和
b
b
b,输入的密码每一位数字与同一位置的两个原始密码中的一个匹配即可。例如原始密码
a
a
a和
b
b
b分别为
1111
1111
1111和
1134
1134
1134。则输入
1111
1111
1111、
1114
1114
1114、
1131
1131
1131、
1134
1134
1134都可以成功登录。
现在给你
a
a
a和
b
b
b,请计算存在多少个不同的可登录密码。
Tag: 思维题
难度: ☆
来源: P A C N W 2021 PACNW\ 2021 PACNW 2021
思路: 当 a [ i ] = = b [ i ] a[i]==b[i] a[i]==b[i],则说明第 i i i位只有 1 1 1种可能,否则第 i i i位可以为 a [ i ] a[i] a[i]或者 b [ i ] b[i] b[i]。最终答案等于 2 c n t 2^{cnt} 2cnt,其中 c n t cnt cnt等于 a [ i ] ≠ b [ i ] a[i]\ne b[i] a[i]=b[i]的数量。
#include<bits/stdc++.h>
using namespace std;
int main()
{
string a, b;
cin >> a >> b;
int ans = 1;
for(int i = 0; i < 4; i++)
if(a[i] != b[i])ans *= 2;
cout<<ans<<endl;
return 0;
}
B 扔小球
题意: 假设一个小球从高度
H
H
H落下,则经过一次弹跳之后,下次最高点为
H
F
\frac{H}{F}
FH高度。
再经过一次弹跳的最高点为
H
F
2
\frac{H}{F^2}
F2H高度,依次类推,此处不是整数除法。
有
N
N
N个学生,每个学生高度为
H
[
i
]
H[i]
H[i],请问存在选出多少二元组满足:
其中一位学生的身高处落下小球,经过若干次弹跳之后,最高点恰好等于另一名学生身高。
Tag: 模拟、 S T L STL STL
难度: ☆☆
来源: C o d e C h e f CodeChef CodeChef
思路: 由于 F ≥ 2 F\ge2 F≥2,所以直接对每个高度暴力模拟即可。需要事先利用 m a p map map统计每个数字出现的次数。
注意:对于高度相同的两个学生,直接统计答案会重复,因此这一部分最终需要除以 2 2 2。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int a[maxn];
int main()
{
int T;
cin >> T;
while(T--)
{
int n, F;
cin >> n >> F;
map<int, int>Map;
//统计每个数字出现次数
for(int i = 1; i <= n; i++)
cin >> a[i], Map[a[i]]++;
long long ans1 = 0, ans2 = 0;
for(int i = 1; i <= n; i++)
{
int H = a[i];
ans1 += Map[H] - 1;
while(H && H % F == 0)
{
H /= F;
ans2 += Map[H];
}
}
cout<<ans1 / 2 + ans2<<endl;
}
return 0;
}
C 村庄与部落
题意:
n
n
n个村庄坐落成一条直线,
A
A
A和
B
B
B两个部落生活在这里。
每个村庄要么无人居住,要么被 两个部落之一所占据。
如果一个无人居住的村庄两侧都是被部落
A
A
A占据的村庄,那么这个村庄也视作被部落
A
A
A占据;部落
B
B
B亦同。
请求出被部落
A
A
A和
B
B
B分别占据的村庄个数。
Tag: 模拟
难度: ☆☆
来源: C o d e C h e f CodeChef CodeChef
思路: 对于一段连续的 A . . . A A...A A...A,可以直接统计 A A A的数量,同理对于 B . . . B B...B B...B也是类似的。对于 A . . . B A...B A...B或者 B . . . A B...A B...A则不需要考虑中间这一段。
所以记录上一个非 ⋅ \cdot ⋅的坐标记为 l a s t _ i d x last\_idx last_idx,对于当前的 i i i满足 s [ i ] ≠ ⋅ a n d s [ l a s t _ i d x ] = s [ i ] s[i] \ne \cdot \ and \ s[last\_idx]=s[i] s[i]=⋅ and s[last_idx]=s[i],则分类讨论,统计这一段的长度。
对于 s [ i ] ≠ ⋅ a n d s [ l a s t _ i d x ] ≠ s [ i ] s[i] \ne \cdot \ and \ s[last\_idx] \ne s[i] s[i]=⋅ and s[last_idx]=s[i],只需要统计当前 s [ i ] s[i] s[i]带来的贡献即可。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int T;
cin >> T;
while(T--)
{
string s;
cin >> s;
int last_idx = -1;
int ansA = 0, ansB = 0;
for(int i = 0; i < s.size(); i++)
{
if(s[i] != '.')
{
if(last_idx != -1 && s[last_idx] == s[i])
{
if(s[i] == 'A')ansA += i - last_idx;
else ansB += i - last_idx;
}
else if(s[i] == 'A')ansA++;
else ansB++;
last_idx = i;
}
}
cout<<ansA<<" "<<ansB<<endl;
}
return 0;
}
D 机器人与指令
题意: 机器人在
(
0
,
0
)
(0,0)
(0,0)位置,需要移动到终点
(
s
x
,
s
y
)
(sx,sy)
(sx,sy)位置。现在有
N
(
N
≤
40
)
N(N\le 40)
N(N≤40)条指令,每条指令有两个数字
(
x
,
y
)
(x,y)
(x,y),表示将机器人的横坐标加上
x
x
x,纵坐标加上
y
y
y。
x
x
x和
y
y
y可以为负数。
现在对于
1
1
1到
N
N
N中的每一个
K
K
K,请求出从
N
N
N条指令中选择
K
K
K条的方案数,使得执行这
K
K
K条指令后,机器人恰好到达终点。
Tag: 双向搜索、哈希
难度: ☆☆☆☆
来源: U S A C O 2022 F e b USACO\ 2022\ Feb USACO 2022 Feb
思路: 从 N N N条指令中选择 K K K条,最简单的想法是枚举每条指令选还是不选,时间复杂度 O ( 2 N ) O(2^N) O(2N)。但是此处 N N N最大为 40 40 40,一般 2 N 2^N 2N级别我们处理的都是 N = 20 N=20 N=20。
那么能否将 2 40 2^{40} 240的暴力转换成 2 20 2^{20} 220的枚举呢?
中途相遇法:一边从起点暴力搜索,一边从终点暴力搜索,中途相遇则更新最短路。
这里就可以参考中途相遇法的思想,将 N N N条指令分成 [ 1 , 1 + N 2 ] , [ 1 + N 2 + 1 , N ] [1,\frac{1+N}{2}],[\frac{1+N}{2}+1,N] [1,21+N],[21+N+1,N]两部分。每部分暴力枚举所有情况,可以得到指数级数量的三元组 < x , y , c n t > <x,y,cnt> <x,y,cnt>,每个三元组表示 Δ x = x , Δ y = y \Delta x=x,\Delta y=y Δx=x,Δy=y,且选择 c n t cnt cnt条指令。
第一部分 [ 1 , 1 + N 2 ] [1,\frac{1+N}{2}] [1,21+N]得到的三元组记为数组 a a a,第二部分记为数组 b b b。
对于 b b b中的某个元素 < x , y , c n t > <x,y,cnt> <x,y,cnt>而言,表示当前从 ( 0 , 0 ) (0,0) (0,0)走到了 ( x , y ) (x,y) (x,y),选择了 [ 1 + N 2 + 1 , N ] [\frac{1+N}{2}+1,N] [21+N+1,N]中的 c n t cnt cnt条指令。
而终点在 < s x , s y > <sx,sy> <sx,sy>,还需要走 < s x − x , s y − y > <sx-x,sy-y> <sx−x,sy−y>这么远,那么只需要在 a a a中对应三元组即可。
为了快速在 a a a中找到坐标 < s x − x , s y − y > <sx-x,sy-y> <sx−x,sy−y>,可以对 a a a排序,二分查找。
也可以利用 u n o r d e r e d _ m a p < p a i r < i n t , i n t > , m a p < i n t , i n t > > A unordered\_map<pair<int,int>, map<int,int>>A unordered_map<pair<int,int>,map<int,int>>A,其中 A [ < x , y > ] [ c n t ] A[<x,y>][cnt] A[<x,y>][cnt]表示在 [ 1 , 1 + N 2 ] [1,\frac{1+N}{2}] [1,21+N]中选择 c n t cnt cnt条指令,使得 < Δ x , Δ y > <\Delta x,\Delta y> <Δx,Δy>等于 < x , y > <x,y> <x,y>的方案数。
要使得 p a i r pair pair在 u n o r d e r _ m a p unorder\_map unorder_map中正常使用,需要提供一个 h a s h hash hash策略,此处模板参考:https://www.zhihu.com/question/30921173/answer/1248680260。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> Pair;
int n;
Pair node[50], s;
///<r.first, r.second>表示到达点r.first处,经过r.second条指令
vector<pair<Pair, int>> a, b;
ll ans[50];
//链接:https://www.zhihu.com/question/30921173/answer/1248680260
template <typename T>
inline void hash_combine(std::size_t &seed, const T &val) {
seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
// auxiliary generic functions to create a hash value using a seed
template <typename T> inline void hash_val(std::size_t &seed, const T &val) {
hash_combine(seed, val);
}
template <typename T, typename... Types>
inline void hash_val(std::size_t &seed, const T &val, const Types &... args) {
hash_combine(seed, val);
hash_val(seed, args...);
}
template <typename... Types>
inline std::size_t hash_val(const Types &... args) {
std::size_t seed = 0;
hash_val(seed, args...);
return seed;
}
struct pair_hash {
template <class T1, class T2>
std::size_t operator()(const std::pair<T1, T2> &p) const {
return hash_val(p.first, p.second);
}
};
void all_subset(int l, int r, vector<pair<Pair, int>>& Subset)
{
int n = r - l + 1;
//二进制枚举[l,r]中的所有指令选择还是不选择
for(int i = 0; i < (1 << n); i++)
{
Pair now(0, 0);
int cnt = 0;
for(int j = 0; j < n; j++)if(i & (1 << j))
{
now.first += node[j + l].first;
now.second += node[j + l].second;
cnt += 1;
}
Subset.push_back(make_pair(now, cnt));
}
}
int main()
{
cin >> n;
cin >> s.first >> s.second;
for(int i = 1; i <= n; i++)
cin >> node[i].first >> node[i].second;
int mid = (1 + n) >> 1;
all_subset(1, mid, a);
all_subset(mid + 1, n, b);
//将每个点 经过指令的数量 进行统计
unordered_map<Pair, map<int,int>, pair_hash> A;
for(auto x : a)
A[x.first][x.second]++;//A[i][j]表示到达点i,经过j条指令的方案数
for(auto x : b)
{
Pair now = x.first; //当前处于(now.first, now.second)处
int cnt = x.second; //经过了cnt条指令
//最终要走到(s.first, s.second)
//相当于从(now.first,now.second)走(s.first-now.first, s.second-now.second)
Pair need_run(s.first-now.first, s.second-now.second);
auto it = A.find(need_run);
if(it != end(A))
{
//it->second为map<int,int>, 表示经过key条指令的方案数为value
for(auto every : it->second)
{
ans[cnt + every.first] += every.second;
}
}
}
for(int i = 1; i <= n; i++)
cout<<ans[i]<<endl;
return 0;
}
E 区间开根
题意: 给定长度为
n
n
n的数组
a
a
a,存在
m
m
m次操作:
1
l
r
1\ l\ r
1 l r:询问区间
[
l
,
r
]
[l,r]
[l,r]数字之和,
2
l
r
2\ l\ r
2 l r:将区间
[
l
,
r
]
[l,r]
[l,r]的每个数字均开根号向下取整。
Tag: 线段树
难度: ☆☆☆☆
来源: B Z O J 3211 BZOJ\ 3211 BZOJ 3211
思路: 区间开根号是一个经典的线段树问题。
每个数字开一次根号之后,数字迅速减小,一个数字开根号的次数是 l o g log log级别的。
最终的状态要么数字本身是 0 0 0,要么数字开根号变成 1 1 1。如果一段区间都为 0 0 0或者 1 1 1,那么说明这段区间并不需要再次开根号。因为此时已经达到了最终状态。
那么如何判断一个区间均为 0 0 0或者 1 1 1? 利用线段树维护最大值。如果最大值不超过 1 1 1,则不需要继续递归。
这样对线段树的更新操作的递归出口进行修改:
- 只要当前节点最大值不超过 1 1 1,不需要再次递归
- 如果当前是叶子节点,暴力开根号
这样每次操作相当于对大于 1 1 1的数字暴力开根号。而最多开 n log ( n ) n\log(n) nlog(n)次之后,整个数组变成 1 1 1或者 0 0 0。
同时维护区间和,保证在 l o g log log的时间内完成询问。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
int n, m;
int a[maxn];
int ma[maxn << 2];
ll sum[maxn << 2];
void push_up(int o)
{
ma[o] = max(ma[o << 1], ma[o << 1 | 1]);
sum[o] = sum[o << 1] + sum[o << 1 | 1];
}
void build(int o, int l, int r)
{
if(l == r)
{
ma[o] = sum[o] = a[l];
return;
}
int mid = (l + r) >> 1;
build(o << 1, l, mid);
build(o << 1 | 1, mid + 1, r);
push_up(o);
}
//把区间[L,R]开根号
void update(int o, int l, int r, int L, int R)
{
//区间[l,r]最大值不超过1,说明整个区间均为1或者0
if(ma[o] <= 1)
return;
//叶子结点
if(l == r)
{
ma[o] = sum[o] = sqrt(ma[o]);
return;
}
int mid = (l + r) >> 1;
if(L <= mid)update(o << 1, l, mid, L, R);
if(R > mid)update(o << 1 | 1, mid + 1, r, L, R);
push_up(o);
}
//查询区间[L,R]的和
ll query(int o, int l, int r, int L, int R)
{
if(L <= l && r <= R)
return sum[o];
int mid = (l + r) >> 1;
ll ans = 0;
if(L <= mid)ans += query(o << 1, l, mid, L, R);
if(R > mid)ans += query(o << 1 | 1, mid + 1, r, L, R);
return ans;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
cin >> n;
for(int i = 1; i <= n; i++)
cin >> a[i];
build(1, 1, n);
cin >> m;
while(m--)
{
int op, l, r;
cin >> op >> l >> r;
if(op == 1)cout<<query(1, 1, n, l, r)<<endl;
else update(1, 1, n, l, r);
}
return 0;
}