2022.9.22———HZOI【CSP-S模拟9】YO寄

\(Preface\)

点击查看代码

\(AtCoder\)的题好像是

今天\(T3\)随只因化暗藏玄只因)爆零了,赛后很难受,总共只得到了\(19\)分的好成绩

\(Rank38/42\)

\(19pts+0pts+0pts+0pts = 19pts\)

下午讲题发现意外的听懂了,并且发觉怎么这么水,我真是个\(shab\)

当然\(Sakura\)讲的没听懂(

T1 最长上升子序列, T2 独特序列, T3 最大GCD, T4连续子段

\(\mathfrak{T1}\ 最长上升子序列\)

一道\(shab\)构造题

首先把不是a[i]的扔到队列里,注意是升序

然后从\(1到K-1\)贪心地使字典序最小,当然要先加入a[i]再贪心,为了使得a[]就是最后的\(LIS\),要满足要加入的一个q[L]小于a[i]。因为如果大于的话如果他比a[i+1]要小,那么就使得\(LIS\)更长了,如果比a[i+1]要大,那么显然不满足字典序最小的要求。不存在等于的情况(废话)。

最后要对a[K]进行一下特殊判断,因为a[K]并不一定等于\(n\),这也是我赛事\(shab\)挂成\(19pts\)的原因。

  • 如果a[K]比队尾大,那么显然直接让队尾到队头降序扔到a[K]的后面就行了。

  • 如果a[K]比队尾小,那么如果直接再按刚才的降序直接扔,显然\(LIS\)会变长,所以让队尾到队列中第一个比a[K]大的数都降序扔到a[K]的前面,剩下的再扔到a[K]的后面就行了。

T1
#include <iostream>
#define GMY (520&1314)
#define FBI_OPENTHEDOOR(x) freopen(#x ".in", "r", stdin), freopen(#x ".out", "w", stdout);
#define re register int
#define char_phi signed
#define DMARK cerr << "###"
#define _ ' '
#define Endl cout << '\n'
#define Dendl cerr << '\n'
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define N 200005
using namespace std;
inline void Fastio_setup(){ios::sync_with_stdio(false); cin.tie(NULL), cout.tie(NULL);}
int n, K, L, R;
char is[N];
int a[N], ans[N], q[N];
void work(){
	cin >> n >> K;
	for (re i = 1 ; i <= K ; ++ i)
		{cin >> a[i]; is[a[i]] = true;}
	L = 0, R = -1;
	for (re i = 1 ; i <= n ; ++ i)
		if (is[i] == false)
			q[++ R] = i;
	for (re i = 1 ; i <= K-1 ; ++ i){
		ans[++ ans[0]] = a[i];
		if (q[L] < a[i] and L <= R)
			ans[++ ans[0]] = q[L ++];
	}
	/*for (re i = 1 ; i <= n ; ++ i)
		cout << ans[i] << _;
	Endl;*/
	if (a[K] < n)
		while (q[R] > a[K] and L <= R)
			ans[++ ans[0]] = q[R --];
	ans[++ ans[0]] = a[K];
	/*for (re i = 1 ; i <= n ; ++ i)
		cout << ans[i] << _;
	Endl;*/
	while (L <= R)
		ans[++ ans[0]] = q[R --];
	for (re i = 1 ; i <= n ; ++ i)
		cout << ans[i] << _;
	Endl;
}
// #define IXINGMY
char_phi main(){
	#ifdef IXINGMY
		FBI_OPENTHEDOOR(a);
	#endif
	Fastio_setup();
	work();
	return GMY;
}

\(\mathfrak{T2}\ 独特序列\)

一个简单的一维\(dp\)

\(f_i\)为转移到\(i\),并且必选\(i\)的方案数,初始值\(f[1] = 1\)

考虑如何转移。

考虑这样一个数列:

\[4\ 6\ 3\ 1\ 2\ 3\ 5\ 4 \]

在搞到第二个\(3\)的时候,\(f\)值只能由上一个\(3\)到当前位置转移,用柿子表达出来就是

\[ f_i = \sum_{j = last_{a_i}}^{i-1}{f_j} \]

\(last\)就是上一个的意思。

考虑为什么从\(1\)\(last_{a_i}-1\)不转移。

\(1\)\(last_{a_i}\)的方案,对于再添上一个\(last_{a_i}\)而言和添上一个\(a_i\)而言是等同的,因为\(last_{a_i}\)就和\(a_i\)一样嘛只不过位置不同,但是这样一来就不满足唯一性了。

就比如序列\(3\ 6\)是合法的(转移到目前而言),归属于\(f_2\);而\(4\ 6\ 3\)是不合法的,因为转移到目前发现有超过一个的\(3\)

考虑为什么要转移\(f_{last_{a_i}}\)

对于序列\(3\),本身不合法,但是当转移的时候要填上他,因为序列\(3\ 3\)是合法的。

但是转移完之后\(f_{last_{a_i}}\)要删去,因为向\(4\ 6\ 3\)这样的序列是不合法的。

最后求一个前缀和就行了,可以用树状数组维护。

T2
#include <iostream>
#define GMY (520&1314)
#define FBI_OPENTHEDOOR(x) freopen(#x ".in", "r", stdin), freopen(#x ".out", "w", stdout);
#define re register int
#define char_phi signed
#define DMARK cerr << "###"
#define _ ' '
#define Endl cout << '\n'
#define Dendl cerr << '\n'
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define N 200005
#define P 998244353
#define mod %
using namespace std;
inline void Fastio_setup(){ios::sync_with_stdio(false); cin.tie(NULL), cout.tie(NULL);}
/*
	一维dp是吧
	我想想,树状数组
	树状数组
	好久不用了
	一维dp
	哎哎,我啥时候能学会dp
	flag:学完线性筛就去打背包dp
*/
long long n;
long long a[N], f[N], plc[N], C[N<<2];// 树状数组要开多大来着
#define lowbit(x) ((x) & (-(x)))
inline void Update(long long k, long long ed, long long w){
	while (k <= ed){
		C[k] = (C[k] + w + P) mod P;
		k += lowbit(k);
	}
}
inline long long Query(long long k){
	long long res(0);
	while (k != 0){
		res += C[k];
		if (res >= P)
			res -= P;
		k -= lowbit(k);
	}
	return res;
}
inline void work(){
	cin >> n;
	for (re i = 1 ; i <= n ; ++ i)
		cin >> a[i];
	/*for (re i = 2 ; i <= n ; ++ i){
		f[i] = 1;
		if (plc[i] != 0)
			Update(1, n, -f[plc[i]]);
		f[i] += Query(i);
		if (f[i] >= P)
			f[i] -= P;
		Update(
	}*/
	f[1] = 1; plc[a[1]] = 1; Update(1, n, 1);
	for (re i = 2 ; i <= n ; ++ i){
		f[i] = Query(i-1);
		if (plc[a[i]] != 0){
			f[i] = (f[i] - Query(plc[a[i]]-1) + P) mod P;
			Update(plc[a[i]], n, -f[plc[a[i]]]);
		}
		else 
			f[i] ++;
		if (f[i] >= P)
			f[i] -= P;
		plc[a[i]] = i;
		Update(i, n, f[i]);
	}
	cout << Query(n) << '\n';
}
// #define IXINGMY
char_phi main(){
	#ifdef IXINGMY
		FBI_OPENTHEDOOR(a);
	#endif
	Fastio_setup();
	work();
	return GMY;
}

\(\mathfrak{T3}\ 最大GCD\)

这个题我上来一眼丁真认定为场切题\(O(nlogn)\),发颠分解了个质因数发现\(P\)用没有,之后又尝试二分\(gcd\)发现假了,然后就搞了随只因化

然后喜提\(0pts\)

正解很简单,分两种情况

\(mx\)\(a_i\)中最大值

\(K >= \sum_{i=1}^{n} {mx-a_i}:\)

显然要先让所有的\(a_i\)达到\(mx\),此时\(gcd\)就是\(mx\),然后贪心地使\(gcd\)不断\(+1\)并判断当前\(K\)值能否达到。

可以直接写成一个柿子dsu

\(fst\)为所有\(a_i\)达到\(mx\)所需要的和,那么最后答案即为\(mx + \lfloor\frac{K-fst}{n}\rfloor\)

这玩意不难理解吧,\(mx\)已经是基础了,所有\(a_i\)的值都是\(mx\)了这时候所有\(a_i\)同时\(+1\)他们的\(gcd\)也就会同时\(+1\),也就是那个柿子

\(K < \sum_{i=1}^{n} {mx-a_i}:\)

这就有意思了

考虑枚举他们的公共因子\(factor\)。那么如果让每个\(a_i\)拥有该因子,根据显然的贪心策略,每个\(a_i\)肯定是变成比他大的第一个的\(factor\)的倍数。

那么达到该状态所需要消耗的+1次数就是\(:\)

\[ \sum_{i=1}^{n}{\lceil\frac{ai}{factor}\rceil \times factor - a_i} \]

这样做是\(n^2\)的。

考虑如何优化。

我们可以把\(a_i\)升序排序一下,然后枚举\(\lceil\frac{ai}{factor}\rceil\)也就是\(factor\)的倍数,二分找到他应该在\(a\)数组中的位置,减去上一个倍数所在位置,然后直接累加。根据调和级数这样做是只带一个\(log\)的。

所以就拆掉了一个\(n\)变成了\(logn\)。总时间复杂度为\(O(nlogn)\)(\(n\)与值域同阶)。

然后可以拆一下原柿子,就直接把\(\sum_{i=1}^{n}{a_i}\)拆出来,也就是求一下\(a_i\)的总和,不用在刚才二分的过程中减来减去,最后直接减就完了。

然后这题就做完了,总体思路十分简单,代码实现也不难,确实可以算是场切题

但是我这个\(shab\)赛时怎么就没想到。。。

T3
#include <iostream>
#include <algorithm>
#include <cmath>
#define GMY (520&1314)
#define FBI_OPENTHEDOOR(x) freopen(#x ".in", "r", stdin), freopen(#x ".out", "w", stdout);
#define re register int
#define char_phi signed
#define DMARK cerr << "###"
#define _ ' '
#define Endl cout << '\n'
#define Dendl cerr << '\n'
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define N 300005
using namespace std;
inline void Fastio_setup(){ios::sync_with_stdio(false); cin.tie(NULL), cout.tie(NULL);}
/*
	枚举倍数
	调和级数
	蛮巨的
	分两种情况,
	算了在草稿纸上胡了一遍就不在这里写了
*/
long long n, K, mx, first_step, sum, final_ans;
long long a[N];
inline void work(){
	cin >> n >> K;
	for (re i = 1 ; i <= n ; ++ i)
		{cin >> a[i]; mx = MAX(mx, a[i]); sum += a[i];}
	
	for (re i = 1 ; i <= n ; ++ i)
		first_step += (mx-a[i]);
	if (first_step <= K)
		{goto Another_Me;}
	
	sort(a+1, a+n+1);
	for (long long factor = mx, plc, res, ed, lst ; factor >= 1 ; -- factor){
		res = -sum; ed = ceil((double)mx / factor); lst = 0;
		for (long long bs = 1 ; bs <= ed ; ++ bs){
			plc = upper_bound(a+1, a+n+1, bs*factor) - a - 1;
			res += bs * factor * (plc-lst);
			// cerr << factor << _ << lst << _ << plc << _ << bs << _ << res << '\n';
			lst = plc;
		}
		// cerr << '\n' << '\n';
		if (res <= K)
			{final_ans = factor; break;}
	}
	cout << final_ans << '\n';
	return ;
	Another_Me:{
		final_ans = mx + (K-first_step)/n;
		cout << final_ans << '\n';
	}
}
// #define IXINGMY
char_phi main(){
	#ifdef IXINGMY
		FBI_OPENTHEDOOR(a);
	#endif
	Fastio_setup();
	work();
	return GMY;
}

\(\mathfrak{T4}\ 连续子段\)

(其实我也不是特别明白)

(开始大量转发)

观察K的数据范围 不难想到状压。
\(dp_s\)为选到的数的集合为 \(s\)的最少移动次数。
先预处理一下每个状态选了几个数(有多少个\(1\))。
转移的话就是看看这个数有没有在集合里:如果不在,就加上这个数所移动的步数(逆序对数)。
每种集合还要加上 集合向右平移的步数 或者 让还没有选的数集体向左平移,二者取 \(min\)
———迪神

T4
#include <iostream>
#include <cstring>
#define GMY (520&1314)
#define FBI_OPENTHEDOOR(x) freopen(#x ".in", "r", stdin), freopen(#x ".out", "w", stdout);
#define re register int
#define char_phi signed
#define DMARK cerr << "###"
#define _ ' '
#define Endl cout << '\n'
#define Dendl cerr << '\n'
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define N 300005
using namespace std;
inline void Fastio_setup(){ios::sync_with_stdio(false); cin.tie(NULL), cout.tie(NULL);}
int n, K, S;
int a[N], f[(1<<16) + 5], cnt[(1<<16) + 5];
/*
	“浮华的二进制。”
*/
#define lowbit(x) ((x) & (-(x)))
inline void work(){
	cin >> n >> K; S = (1 << K) - 1;
	for (re i = 1 ; i <= n ; ++ i)
		cin >> a[i];
	for (re i = 0, x ; i <= S ; ++ i){
		x = i;
		while (x != 0)
			cnt[i] ++, x ^= lowbit(x);
	}
	/*Dendl;
	for (re i = 0 ; i <= S ; ++ i)
		cerr << cnt[i] << _;
	Dendl;*/
	memset(f, 0x5f, sizeof(f));
	f[0] = 0;
	for (re i = 1, x ; i <= n ; ++ i){
		x = 1 << (a[i]-1);
		for (re s = S ; s >= 0 ; -- s){
			f[s|x] = MIN(f[s|x], f[s] + cnt[s&(-x)]);
			f[s] += MIN(cnt[s], K-cnt[s]);
		}
		// cerr << f[S] << '\n';
	}
	cout << f[S] << endl;
}
// #define IXINGMY
char_phi main(){
	#ifdef IXINGMY
		FBI_OPENTHEDOOR(a);
	#endif
	Fastio_setup();
	work();
	return GMY;
}

\(Postscript\)

(图我给扔到酰铧里了)

posted @ 2022-09-22 20:03  char_phi  阅读(50)  评论(6)    收藏  举报