A.Buildings
题意
给出若干个建筑,每个建筑有一个高度,问,从第二个建筑开始,比第一个建筑高的建筑中编号最小的是多少?如果不存在,输出-1.
分析
边输入边比较即可,如果循环结束还未找到,输出-1
.
代码
#include<bits/stdc++.h>
using namespace std;
int a[200005];
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
if (i - 1 && a[i] > a[1]) {
cout << i << endl;
return;
}
}
cout << -1 << endl;
}
int main() {
solve();
return 0;
}
B.AtCoder Amusement Park(模拟)
题意
有 n n n个小团体需要乘坐小船,第 i i i个团体有 a i a_i ai个人,而小船只有 k k k个座位。同时,小团体需要按照给出的顺序来乘坐小船,且同一个团体中的人必须乘坐同一艘小船,问,需要多少只小船,才能满足所有的小团体。
分析
模拟即可,每次检查当前小船是否还能放下当前的团体人数,如果够,就加入当前小船中,如果不够,开一艘新的小船,用于放置当前的小团体。
代码
#include<bits/stdc++.h>
using namespace std;
int a[200005];
void solve() {
int n, k;
cin >> n >> k;
int sum = 0, cnt = 1;
for (int i = 1; i <= n; i++) {
int a;
cin >> a;
sum += a;
if (sum > k) {
sum = a;
cnt++;
}
}
cout << cnt << endl;
}
int main() {
solve();
return 0;
}
C.Sigma Problem(前缀和+二分)
题意
给出一个函数 f ( x , y ) = ( x + y ) f(x, y) = (x + y) % 10^{8} f(x,y)=(x+y),求:
- ∑ i = 1 n − 1 ∑ j = i + 1 n f ( a i , a j ) \sum\limits_{i = 1}^{n - 1}\sum\limits_{j = i + 1}^{n} f(a_i, a_j) i=1∑n−1j=i+1∑nf(ai,aj)。
分析
由于给出的函数 f ( x , y ) f(x, y) f(x,y)是将两个数字相加,而加法是可交换的,那么 f ( x , y ) = f ( y , x ) f(x, y) = f(y, x) f(x,y)=f(y,x)。 然后求和要求的是任意两个不同数字经过函数运算后相加的结果,那么任意交换给出的数字是不会改变答案的。
因此,为了便于处理,可以把数组排序。
排序完成后,可以先维护前缀和,因为不难发现,对于每个 a [ i ] a[i] a[i],均需要与前面每个数字进行一次函数运算,那么在不考虑取模的情况下,产生的结果为 p r e [ i − 1 ] + ( i − 1 ) × a [ i ] pre[i - 1] + (i - 1) \times a[i] pre[i−1]+(i−1)×a[i],其中 p r e [ i − 1 ] pre[i - 1] pre[i−1]表示前 i − 1 i - 1 i−1个数字的前缀和。
然后考虑取模需要减掉的部分,不难想到,对于 x + y < 1 0 8 x + y < 10^{8} x+y<108的情况,是不需要进行取模的,而 x + y ≥ 1 0 8 x + y \ge 10^{8} x+y≥108时,则每次运算的取模均相当于减去一个 1 0 8 10^{8} 108,由于数组已经经过排序了,那么只需要通过二分对 1 0 8 − a [ i ] 10^{8} - a[i] 108−a[i]进行查找,就可以知道需要减去的数字所在的起点是多少,那么这个查找到的位置到下标 i − 1 i - 1 i−1之间的所有数字与当前数字 a [ i ] a[i] a[i]进行运算,均需要减掉 1 0 8 10^{8} 108,令 c n t cnt cnt为需要减去的数量,则 a [ i ] a[i] a[i]产生的贡献为
- p r e [ i − 1 ] + ( i − 1 ) × a [ i ] − c n t ∗ 1 0 8 pre[i - 1] + (i - 1) \times a[i] - cnt * 10^{8} pre[i−1]+(i−1)×a[i]−cnt∗108
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e8;
ll a[300005], pre[300005];
void solve() {
ll n;
ll ans = 0;
cin >> n;
for (ll i = 1; i <= n; i++) {
cin >> a[i];
}
sort(a + 1, a + n + 1);
for (ll i = 1; i <= n; i++) {
pre[i] = pre[i - 1] + a[i];
}
for (ll i = 2; i <= n; i++) {
int id = lower_bound(a + 1, a + i, mod - a[i]) - a;
ans += pre[i - 1] + (i - 1) * a[i] - (mod * (i - id));
}
cout << ans << endl;
}
int main() {
solve();
return 0;
}
D.Another Sigma Problem(后缀和)
题意
给出一个函数 f ( x , y ) = x y f(x, y) = xy f(x,y)=xy
例:
-
f ( 3 , 14 ) = 314 f(3, 14) = 314 f(3,14)=314
-
f ( 100 , 1 ) = 1001 f(100, 1) = 1001 f(100,1)=1001
请你求出以下操作取模后的结果:
- ∑ i = 1 n − 1 ∑ j = i + 1 n f ( a i , a j ) \sum\limits_{i = 1}^{n - 1}\sum\limits_{j = i + 1}^{n}f(a_i, a_j) i=1∑n−1j=i+1∑nf(ai,aj)
分析
当前给出的函数 f ( x , y ) f(x, y) f(x,y)可以将数字分为两个部分,前半部分需要乘上 1 0 y的十进制位数 10^{\text{y的十进制位数}} 10y的十进制位数,后半部分直接加上。
那么对于 a i a_i ai来说,与前面的 i − 1 i - 1 i−1个数字运算后,产生的贡献为 ( i − 1 ) × a i (i - 1) \times a_i (i−1)×ai,与后面的 n − i n - i n−i个数字运算后,产生的贡献为 ∑ j = 1 10 1 0 j × c n t j × a i \sum\limits_{j = 1}^{10} 10^{j} \times cnt_j \times a_i j=1∑1010j×cntj×ai,其中 c n t j cnt_j cntj表示 a i + 1 ∼ a n a_{i + 1} \sim a_n ai+1∼an中十进制位数为 j j j的数字数量。
对于 ( i − 1 ) × a i (i - 1) \times a_i (i−1)×ai,可以直接通过计算得到,而 c n t j cnt_j cntj可以通过预处理后缀和来快速计算得到,即使用 n x t [ i ] [ j ] nxt[i][j] nxt[i][j]表示 i ∼ n i \sim n i∼n之间十进制位数为 j j j的数字个数.
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 998244353;
ll a[300005], nxt[300005][15];
int getlen(int x) {
int cnt = 0;
while (x) {
cnt++;
x /= 10;
}
return cnt;
}
void solve() {
ll n;
ll ans = 0;
cin >> n;
for (ll i = 1; i <= n; i++) {
cin >> a[i];
}
for (ll i = n; i >= 1; i--) {
for (int j = 1; j <= 10; j++) {
nxt[i][j] = nxt[i + 1][j];
}
nxt[i][getlen(a[i])]++;
}
for (ll i = 1; i <= n; i++) {
ans += (i - 1) * a[i] % mod;
ans %= mod;
ll mul = 1;
for (int j = 1; j <= 10; j++) {
mul *= 10;
ans = (ans + mul * nxt[i + 1][j] % mod * a[i] % mod) % mod;
}
}
cout << ans << endl;
}
int main() {
solve();
return 0;
}
E.Yet Another Sigma Problem(哈希)
题意
给出一个函数 f ( x , y ) f(x, y) f(x,y),表示字符串 x x x和 y y y的最长公共前缀长度。
请你求出:
- ∑ i = 1 n − 1 ∑ j = i + 1 n f ( S i , S j ) \sum\limits_{i = 1}^{n - 1}\sum\limits_{j = i + 1}^{n}f(S_i, S_j) i=1∑n−1j=i+1∑nf(Si,Sj)。
分析
可以不用直接考虑当前字符串与前面所有字符串中的最长公共前缀长度,而是依次考虑长为 1 ∼ S i . s i z e ( ) 1 \sim S_i.size() 1∼Si.size()的前缀在前面的字符串中作为前缀的个数。
为什么可以这么做?
不难想到,此时将所有长度的字符串均设置为贡献1,那么长度为1的前缀会产生1点贡献,长度为2的前缀也会产生1点贡献,依次类推,最后计算出的贡献与所求的答案是一致的。
而字符串匹配是比较麻烦的,很难快速的进行检查,因此,可以使用哈希将字符串转化为数字(为了避免冲突,这里选择了双模数),而转化成的数字范围也是较大的,使用数组并不方便维护,可以使用map
进行维护,即
m
a
p
[
x
]
=
y
map[x] = y
map[x]=y表示字符串
x
x
x作为前缀的出现次数为
y
y
y。
那么对于每个字符串,枚举这个字符串所有的前缀字符串,然后加上这个前缀在前面字符串中的出现次数,并将该前缀更新到map
中。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
map<pair<int, int>, int> H;
int base = 131, mod[] = {1000000007, 998244353};
void solve() {
int n;
cin >> n;
ll ans = 0;
for (int i = 1; i <= n; i++) {
string s;
ll hashCode[5] = {0, 0};
cin >> s;
for (auto j : s) {
hashCode[0] = (hashCode[0] * base % mod[0] + j) % mod[0];
hashCode[1] = (hashCode[1] * base % mod[1] + j) % mod[1];
ans += H[make_pair(hashCode[0], hashCode[1])];
H[make_pair(hashCode[0], hashCode[1])]++;
}
}
cout << ans << endl;
}
int main() {
solve();
return 0;
}
F.Tile Distance(思维)
题意:
在一个坐标平面上。有两种格子:大小为 1 × 1 1\times1 1×1 的小格子和大小为 K × K K\times K K×K 的大格子,它们按以下规则摆放:
- 对于每一对整数
(
i
,
j
)
(i,j)
(i,j) 来说,正方形
{
(
x
,
y
)
∣
i
≤
x
≤
i
+
1
∧
j
≤
y
≤
j
+
1
}
\lbrace(x,y)\mid i\leq x\leq i+1\wedge j\leq y\leq j+1\rbrace
{(x,y)∣i≤x≤i+1∧j≤y≤j+1} 要么包含在一块小方格子中,要么包含在一块大方格子中。
- 如果 ⌊ i K ⌋ + ⌊ j K ⌋ \left\lfloor\dfrac iK\right\rfloor+\left\lfloor\dfrac jK\right\rfloor ⌊Ki⌋+⌊Kj⌋ 是偶数,则它包含在一个小方格中。
- 否则,它被包含在一个大方格中。
格子包括它们的边界,没有两个不同的格子有正的交集区域。
小 A A A从坐标平面上的 ( S x + 0.5 , S y + 0.5 ) (S_x+0.5,S_y+0.5) (Sx+0.5,Sy+0.5) 点开始。
他可以重复下面的动作任意多次:
- 选择一个方向(上、下、左或右)和一个正整数 n n n 。向该方向移动 n n n 个单位。
每次他从一张格子移动到另一张格子时,必须支付 1 1 1 的过路费。
求小 A A A到达 ( T x + 0.5 , T y + 0.5 ) (T_x+0.5,T_y+0.5) (Tx+0.5,Ty+0.5) 点所需支付的最小通行费。
分析:
分类讨论,如果两个点都在小格子区域,那么答案为曼哈顿距离,如果两个不在同一个小格子,那么能走大格子,就不要走小格子。假设
s
s
s点在小格子区域里,那么它一定要先走到他四周的大格子中,对于
t
t
t点同理,我们只要考虑所有这些情况即可。
接下来考虑大格子怎么走,可以发现,如果
k
=
2
k=2
k=2,那么直着走是更优的。如果
k
>
2
k>2
k>2,应该尽可能沿着对角线走,这种情况通过把坐标系转度来计算答案。如果
k
=
1
k=1
k=1,那么答案是曼哈顿距离。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};
LL k, sx, sy, tx, ty, ans;
int main() {
LL i, j;
cin >> k >> sx >> sy >> tx >> ty;
ans = abs(sx - tx) + abs(sy - ty);
LL ax = sx / k, ay = sy / k, bx = tx / k, by = ty / k;
for (i = 0; i < 4; i++) {
if (((ax + ay) & 1) && i > 0)
continue;
for (j = 0; j < 4; j++) {
if (((bx + by) & 1) && j > 0)
continue;
LL cost = 0, nax = ax, nay = ay, nbx = bx, nby = by;
if ((ax + ay) % 2 == 0) {
nax += dx[i], nay += dy[i];
if (i == 0)
cost += nay * k - sy;
else if (i == 1)
cost += nax * k - sx;
else if (i == 2)
cost += (sy % k) + 1;
else
cost += (sx % k) + 1;
}
if ((bx + by) % 2 == 0) {
nbx += dx[j], nby += dy[j];
if (j == 0)
cost += nby * k - ty;
else if (j == 1)
cost += nbx * k - tx;
else if (j == 2)
cost += (ty % k) + 1;
else
cost += (tx % k) + 1;
}
if (k == 2) {
LL v1 = abs(nax - nbx), v2 = abs(nay - nby);
if (v1 < v2)
swap(v1, v2);
ans = min(ans, cost + v2 * 2 + (v1 - v2) / 2 * 3);
}
ans = min(ans, cost + abs((nax + nay) - (nbx + nby)) + abs((nax - nay) - (nbx - nby)));
}
}
cout << ans << endl;
return 0;
}
G.Merchant Takahashi(dp)
题意:
问题陈述
有 N N N 个城镇: 1 1 1 、 2 2 2 、 … \ldots … 、 N N N 。从 i i i 镇到 j j j 镇,必须支付 C × ∣ i − j ∣ C \times |i-j| C×∣i−j∣ 日元的过路费。小 A A A现在准备参加 M M M 个或更多即将到来的活动。第 i i i个活动 ( 1 ≤ i ≤ M ) (1 \leq i \leq M) (1≤i≤M) 由一对整数 ( T i , P i ) (T_i, P_i) (Ti,Pi) 描述,其中活动在城镇 T i T_i Ti 举行,如果参加将赚取 P i P_i Pi 日元。
在所有 1 ≤ i < M 1 \le i < M 1≤i<M 中, i i i 次活动在 ( i + 1 ) (i+1) (i+1) 次活动开始之前结束。他移动所花费的时间可以忽略不计。
他从 1 0 1 0 100 10^{10^{100}} 1010100 日元开始,最初在第一个镇子。通过优化选择参与哪些活动以及如何移动,确定他可以获得的最大利润。
形式上,设 1 0 1 0 100 + X 10^{10^{100}} + X 1010100+X 为他在 M M M 个活动后获得的最大资金额,则 1 0 1 0 100 + X 10^{10^{100}} + X 1010100+X 为他的最终资金额。求 X X X 。
分析:
如果选择参加活动,那么需要计算出城市间转移的代价,而该代价取决于之前在哪个城市,因此在
d
p
dp
dp的转移中只需要保留该信息就可以转移了。
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示考虑前
m
m
m个活动后,最后在城市
j
j
j的最大收益。转移
d
p
[
i
]
[
t
i
]
=
m
a
x
1
≤
j
≤
n
(
d
p
[
i
−
1
]
[
j
]
−
c
∣
t
i
−
j
∣
)
+
p
i
dp[i][t_i]=max_{1\le j \le n}(dp[i−1][j]−c|t_i−j|)+p_i
dp[i][ti]=max1≤j≤n(dp[i−1][j]−c∣ti−j∣)+pi。
考虑优化转移,转移为一个区间取最大值,并通过分类讨论,去掉绝对值,将绝对值拆掉后,转移式就变成了两个关于
d
p
[
j
]
+
c
j
dp[j]+c_j
dp[j]+cj和
d
p
[
j
]
−
c
j
dp[j]−c_j
dp[j]−cj 的区间最大值问题,用两个线段树维护这两类值即可。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 8;
const LL INF = 1e18;
class segment {
#define lson (root << 1)
#define rson (root << 1 | 1)
public:
LL maxx[N << 2];
void build(int root, int l, int r, vector<LL> &a) {
if (l == r) {
maxx[root] = a[l - 1];
return;
}
int mid = (l + r) >> 1;
build(lson, l, mid, a);
build(rson, mid + 1, r, a);
maxx[root] = max(maxx[lson], maxx[rson]);
}
void update(int root, int l, int r, int pos, LL val) {
if (l == r) {
maxx[root] = max(maxx[root], val);
return;
}
int mid = (l + r) >> 1;
if (pos <= mid)
update(lson, l, mid, pos, val);
else
update(rson, mid + 1, r, pos, val);
maxx[root] = max(maxx[lson], maxx[rson]);
}
LL query(int root, int l, int r, int L, int R) {
if (L <= l && r <= R) {
return maxx[root];
}
int mid = (l + r) >> 1;
LL tmp1 = -INF, tmp2 = -INF;
if (L <= mid)
tmp1 = query(lson, l, mid, L, R);
if (R > mid)
tmp2 = query(rson, mid + 1, r, L, R);
return max(tmp1, tmp2);
}
} tl, tr;
int main() {
int n, c;
cin >> n >> c;
vector<LL> a(n);
for (int i = 0; i < n; ++i)
a[i] = -INF;
a[0] = 0;
tr.build(1, 1, n, a);
tl.build(1, 1, n, a);
int m;
cin >> m;
LL ans = 0;
for (int i = 0; i < m; ++i) {
int tmp;
LL p;
cin >> tmp >> p;
int id = tmp;
tmp--;
LL dp1 = tl.query(1, 1, n, 1, id) - 1ll * c * tmp;
LL dp2 = tr.query(1, 1, n, id, n) + 1ll * c * tmp;
LL dp = max(dp1, dp2) + p;
ans = max(ans, dp);
tl.update(1, 1, n, id, dp + 1ll * c * tmp);
tr.update(1, 1, n, id, dp - 1ll * c * tmp);
}
cout << ans << endl;
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。