牛客周赛38
A 小红的正整数自增
小红拿到了一个正整数,她每次操作可以使得这个正整数加 1。
小红想知道,使得该数的末尾为 0 至少需要操作多少次?
void solve() {
string s;cin >> s;
int x = s.back() - '0';
if (!x) {
cout << "0\n";return;
}
cout << 10 - x << '\n';
}
B 小红的抛弃后缀
小红得到一个正整数,她打算删去一个数字并丢弃,以便剩下的部分是 9 的倍数。小红想知道有多少种不同的操作方案?
#define int long long
void solve() {
string s;cin >> s;s = " " + s;
vector<int> pre(s.size() + 1);
for (int i = 1;i < s.size();i++) {
pre[i] = pre[i - 1] + s[i] - '0';
}
int ans = 0;
for (int i = 1;i < s.size();i++) {
if (pre[i] % 9 == 0) {
ans++;
}
}
cout << ans << '\n';
}
C 小红的字符串构造
小红想让你创建一个长度为 n 的字符串,只包含小写字母,里面正好有 k 个长度大于 1 的回文子串。你能帮帮她吗?
D 小红的平滑值插值
现在小红手头有个数组,她想调整其中数值的顺序来使得最终数组的平均值正好是 k。你能帮忙计算出最少需要操作几次吗?
#define int long long
void solve() {
int n, k;cin >> n >> k;vector<int> a(n + 1);
for (int i = 1;i <= n;i++)cin >> a[i];
int mx = 0;
for (int i = 2;i <= n;i++) {
mx = max(mx, abs(a[i] - a[i - 1]));
}
if (mx == k) {
cout << "0\n";return;
}
if (mx < k) {
cout << "1\n";return;
}
int ans = 0;
for (int i = 2;i <= n;i++) {
if (abs(a[i] - a[i - 1]) > k) {
ans += (abs(a[i] - a[i - 1]) - 1) / k;
}
}
cout << ans << '\n';
}
E 小苯的等比数列
小苯很喜欢等比数列。有一天他得到了一个长为 n 的数组 a,他想从里面选一些数字使得被选中的数字构成一个等比数列,请问他最多可以选择多少个数字呢?
注意:小苯选择的等比数列公比需要是正整数。
等比数列公比不可能会很大,这里只需要列到 10 就可以过了。
#define int long long
void solve() {
int n;cin >> n;map<int, int>mp;
for (int i = 1;i <= n;i++) {
int x;cin >> x;mp[x]++;
}
int ans = 0;
for (auto [x, y] : mp) {
ans = max(ans, y);
}
for (auto [x, y] : mp) {
int res = 0;
for (int i = 2;i * x<=2e5;i++) {
int k = x;
res = 0;
while (mp.count(k)) {
res++;k *= i;
}
ans = max(ans, res);
}
}
cout << ans << '\n';
}
F 小苯的回文询问
小苯有一个长度为
他现在进行了
Solution
这题只要满足有 3 个字符,第一个字符和第三个字符相等,则一定满足要求。
可以通过预处理。jd
数组来记录上一个最近满足回文字符串要求的下标,mp
来映射数组的下标,每次遇到满足要求的情况,取最大下标即可。
void solve() {
int n, q;cin >> n >> q;
map<int, int> mp;
vector<int>a(n + 1), jd(n + 1);
for (int i = 1;i <= n;i++) {
cin >> a[i];jd[i] = jd[i - 1];
if (mp.count(a[i])) {
if (mp[a[i]] < i - 1)jd[i] = max(jd[i], mp[a[i]]);
}
if (i >= 3 && a[i] == a[i - 2])jd[i] = max(jd[i], i - 2);
mp[a[i]] = i;
}
while (q--) {
int l, r;cin >> l >> r;
if (r > 2 && jd[r] >= l)cout << "YES\n";
else cout << "NO\n";
}
}
G 小红的区间删除
小红有一个长度为 n 的数组 a,他想从 a 中删除一段区间,但又要使得剩余的部分拼起来组成的新数组满足:逆序对个数不少于 k。
她想知道有多少种删除方案,请你帮帮她吧。
注:空数组逆序对数量为 0。
Solution
树状数组+滑动窗口/逆序对
为什么没开全局 ll 就会 tle?
两个树状数组,一个记录前缀,一个记录后缀。
计算逆序对可以从后往前遍历,每次看有多少个数字的下标比该数字小的,每次累加,就是逆序对的个数
for (int i = n;i >= 1;i--) {
change(tr2, a[i], 1);
sum += query(tr2, a[i] - 1);
}
也可从前往后遍历:每次看有多少下标比该数字大即可。
for (int i = 1;i <= n;i++) {
change(tr1, a[i], 1);
sum += query(tr2, max(a)) - query(tr2, a[i]);
}
从下标前往后删除数,减去相应的逆序对数,若仍然满足逆序对数
#define int long long
int a[200010];
int n, k, tr1[1000010], tr2[1000010];
int lowbit(int x) {
return x & -x;
}
void change(int tr[], int x, int k) {
while (x <= 1e6)tr[x] += k, x += lowbit(x);
}
int query(int tr[], int x) {
int ans = 0;
while (x)ans += tr[x], x -= lowbit(x);
return ans;
}
void solve() {
cin >> n >> k;
for (int i = 1;i <= n;i++) {
cin >> a[i];
}
int sum = 0;
for (int i = n;i >= 1;i--) {
change(tr2, a[i], 1);
sum += query(tr2, a[i] - 1);//算出的结果就是逆序对的总对数
}
int ans = (sum >= k);
for (int i = 1, j = 1;i <= n;i++) {
change(tr2, a[i], -1);
sum -= query(tr2, a[i] - 1);
sum -= query(tr1, 1e6) - query(tr1, a[i]);
while (j <= i && sum < k) {
change(tr1, a[j], 1);
sum += query(tr2, a[j] - 1);
sum += query(tr1, 1e6) - query(tr1, a[j]);
j++;
}
ans += (i - j + 1);
}
cout << ans << '\n';
}