前面两道阅读理解直接跳过。
C - Sigma Problem
大意
有一函数。
给定长度为的序列,求。
思路
我们将变一下:
那么可以先算出,再减去满足以下条件的数对个数:
考虑求前式,由于每项都相加次,所以可以改写为,用求。
接下来,求的整数对的个数。
将从小到大排序,我们可以发现,随着的增大,满足的最小值是单调递减的,可以用双指针(放缩法)求得。注意在的情况下不能重复计算。
代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int mod = 1e8;
#define int long long
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n;
cin >> n;
vector<int> a(n);
for (auto &i: a) cin >> i;
sort(a.begin(), a.end());
int ans = 0, cnt = 0, r = n;
for (int i = 0; i < n; i++){
ans += a[i] * (n - 1);
r = max(r, i + 1);
while(r - 1 > i && a[r - 1] + a[i] >= mod) r--;
cnt += n - r;
}
ans -= cnt * mod;
cout << ans << endl;
return 0;
}
D - Another Sigma Problem
大意
定义表示把直接拼接到后面所形成的整数。
给定长度为的序列,求。
思路
设为的位数,则可以式子变形为.
将每个数的贡献拆成两个部分计算:,。
前者很容易,因为被原封不动地加了次,因此对答案的贡献为。
接着考虑后者,可以使用后缀和,设表示中位数的个数,那么对答案的贡献为。
代码
#include <iostream>
#include <vector>
#include <string>
#include "atcoder/modint"
using namespace std;
typedef atcoder::modint998244353 Z;
int main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n;
cin >> n;
vector<int> a(n), d(11);
auto len = [&](int x){
return to_string(x).size();
};
for(auto &i: a){
cin >> i;
d[len(i)]++;
}
Z ans = 0;
vector<Z> xp(11, 1);
for(int i = 1; i <= 10; i++) xp[i] = xp[i - 1] * 10;
for(int i = 0; i < n; i++){
ans += Z(a[i]) * i;
d[len(a[i])]--;
for(int j = 1; j <= 10; j++) ans += xp[j] * a[i] * d[j];
}
cout << ans.val() << endl;
return 0;
}
E - Yet Another Sigma Problem
大意
定义表示字符串和字符串的最长公共前缀的长度。
给定个字符串,求。
思路
看到LCP想Trie树,因为它会把所有具有相同前缀的字符串合并。
我们依次将每个字符串插入Trie树,每个节点都保留了以它所代表的字符串为前缀的字符串的数量信息。
因此在到达每个节点后,答案都加上。
最后输出答案即可。
代码
#include <iostream>
#include <array>
#include <vector>
using namespace std;
#define int long long
struct trie {
using M = array<int, 26>;
vector<M> son;
M init;
int size, ans;
vector<int> cnt;
trie() = default;
trie(int len) {
son.reserve(len + 1);
cnt.reserve(len + 1);
init.fill(-1);
size = ans = 0;
newnode();
}
int newnode() {
son.push_back(init);
cnt.push_back(0);
return size++;
}
void insert(string &s) {
int p = 0;
for(int i = 0; i < s.size(); i++) {
int u = s[i] - 'a';
int &nx = son[p][u];
if(nx == -1) nx = newnode();
p = nx;
ans += cnt[p]++;
}
}
};
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n;
cin >> n;
trie T(3e5);
for(int i = 0; i < n; i++) {
string s;
cin >> s;
T.insert(s);
}
cout << T.ans << endl;
return 0;
}
F - Tile Distance
大意
给定,其定义了一个网格:
其中表示的小格子,表示的大格子。
从一个格子走到另一个格子的代价是。
给定起点和终点,问从起点到终点的最小代价。
思路
显然,最多只能走步,易知这是时的答案。
接下来看的情况。
我们可以知道,如果想要更优的方案,那么需要穿过大格子,否则代价就是上限。
考虑对实现答案的移动中到达的第一对大格子和最后一对大格子进行枚举。
可能到达的第一对大格子为:
- 如果起点处于大格子,那么就是它。
- 否则,四周的大格子都有可能。
最后一对同理。
我们分别计算出起终点绕到上述大格子的距离。
剩下的,只需求得在大格子上移动的费用即可。
这就完了吗?并没有。
我们看的情况,发现有时不用绕,直接穿过反而代价更小。
特判一下即可。
代码
#include <iostream>
#include <tuple>
#include <vector>
using namespace std;
typedef unsigned long long ULL;
typedef tuple<ULL, ULL, ULL> P;
int main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
ULL k, sx, sy, tx, ty;
cin >> k >> sx >> sy >> tx >> ty;
sx += k, sy += k;
tx += k, ty += k;
auto diff = [&](ULL p, ULL q) -> ULL{
return max(p, q) - min(p, q);
};
auto getpos = [&](ULL x, ULL y) -> vector<P> {
vector<P> pos;
if(((x / k) ^ (y / k)) & 1)
pos.emplace_back(x / k, y / k, 0);
else{
pos.emplace_back(x / k - 1, y / k, 1 + x % k); // 左
pos.emplace_back(x / k + 1, y / k, k - x % k); // 右
pos.emplace_back(x / k, y / k - 1, 1 + y % k); // 下
pos.emplace_back(x / k, y / k + 1, k - y % k); // 上
}
return pos;
};
ULL ans = diff(sx, tx) + diff(sy, ty);
if(1 < k){
vector<P> start = getpos(sx, sy);
vector<P> goal = getpos(tx, ty);
if(k == 2){
for(auto& [x, y, d1] : start)
for(auto& [z, w, d2] : goal){
ULL x_diff = diff(x, z);
ULL y_diff = diff(y, w);
ans = min(ans, d1 + d2 + x_diff + y_diff + diff(x_diff, y_diff) / 2);
}
}else{
for(auto& [x, y, d1] : start)
for(auto& [z, w, d2] : goal)
ans = min(ans, d1 + d2 + diff(x + y, z + w) + diff(x + w, z + y));
}
}
cout << ans << endl;
return 0;
}