CSP-S模拟9

A. 最长上升子序列

考虑构造字典序最小的方案,他长这个样子

image

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <=  '9' && c >='0');
	return x;
}
const int maxn = 200005;
int n, k, a[maxn], ans[maxn], rem[maxn];
bool vis[maxn];
int main(){
	n = read(); k = read();
	for(int i = 1; i <= k; ++i)vis[a[i] = read()] = true;
	if(k == 1){
		for(int i = n; i >= 1; --i)printf("%d ",i);
		return 0;
	}
	int p = 1;
	for(int i = 1; i <= n; ++i)if(!vis[i]){
		while(p <= k - 1 && (a[p] < i || rem[p]))++p;
		if(p >= k)break;
		rem[p] = i; vis[i] = 1;
	}
	p = 0;
	for(int i = 1; i <= k - 1; ++i){
		ans[++p] = a[i];
		if(rem[i])ans[++p] = rem[i];
	}
	for(int i = n; i >= a[k]; --i)ans[++p] = i;
	for(int i = a[k]; i > 0; --i)if(!vis[i])ans[++p] = i;
	for(int i = 1; i <= n; ++i)printf("%d ",ans[i]);
	return 0;
}

B. 独特序列

考虑设 \(f_i\) 表示以 \(i\) 为结尾的合法子序列个数

\(pre_i\) 表示上一个与 \(i\) 相同的位置

那么 \(\displaystyle f_i = \sum_{j = pre_i}^{i}f_j\)

考虑到 \(pre_i\) 直接到后面的转移就不合法了,所以把 \(f_{pre_i}\) 清除

\(BIT\) 简单维护即可

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <=  '9' && c >='0');
	return x;
}
const int maxn = 200005;
const int mod = 998244353;
int a[maxn], n, pre[maxn], vis[maxn];
struct BIT{
	int t[maxn];
	int lowbit(int x){return x & -x;}
	void add(int x, int val){
		val = (val % mod + mod) % mod;
		while(x <= n + 1){
			t[x] += val;
			t[x] = t[x] >= mod ? t[x] - mod : t[x];
			x += lowbit(x);
		}
	}
	int query(int x){
		int ans = 0;
		while(x){
			ans = (ans + t[x]) % mod;
			x -= lowbit(x);
		}
		return ans;
	}
}t;

int main(){
	n = read();
	for(int i = 1; i <= n; ++i)a[i] = read();
	int ans = 0;
	a[0] = 0; a[n + 1] = n + 1;
	for(int i = 1; i <= n; ++i){
		pre[i] = vis[a[i]];
		vis[a[i]] = i;
	}
	for(int i = 1; i <= n; ++i)vis[a[i]] = 0;
	for(int i = 1; i <= n; ++i){
		int now = t.query(i) -(pre[i] ? t.query(pre[i] - 1) : 0);
		if(vis[a[i]] == 0){
			vis[a[i]] = i;
			++now;
		}else{
			t.add(vis[a[i]], t.query(vis[a[i]] - 1)- t.query(vis[a[i]]));
			vis[a[i]] = i;
		}
		now = now < 0 ? now + mod : now;
		t.add(i, now);
	}
	printf("%d\n",t.query(n + 1));
	return 0;
}

C. 最大GCD

随机化过的,这里就不写出来影响大家了

正解其实是暴力 \(check\) 所有可能值,优化了 \(check\)

考虑在值域上进行 \(check\),假设 \(check\) 的为 \(x\), 每次取 $[kx + 1, kx + x - 1] $ 计算,前缀和即可

这样出来复杂度为调和级数,近似 \(log\)

D. 连续子段

装压记录状态

每次考虑是否选择该位置的元素

如果选择计算逆序对数即为贡献

不选择,那么左侧/右侧的序列需要移动拼起来,那么就是左右元素个数的 \(min\)

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <=  '9' && c >='0');
	return x;
}
const int maxn = 205;
const int mk = 66000;
const int inf = 0x3f3f3f3f;
int n, k, a[maxn];
int f[2][mk], cnt[mk];
int vis[21];
void ckmin(int &x, int y){x = min(x, y);}
int main(){
	n = read(), k = read();
	for(int i = 1; i <= n; ++i)a[i] = read();
	int mx = 1 << k;
	for(int i = 1; i < mx; ++i)cnt[i] = cnt[i - (i & -i)] + 1;
	for(int i = 0; i <= 1; ++i)
		for(int j = 1; j < mx; ++j)
			f[i][j] = inf;
	int ans = inf;
	for(int i = 0; i < n; ++i){
		int nt = i & 1, nzt = 1 - nt;
		f[nt][0] = 0;
		for(int j = 0; j < mx; ++j){
			if(f[nt][j] != inf){
				ckmin(f[nzt][j], f[nt][j] + min(cnt[j], k - cnt[j]));
				if(a[i + 1] <= k && (j & (1 << (a[i + 1] - 1))) == 0)ckmin(f[nzt][j | (1 << (a[i + 1] - 1))], f[nt][j] + cnt[j >> a[i + 1]]);
				f[nt][j] = inf;
			}
		}
		ans = min(ans, f[nzt][mx - 1]);
	}
	printf("%d\n",ans);
	return 0;
}
posted @ 2022-09-22 17:05  Chen_jr  阅读(41)  评论(2编辑  收藏  举报