2022“杭电杯”中国大学生算法设计超级联赛(9) 题解
A. Arithmetic Subsequence
首先可知如果一个数出现至少3次则不合法。
然后考虑长度为3的等差数列 \(a_i,a_j,a_k\) 的性质。有 \(a_k-a_i=2*(a_i-a_j)=2t\),\(a_i\) 和 \(a_k\) 的奇偶性相同。这启发了我们可以将偶数放在左半部分,奇数放在右半部分,然后继续分治处理两部分的情况。如果当前部分全为偶数或者奇数,我们直接对这部分的所有数右移一位,然后进行操作(对每个数除以相同的数会将两数之差除以此数,而减去相同的数不影响两数之差)。
#include<bits/stdc++.h>
#pragma GCC optimize(2)
#define int long long
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
const int MOD = 998244353;
const int MAXN = 5005;
const int INF = 0x3f3f3f3f;
int n, a[MAXN];
pii b[MAXN];
unordered_map<int, int> Map;
void Solve(int l, int r) {
if(l >= r) return ;
if(l + 1 == r && b[l].fi == b[r].fi) return ;
vector<pii> vec[2];
int num[2] = {0, 0};
for(int i = l; i <= r; i++) num[ b[i].fi & 1 ]++;
while( !(num[0] && num[1]) ) {
num[0] = num[1] = 0;
for(int i = l; i <= r; i++) {
b[i].fi >>= 1;
num[ b[i].fi & 1 ]++;
}
}
for(int i = l; i <= r; i++) vec[ b[i].fi & 1 ].push_back(b[i]);
int p = l, last;
for(auto &i : vec[0]) b[p++] = i;
last = p;
for(auto &i : vec[1]) b[p++] = i;
Solve(l, last - 1);
Solve(last, r);
}
void Solve() {
cin >> n; Map.clear();
for(int i = 1; i <= n; i++) cin >> a[i], Map[ a[i] ]++;
for(auto &i : Map) if(i.se > 2) return void(cout << "NO\n");
for(int i = 1; i <= n; i++) b[i] = {a[i], i};
Solve(1, n);
cout << "YES\n";
for(int i = 1; i <= n; i++) cout << a[ b[i].se ] << " "; cout << "\n";
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
while(T--) Solve();
return 0;
}
C. Fast Bubble Sort
发现对于一次询问 \([l,r]\),我们从 \(l\) 开始找下一个比他大的位置,设其为 \(pos\),则平移 \([l,pos-1]\) 可等价于进行一轮冒泡排序。从 \(pos\) 开始继续进行操作直到 \(pos\) 大于 \(r\)。对过程中产生的所有区间都进行操作时最终答案最小。
设 \(st[i][j]\) 表示从 \(i\) 开始进行 \(2^j\) 次平移操作后到达的点,对每次询问可以用 \(log\) 次操作得到答案。
注意特判处理询问时产生的区间长度为 \(1\) 时的情况,此时不用对区间进行平移操作。
#include<bits/stdc++.h>
#pragma GCC optimize(2)
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
const int MOD = 998244353;
const int MAXN = 100005;
const int INF = 0x3f3f3f3f;
int n, q, pos[MAXN][25], sum[MAXN][25], c[MAXN];
pii p[MAXN];
vector<int> vec;
void Modify(int x) {
while(x <= n) c[x]++, x += (x & (-x));
}
int Query(int x) {
int res = 0;
while(x) res += c[x], x -= (x & (-x));
return res;
}
int Divide(int x, int tot) { // [x...n]
int qx = Query(x - 1);
if(tot - qx == 0) return n + 1;
int l = x, r = n;
while(l < r) {
int mid = l + r - 1 >> 1;
if(( Query(mid) - qx ) > 0) r = mid;
else l = mid + 1;
}
return l;
}
void Solve() {
cin >> n >> q;
for(int i = 1; i <= n; i++) cin >> p[i].fi, p[i].se = i;
sort(p + 1, p + 1 + n);
for(int i = 1; i <= n; i++) c[i] = 0;
for(int k = 0; k <= 20; k++) for(int i = 1; i <= n + 1; i++) pos[i][k] = n + 1;
for(int i = n; i >= 1; i--) {
pos[ p[i].se ][0] = Divide(p[i].se + 1, n - i);
sum[ p[i].se ][0] = (pos[ p[i].se ][0] - p[i].se > 1);
Modify(p[i].se);
}
for(int k = 1; k <= 20; k++) {
for(int i = 1; i <= n; i++) {
pos[i][k] = pos[ pos[i][k - 1] ][k - 1];
sum[i][k] = sum[i][k - 1] + sum[ pos[i][k - 1] ][k - 1];
}
}
for(int i = 1; i <= q; i++) {
int l, r, cnt = 0; cin >> l >> r;
for(int k = 20; k >= 0; k--) {
if(pos[l][k] <= r) {
cnt += sum[l][k];
l = pos[l][k];
}
}
while(l < r) cnt += (pos[l][0] - l > 1), l = pos[l][0];
cout << cnt << "\n";
}
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
while(T--) Solve();
return 0;
}
F. Mario Party
有一个显而易见的结论:两个询问在 \(i\) 上的硬币数相同,其后续操作也相同。这启发了我们将询问离线再用并查集维护询问。
将询问排序,开一个set表示询问过程中出现的所有值,base表示set中所有值的偏移量。
对于 \(a[i] \geq 0\),直接将 \(base += a[i]\);
对于 \(a[i]<0\),需要查找set中小于 \(a[i]\) 的元素 \(val\),并与当前值为 \(val+a[i]\) 的所有询问进行合并,之后令 \(base += a[i]\)。
复杂度为 \(O(n \log q)\),set换成值域有 \(O(n+q)\) 的写法
#include<bits/stdc++.h>
#define int long long
#pragma GCC optimize(2)
using namespace std;
const int MOD = 998244353;
const int MAXN = 2000005;
const int INF = 0x3f3f3f3f;
int f[MAXN], val[MAXN], a[MAXN], x[MAXN], ans[MAXN], n, q;
vector<int> add[MAXN], del[MAXN];
set<int> vals;
unordered_map<int, int> rt;
int find(int x) { return x == f[x] ? x : f[x] = find(f[x]); }
void merge(int u, int v) {
int fu = find(u), fv = find(v);
f[fv] = fu;
}
void Solve() {
cin >> n >> q;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++) {
add[i].clear();
del[i].clear();
}
vals.clear(); rt.clear();
for(int i = 1; i <= q; i++) f[i] = i, val[i] = 0;
for(int i = 1; i <= q; i++) {
int l, r; cin >> l >> r >> x[i];
add[l].push_back(i);
del[r].push_back(i);
}
for(int i = 1, sum = 0; i <= n; i++) {
if(a[i] < 0) {
while( !vals.empty() ) {
int j = *vals.begin();
if(j + sum + a[i] >= 0) break;
if(!rt[j]) continue;
if(rt[ j - a[i] ]) merge(rt[ j - a[i] ], rt[j]);
else {
rt[ j - a[i] ] = rt[j];
val[ rt[j] ] = j - a[i];
vals.insert(j - a[i]);
}
rt[j] = 0;
vals.erase( vals.find(j) );
}
}
sum += a[i];
for(auto &j : add[i]) {
int v = x[j] - sum;
if(!rt[v]) rt[v] = j, val[j] = v;
else merge(rt[v], j);
vals.insert(v);
}
for(auto &j : del[i]) ans[j] = val[ find(j) ] + sum;
}
for(int i = 1; i <= q; i++) cout << ans[i] << "\n";
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
while(T--) Solve();
return 0;
}
G. Matryoshka Doll
设 \(f[i][j]\) 表示 \([1,i]\) 分为 \(j\) 堆的方案数,从 \(i-1\) 枚举上来,有两种情况:
① 新开一堆存着,从 \(f[i-1][j-1]\) 转移上来。
② 放到这 \(j\) 堆里,设 \(p\) 为满足 \(a[i]-a[p] \geq r\) 的最大位置,则这 \(j\) 堆中不能接在 \(i-p\) 堆的后面,其余位置都可以接。从 \(f[i-1][j]*(j-(i-p))\) 转移上来。
#include<bits/stdc++.h>
#pragma GCC optimize(2)
#define int long long
using namespace std;
const int MOD = 998244353;
const int MAXN = 5005;
const int INF = 0x3f3f3f3f;
int n, k, r, a[MAXN], pos[MAXN];
long long f[MAXN][MAXN];
vector<int> vec;
void Solve() {
cin >> n >> k >> r;
for(int i = 1; i <= n; i++) cin >> a[i];
a[n + 1] = INF;
for(int i = 0; i <= n; i++) for(int j = 0; j <= k; j++) f[i][j] = 0;
f[0][0] = 1;
for(int i = 1, p = 0; i <= n; i++) {
while(p < n && a[p + 1] + r <= a[i]) p++;
for(int j = 1; j <= k; j++) {
int sum = 0;
if(p && j - (i - p - 1) >= 0) sum = f[i - 1][j] * (j - (i - p - 1) ) % MOD;
f[i][j] = (f[i - 1][j - 1] + sum) % MOD;
}
}
cout << f[n][k] << "\n";
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
while(T--) Solve();
return 0;
}

浙公网安备 33010602011771号