Harbin Engineering University Collegiate Programming Contest 2021
A. stral Reflection
题意:
在长 n 的数轴上,有 m 次操作,第一种是学习技能(释放消耗一点体力),可以清除区间 [ l, r ] 内的陨石,第二种操作是查询能否清除给定位置的 k 个陨石或最少需要多少体力才能清除。
Idea:
对于每个陨石,清除它时肯定是想要顺带清除更多陨石,我们选择从左到右来看,清除左边的陨石时就应该选择包含改点且右端点最靠右的技能。对于学习操作可以使用线段树维护每个点上的技能能达到的最右点,询问操作单点查询加区间覆盖即可。再更进一步,其实不需要维护每个点,只维护每个技能的左端点所在的点即可,查询 p 点上最右边的技能等同于区间查询 [ 1, p ] 上最右边的技能,该操作使用树状数组就能完成。
Code:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n, m, rgt[N], a[N];
int lowbit(int x)
{
return x & -x;
}
void change(int x, int v)
{
while (x <= n)
{
rgt[x] = max(rgt[x], v);
x += lowbit(x);
}
}
int query(int r)
{
int ret = 0;
while (r)
{
ret = max(rgt[r], ret);
r -= lowbit(r);
}
return ret;
}
int main()
{
cin >> n >> m;
while (m--)
{
int t, l, r, w, k;
cin >> t;
if (t == 1)
{
cin >> l >> r >> w;
change(l, r);
}
else
{
cin >> k;
for (int i = 1; i <= k; i++)
cin >> a[i];
int mx = query(a[1]);
int ans = 1;
//区间覆盖
for (int i = 1; i <= k; i++)
{
if (mx >= a[i])
continue;
mx = query(a[i]);
//该点未被覆盖则直接跳出
if (mx < a[i])
{
cout << "-1\n";
goto ss;
}
ans++;
}
cout << ans << '\n';
}
ss:;
}
return 0;
}
C. hivalric Blossom
题意:
有n个任务,任务都有优先级,按照任务优先级且编号递增执行任务,给定m个任务对,执行完一对任务中的第一个任务后必须马上执行相应的第二个任务,要求为n个任务给出优先级用数字表示,用到的数字种类要尽可能少。
Idea:
分析题目,每对两个任务得优先级肯定是要相同的,要求优先级种类尽可能少,考虑贪心。将每个对看做一条有头有尾的链,没有被纳入的点自身为一条链,预处理数轴上每个点的前驱后继。编号从小到大遍历数轴对链染色,将颜色存在一个栈中,遇到头部点就拿出栈顶为其染色,遇到尾部点就染上与它的头相同的颜色并将该颜色压回栈中重复利用。按顺序输出即可。
Code:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n, m;
int a[N];
pair<int, int> line[N];
int main()
{
cin >> n >> m;
for (int i = 0, t1, t2; i < m; i++)
{
cin >> t1 >> t2;
//把任务对放到数轴上,标示每个点的前驱后继
line[t1].second = t2;
line[t2].first = t1;
}
stack<int> st;
//存入所有颜色
for (int i = n; i >= 1; i--)
st.push(i);
for (int i = 1; i <= n; i++)
{
//尾部点(有前驱的点)
if (line[i].first)
a[i] = a[line[i].first];
//头部点
else
{
a[i] = st.top();
st.pop();
}
if (!line[i].second)
st.push(a[i]);
}
for (int i = 1; i <= n; i++)
cout << a[i] << ' ';
return 0;
}
D. andelion Knight
题意:
给定两个长度为n的01序列a,b,求对于每个 x ∈ \in ∈ [ 0, 2n ],在a,b中各选择一个前缀序列,两个序列长度和为x,且两个序列之和相等,问有几种选法,依次打印出对应的选法数。
Idea:
要求选出的前缀和相同,就可以先考虑求出a,b的前缀和。设 (x,y) 为在a中选前a个数,b中选前y个数。如果prea中的数在preb中找到相同的则构成一个答案。预处理出preb中每个数出现的第一个和最后一个位置 l,r
则对于 i ∈ \in ∈ [ 0, n ],若 p r e b preb preb 中存在 p r e a i prea_i preai ,则对于 y ∈ \in ∈ [ l[ p r e a i prea_i preai], r[ p r e a i prea_i preai] ], (i, y) 可对 a n s ( i + y ) ans_(i+y) ans(i+y) 的贡献加一,遍历 i ∈ \in ∈ [ 0, n ] 用差分数组对 a n s ( i + y ) ans_(i+y) ans(i+y) 进行区间加即可。
Code:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int n, a[N], b[N], pa[N], pb[N], l[N], r[N], ans[N * 2], n2;
int main()
{
cin >> n;
n2 = n * 2;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
pa[i] = pa[i - 1] + a[i];
}
for (int i = 1; i <= n; i++)
{
cin >> b[i];
pb[i] = pb[i - 1] + b[i];
}
for (int i = 0; i <= n; i++)
l[i] = r[i] = -1;
//预处理l,r数组
for (int i = 0; i <= n; i++)
{
if (l[pb[i]] == -1)
l[pb[i]] = i;
if (i > 0 && pb[i] != pb[i - 1])
r[pb[i - 1]] = i - 1;
}
r[pb[n]] = n;
for (int i = 0; i <= n; i++)
{
//l[pa[i]] 不存在则退出
if (l[pa[i]] == -1)
break;
ans[i + l[pa[i]]]++;
ans[i + r[pa[i]] + 1]--;
}
cout << "1 ";
for (int i = 1; i <= n2; i++)
{
cout << (ans[i] += ans[i - 1]) << ' ';
}
return 0;
}
E. clipsing Star
题意:
你和凝光玩n轮游戏,每轮游戏有 a i a_i ai 摩拉。对于每轮游戏先手者可选择拿 b i b_i bi 摩拉,剩下的则另一个人拿,该轮结束后有 b i a i b_i\over a_i aibi 的概率两人交换先后手,你们都有最优决策,求最后你和凝光拿到的摩拉差最大是多少。
Idea:
最后一轮的先手者必定拿走所有摩拉,则考虑第 n-1 轮的先手者的决策。假设 n-1 轮先手者选择拿 b ( n − 1 ) b_(n-1) b(n−1) 摩拉,则他在第 n 轮预计拿到 b ( n − 1 ) a n a ( n − 1 ) b_(n-1)a_n\over a_(n-1) a(n−1)b(n−1)an 。最后两轮总共拿到 b ( n − 1 ) + b_(n-1) + b(n−1)+$ b_(n-1)a_n\over a_(n-1)$ 摩拉,只与第 n-1 轮的选择有关,且是一个一次函数,其最值在定义域两端取到。所以对于第 n-1 轮的先手者最优决策是要么拿全部的 a ( n − 1 ) a_(n-1) a(n−1), 把 a n a_n an 让给对方,要么把 a ( n − 1 ) a_(n-1) a(n−1) 让给对方,拿全部的 a n a_n an ,第 n-1 轮先手者在最后两轮总是可以比另一人多拿 ∣ a n − a ( n − 1 ) ∣ |a_n - a_(n-1)| ∣an−a(n−1)∣ 摩拉。因为我们最后只需要求两人的差,所以可以把最后两轮看成一轮,值为 ∣ a n − a ( n − 1 ) ∣ |a_n - a_(n-1)| ∣an−a(n−1)∣ 。不断将最后两轮归纳,直到只剩一轮时,就是答案。
Code:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define IO \
ios::sync_with_stdio(false); \
cin.tie(0); \
cout.tie(0);
const int N = 1e6 + 5;
int a[N];
signed main()
{
IO;
int n;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = n - 1; i >= 1; i--)
{
a[i] = abs(a[i] - a[i + 1]);
}
printf("%lf", (double)a[1]);
return 0;
}
J. uvenile Galant
题意:
用以下两种片段拼成(可旋转)长度为 n 的给定样式的剑,问有几种拼法。
Idea:
可以看出剑的最后一段必须是未经过旋转的这两种片段。若不旋转,则第一种片段必须接在平头的地方,第二种片段必须接在另一把剑后面。则长度为 n 的剑的拼法数等于长度为 n-2 的平头的和尖头的剑的拼法之和。定义 d p [ i ] [ 0 ] dp[i][0] dp[i][0] 和 d p [ i ] [ 1 ] dp[i][1] dp[i][1] 分别为长度为 i 的平头和尖头的剑的拼法,状态转移方程:
d p [ i ] [ 0 ] = d p [ i − 1 ] [ 1 ] ∗ 2 dp[i][0] = dp[i-1][1] * 2 dp[i][0]=dp[i−1][1]∗2
d p [ i ] [ 1 ] = d p [ i − 2 ] [ 0 ] + d p [ i − 2 ] [ 1 ] dp[i][1] = dp[i-2][0] + dp[i-2][1] dp[i][1]=dp[i−2][0]+dp[i−2][1]
Code:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define IO \
ios::sync_with_stdio(false); \
cin.tie(0); \
cout.tie(0);
const int N = 2e6 + 5;
int dp[N][2];
int mod = 998244353;
signed main()
{
int n;
cin >> n;
dp[2][1] = 1;
dp[0][0] = 1;
for (int i = 2; i <= n; i++)
{
dp[i][1] = (dp[i - 2][1] + dp[i - 2][0]) % mod;
dp[i][0] = (dp[i - 1][1] * 2) % mod;
}
cout << dp[n][1];
return 0;
}