比赛链接:
https://vjudge.net/contest/298098
B - Element Swapping
题目大意:
定义序列的 \(X = \sum_{k = 1}^{n} ka_k\),\(Y = \sum_{k = 1}^{n} ka_k^2\)。
原 \(a\) 序列的 \(X\) 和 \(Y\) 为 \(x\) 和 \(y\)。
现在已知交换了 \(a\) 序列中某两个数后的序列,问有可以找到多少对 \((i, j)\),交换 \(a_i, a_j\) 后求出的 \(X\) 和 \(Y\) 等于给定的 \(x\) 和 \(y\)。
思路:
定义当前序列的 \(X\) 和 \(Y\) 为 \(p\) 和 \(q\)。
若 \(i\) 和 \(j\) 交换后可以满足条件,即交换 \(i\) 和 \(j\) 的位置后,\(p\) 变成了 \(x\),\(q\) 变成了 \(y\)。
\(p - x = ia_i + ja_j - (ia_j - ja_i) = (i - j)(a_i - a_j)\)
\(q - y = ia_i^2 + ja_j^2 - (ia_j^2 - ja_i^2) = (i - j)(a_i^2 - a_j^2)\)
\(\frac{q - y}{p - x} = a_i + a_j\)
定义 \(\frac{q - y}{p - x} = k, dx = p - x, dy = q - y\)
\(p、q、x、y\) 都已知了,那么 \(a_i + a_j\) 的值就知道了。
考虑一个特殊情况 \(dx\) 等于 0 的时候,\(dy\) 一定也要是 0,不然就没有答案了。这种情况下,说明交换了 \(i\) 和 \(j\) 之后,值不变,即 \(a_i\) 和 \(a_j\) 的值是相同。
其它情况下,\(k\) 一定要是个整数,不然 \(a_i + a_j\) 永远不可能等于 \(k\)。
那么就可以去遍历每一个 \(a_i\),根据 \(dx = (i - j)(a_i - a_j)\),得出 \(j = i - \frac{dx}{a_i - a_j} = i - \frac{dx}{a_i - (k - a_i)} = i - \frac{dx}{2a_i - k}\),判断有没有符合条件的 \(a_j\) 存在,若有,则答案 + 1。
\(ps\):要判断一下除数为 0 的情况。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
LL T = 1, n, x, y;
void solve(){
cin >> n >> x >> y;
LL p = 0, q = 0, ans = 0;
vector <LL> a(n + 1);
map <LL, LL> cnt;
for (int i = 1; i <= n; ++ i){
cin >> a[i];
p += i * a[i];
q += i * a[i] * a[i];
cnt[a[i]]++;
}
LL dx = p - x, dy = q - y;
if (dy == 0 && dx == 0){
for (auto i : cnt)
ans += (i.second - 1) * i.second / 2;
}
else if (dy && dx && dy % dx == 0){
LL k = dy / dx;
for (int i = 1; i <= n; ++ i){
if ( 2 * a[i] - k == 0 ) continue;
int j = i - dx / (2 * a[i] - k);
if (j <= n && j > i && a[j] + a[i] == k)
ans++;
}
}
cout << ans << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
cin >> T;
while (T--)
solve();
return 0;
}
E - Sequence in the Pocket
题目大意:
给一个长为 \(n\) 的序列,每次可以将其中一个元素移动到序列开头,问最少几次操作能让序列非递减。
思路:
因为判断最少操作几次,那么序列最大值的位置肯定不能移动,如果次大值在最大值前面,那么它也不需要移动,以此类推,可以发现,最小的操作次数即序列元素总数 - 最长的一个非递减序列。
且该非递减序列一定是排完序之后的序列的一个后缀序列。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
LL T = 1, n;
void solve(){
cin >> n;
vector <LL> a(n + 1), b(n + 1);
LL ans = n;
for (int i = 1; i <= n; ++ i){
cin >> a[i];
b[i] = a[i];
}
sort(a.begin() + 1, a.begin() + n + 1);
for (int i = n; i >= 1; -- i)
if (a[ans] == b[i])
ans--;
cout << ans << "\n";
}
int main(){
cin >> T;
while (T--)
solve();
return 0;
}
F - Abbreviation
题目大意:
若给定字符串中出现 'a', 'e', 'i', 'y', 'o', 'u' 这些字母,且不在第一位上,那么删除该字母,输出处理后的字符串。
思路:
按题意模拟。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
LL T = 1;
string s, t = "aeiyou";
void solve(){
cin >> s;
string ans = "";
ans += s[0];
for (int i = 1; i < s.size(); ++ i){
int f = 1;
for (int j = 0; j < t.size(); ++ j){
if (s[i] == t[j]){
f = 0;
break;
}
}
if (f) ans += s[i];
}
cout << ans << "\n";
}
int main(){
cin >> T;
while (T--)
solve();
return 0;
}
G - Lucky 7 in the Pocket
题目大意:
找到第一个 >= \(n\) 的满足 7 的倍数,同时又不是 4 的倍数的数。
思路:
暴力找。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
LL T = 1, n;
void solve(){
cin >> n;
while (1){
if (n % 7 == 0 && n % 4 != 0){
cout << n << "\n";
return;
}
n++;
}
}
int main(){
cin >> T;
while (T--)
solve();
return 0;
}
H - Singing Everywhere
题目大意:
给一个长为 \(n\) 的序列,当 \(a[k] > a[k - 1]\) && \(a[k] > a[k + 1]\) (\(k\) > 1 && \(k < n\)) 时,会丢失一个分数,现在可以删除序列中的一个元素,改变自己的失分,求出最少会失去多少分数。
思路:
模拟删除每一个元素对分数的影响即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
LL T = 1, n;
void solve(){
cin >> n;
vector <LL> a(n + 1);
LL ans = 0;
for (int i = 1; i <= n; ++ i)
cin >> a[i];
for (int i = 2; i <= n - 1; ++ i)
if (a[i] > a[i - 1] && a[i] > a[i + 1])
ans++;
LL f = 0;
for (int i = 1; i <= n; ++ i){
LL add1 = min(i + 1LL, n), add2 = min(i + 2LL, n);
LL del1 = max(i - 1LL, 1LL), del2 = max(i - 2LL, 1LL);
if (i == 1){
if (a[add1] > a[i] && a[add1] > a[add2]) f = -1;
}
else if (i == n){
if (a[del1] > a[i] && a[del1] > a[del2]) f = min(f, -1LL);
}
else {
LL t = 0;
if (a[i] > a[del1] && a[i] > a[add1]) t--;
if (a[add1] > a[i] && a[add1] > a[add2]) t--;
if (a[del1] > a[i] && a[del1] > a[del2]) t--;
if (a[del1] > a[del2] && a[del1] > a[add1]) t++;
if (a[add1] > a[add2] && a[add1] > a[del1]) t++;
f = min(f, t);
}
}
cout << ans + f << "\n";
}
int main(){
cin >> T;
while (T--)
solve();
return 0;
}
I - Fibonacci in the Pocket
题目大意:
告诉 \(a\) 和 \(b\),求出 \(Fibonacci\) 数列第 \(a\) 到 \(b\) 个元素之和的奇偶性。
思路:
找一下 \(Fibonacci\) 的奇偶性规律,发现是以 3 为周期的,然后模拟判断一下就可以了。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
LL T = 1;
string a, b;
void solve(){
LL x = 0, y = 0;
cin >> a >> b;
for (int i = 0; i < a.size(); ++ i)
x += (a[i] - '0');
for (int i = 0; i < b.size(); ++ i)
y += (b[i] - '0');
x %= 3;
y %= 3;
if (x == 0){
if (y == 0) cout << "0\n";
else if (y == 1) cout << "1\n";
else cout << "0\n";
}
else if (x == 1){
if (y == 0) cout << "0\n";
else if (y == 1) cout << "1\n";
else cout << "0\n";
}
else{
if (y == 0) cout << "1\n";
else if (y == 1) cout << "0\n";
else cout << "1\n";
}
}
int main(){
cin >> T;
while (T--)
solve();
return 0;
}
J - Welcome Party
题目大意:
\(n\) 个人入场,接下来有 \(m\) 行,每行两个数,\(a\) 和 \(b\),表示 \(a\) 和 \(b\) 是好朋友,某个人入场后,若场内没有自己的好朋友,他就会不开心,求出一个入场顺序,让不开心的人数最少,输出该数及方案,若有多个方案,输出字典序最小的那个方案。
思路:
\(m\) 对关系可以组合成一个及多个连通块,即有关系的人就是一个连通块。
要实现不开心的人的数量最小,显然,每个连通块中一个人进去了,接下来进去的那个人一定是他的好朋友或者另一个连通块中的一个人。
所以,不开心的人数就是连通块的数量,字典序最小的方案就是让编号最小的那个人先入场,然后让他的好朋友中最小编号的那个人入场,这个可以通过 bfs + 优先队列 实现。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int T = 1, n, m, fa[N], v[N];
vector <int> g[N];
priority_queue <int, vector <int>, greater<int> > q;
void Clear(){
for (int i = 1; i <= n; ++ i){
fa[i] = i;
v[i] = 0;
g[i].clear();
}
}
int find(int x){
if (fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
void join(int a, int b){
int aa = find(a), bb = find(b);
if (aa == bb) return;
if (aa > bb) fa[aa] = bb;
else fa[bb] = aa;
}
void bfs(){
while (q.size())
q.pop();
for (int i = 1; i <= n; ++ i)
if (fa[i] == i){
q.push(i);
v[i] = 1;
}
cout << q.size() << "\n";
vector <int> ans;
while (q.size()){
int x = q.top();
q.pop();
ans.push_back(x);
for (auto y : g[x]){
if (v[y]) continue;
v[y] = 1;
q.push(y);
}
}
for (int i = 0; i < n; ++ i)
cout << ans[i] << " \n"[i == n - 1];
}
void solve(){
cin >> n >> m;
Clear();
for (int i = 1; i <= m; ++ i){
int u, v;
cin >> u >> v;
join(u, v);
g[u].push_back(v);
g[v].push_back(u);
}
bfs();
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
cin >> T;
while (T--)
solve();
return 0;
}
K - Strings in the Pocket
题目大意:
给定两个字符串 \(s\) 和 \(t\),问翻转 \(s\) 的某一连续子段,让 \(s\) 等于 \(t\) 的方案有多少种。
思路:
先分成两种:
当 \(s\) 等于 \(t\) 的时候,那答案就是 \(s\) 的回文子串的数量之和,通过 \(manacher\) 可以求解。
当 \(s\) 不等于 \(t\) 的时候,又可以分两种:
当翻转 \(s\) 的某段后不能让 \(s\) 等于 \(t\) 的时候,答案为 0。
若可以,那要从翻转的那一段开始往两边找相同的字母,答案就是相同字母的数量 + 1(翻转要翻转的那一段或翻转延伸出来的一段)(可以参考题目第一个样例)。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 2e6 + 10;
LL T = 1, n, p[N << 1];
char s[N], t[N], ss[N << 1];
bool check(LL a, LL b){
for (int i = a, j = b; i <= b; ++ i, -- j)
if (s[i] != t[j])
return false;
return true;
}
void init(){
ss[0] = '@', ss[1] = '#';
for (int i = 0; i < n; ++ i){
ss[2 * i + 2] = s[i];
ss[2 * i + 3] = '#';
}
n = n * 2 + 1;
ss[n + 1] = '-';
}
void manacher(){
init();
LL mid = 0, r = 0, ans = 0;
for (int i = 1; i < n; ++ i){
if (i < r) p[i] = min( p[(mid << 1) - i], r - i );
else p[i] = 1;
while (ss[i - p[i]] == ss[i + p[i]])
p[i]++;
if (i + p[i] > r){
r = i + p[i];
mid = i;
}
ans += p[i] / 2;
}
cout << ans << "\n";
}
void solve(){
cin >> s >> t;
n = strlen(s);
LL a = 0, b = n - 1;
while (a < n && s[a] == t[a])
a++;
while (b >= 0 && s[b] == t[b])
b--;
if (a == n) manacher();
else if (check(a, b)) {
LL ans = 1;
for (int i = b + 1, j = a - 1; i < n, j >= 0; ++ i, -- j)
if (s[i] == s[j]) ans++;
else break;
cout << ans << "\n";
}
else cout << "0\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
cin >> T;
while (T--)
solve();
return 0;
}