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;
}
posted @ 2022-08-20 10:38  Orzjh  阅读(88)  评论(1)    收藏  举报