A
B

[HZOI]CSP-S模拟32

CSP-S模拟32

今天打的要睡着了,根本没有大脑可以思考

小 Z 专场!(是谁不重要,无限 %%% )

T1 小 Z 爱计数

签到题 ,差点挂掉的签到题。

题意:有三种操作(+1、-1、归零),给定 n 个询问,问存不存在一种情况满足所有 n 个询问。每次询问给定时间 t 和数值 v 。

首先非常容易想到排序后判断不合法情况。

经过简单思考可得不合法情况要同时满足以下两种情况:

  • 本次询问与上一次询问的时间差比数值差要大,或者奇偶性不匹配

  • 本次询问的数值无法通过在本次询问与上次询问的间隔中通过某个归零操作来获得

然后就没了,一定不要忘了排序哟~

点击查看代码
#include<bits/stdc++.h>
#define int long long 
#define Blue_Archive return 0
#define con putchar_unlocked(' ')
#define ent putchar_unlocked('\n')
using namespace std;
constexpr int N = 1e6 + 3;
constexpr int INF = 1e18;
constexpr int mod = 1e9 + 7;
constexpr char me[] = "終末なにしてますか?忙しいですか?救ってもらっていいですか?";

int T;
int n;
int c;

struct miku
{
	int a,b;
}l[N];

inline int read()
{
	int k = 0,f = 1;
	char c = getchar_unlocked();
	while(c < '0' || c > '9')
	{
		if(c == '-') f = -1;
		c = getchar_unlocked();
	} 
	while(c >= '0' && c <= '9') k = (k << 3) + (k << 1) + c - '0',c = getchar_unlocked();
	return k * f;
}

inline void write(int x)
{
	if(x < 0) putchar_unlocked('-'),x = -x;
	if(x > 9) write(x / 10);
	putchar_unlocked(x % 10 + '0');
}

inline void solve()
{
	bool can = 1;
	c = read();
	n = read();
	for(int i = 1;i <= n;i ++)
	{
		l[i].a = read();
		l[i].b = read();
	} 
	sort(l + 1,l + n + 1,[](miku a,miku b){return a.a < b.a;});
	for(int i = 1;i <= n;i ++)
	{
		if(((l[i].a - l[i - 1].a < abs(l[i].b - l[i - 1].b)) || ((l[i].a - l[i - 1].a - abs(l[i].b - l[i - 1].b)) % 2 == 1)) && abs(l[i].b) >= l[i].a - l[i - 1].a) 
		{
			can = 0;
			break;
		}
	}
	if(can) puts("Yes");
	else puts("No");
}

signed main()
{
	freopen("count.in","r",stdin);freopen("count.out","w",stdout);
	// freopen("data.in","r",stdin);freopen("data.out","w",stdout);
	T = read();
	while(T --) solve();
	Blue_Archive;
}

T2 小 Z 爱划分

首先观察题意可得:是将整个序列划分成若干个连续段后求所有分法的权值平方和。

每种分法的权值为每个连续段权值的积。

每个连续段的权值为连续段内每个元素的异或和。

开题首先不会做!

打个 \(O(n!)\) 暴力先。

然后发现可得 0 pts!

这下不得不想正解了。

下边是赛时思路,如果想直接看正解,建议跳过这一段QvQ

首先我们发现,权值的平方和很不好维护,而每种分法的权值为积的形式,所以可以直接求每种分法的权值积,之后直接求和就好,这样我们就把平方给下放到了每个连续段之中。

考虑一个 \(dp_{i,j}\),表示到第 \(i\) 个数分成了 \(j\) 个连续段的平方积。

考虑每加入一个点对于之前的贡献。

然后就可以推出一个转移方程:

$dp_{ij} = \sum_{k = 0}^{i - 1} dp_{kj - 1}(sum[i] ⊻ sum[j])^2 $ (\(sum_i\) 是到 \(i\) 的异或前缀和)

然后当你兴高采烈地想要去交的时候,一试样例,发现没过!

淦!

第一个样例输出 \(274\)

我们将所有的 \(dp\) 数组输出一下,发现 \(dp_{i,1}\) 竟然是正确输出(\(149\))。

开始考虑为啥。

我们发现,在转移的时候,它重复转移了,比如说 \(dp_{1,1}\) 既转移给了 \(dp_{2,1}\) 又转移给了 \(dp_{2,2}\) 这样在 \(dp_{3,3}\) 统计的时候, \(dp_{1,1}\) 就会被多次统计。

考虑如何修正。

因为观察到 \(dp_{i,1}\) 的值是正确的。

可以考虑直接进行转移。

换句话来说,就是将第二维舍去,直接让 \(dp_i\) 记录到 i 的所有的贡献。

这样的话,转移就不会重了。

然后是转移方程:

$dp_i = \sum_{j = 0}^{i - 1} dp_j (sum[i] ⊻ sum[j])^2 $

然后你就有 20 pts了!呱唧呱唧呱唧!

之后你只需要注意一下数组不要开 100 就可以了。。。(赛时直接RE!)

点击查看 O(n ^ 2) 代码
// ubsan: undefined
// accoders
#include<bits/stdc++.h>
#define int long long 
#define Blue_Archive return 0
#define con putchar_unlocked(' ')
#define ent putchar_unlocked('\n')
using namespace std;
constexpr int N = 1e6 + 3;
constexpr int INF = 1e18;
constexpr int mod = 1e9 + 7;
constexpr char me[] = "終末なにしてますか?忙しいですか?救ってもらっていいですか?";

int T;
int n;
int ans;
int a[N];
int dp[N]; // dp[i] : 前 i 个的贡献
int sum[N];

inline int read()
{
	int k = 0,f = 1;
	char c = getchar_unlocked();
	while(c < '0' || c > '9')
	{
		if(c == '-') f = -1;
		c = getchar_unlocked();
	} 
	while(c >= '0' && c <= '9') k = (k << 3) + (k << 1) + c - '0',c = getchar_unlocked();
	return k * f;
}

inline void write(int x)
{
	if(x < 0) putchar_unlocked('-'),x = -x;
	if(x > 9) write(x / 10);
	putchar_unlocked(x % 10 + '0');
}

inline int qpow(int a,int b)
{
	int res = 1;
	while(b)
	{
		if(b & 1) res = (res * a) % mod;
		a = (a * a) % mod;
		b >>= 1;
	}
	return res;
}

inline void dfs(int x,int res,int lim,int las)
{
	if(x == n + 1)
	{
		if(lim) return;
		res = res * (sum[n] ^ sum[las]) % mod;
		ans = (ans + res * res) % mod;
		return;
	}
	if(n - x < lim) return;
	dfs(x + 1,res,lim,las); // 没有断
	dfs(x + 1,res * (sum[x] ^ sum[las]) % mod,lim - 1,x); // 断了
}

inline void solve()
{
	n = read();
	for(int i = 1;i <= n;i ++)
	{
		a[i] = read();
		sum[i] = sum[i - 1] ^ a[i];
	}
	ans = sum[n] * sum[n] % mod;
	if(n <= 10)
	{
		for(int i = 1;i < n;i ++) dfs(1,1,i,0);
	}
	else 
	{
		dp[0] = 1;
		for(int i = 1;i <= n;i ++)
		{
			dp[i] = 0;
			for(int j = 0;j < i;j ++)
			{
				dp[i] = (dp[i] + dp[j] * ((sum[i] ^ sum[j]) * (sum[i] ^ sum[j]) % mod) % mod) % mod;
			}
		}
		ans = dp[n];
	}
	write(ans),ent;
}

signed main()
{
	freopen("partition.in","r",stdin);freopen("partition.out","w",stdout);
	// freopen("data.in","r",stdin);freopen("data.out","w",stdout);
	T = read();
	while(T --) solve();
	Blue_Archive;
}

然后让我们来考虑正解吧!

考虑如何优化这个 \(O(n ^ 2)\)

比较棘手的是 \((sum[i] ⊻ sum[j])^2\)

发现这个玩意儿直接维护很不好维护。

考虑一下平方的本质是 乘法

所以考虑二进制乘法!

举个栗子 : 5 * 2

1 0 1
x 0 1 0
--- --- --- ---
0 0 0
1 0 1 0
--- --- --- ---
1 0 1 0

答案是 10。

这启发我们将开方拆成乘法。

设一个 \(dp_{i,j,0/1,0/1}\) 表示 第 \(i\) 位和第 \(j\) 位分别为 \(0/1\)\(0/1\) 时候的贡献。

当我们枚举到一个 \(i\) 的时候。

直接去分别枚举 \(sum_i\) 的第 \(j\)\(k\) 位,当且仅当 \(wei(sum_i)_j ⊻ 1\)\(wei(sum_i)_k ⊻ 1\) 的时候有贡献。并且贡献应该乘上 \(2 ^ {j + k}\) 。(其所处位数的乘积)

然后每次将用答案更新 \(dp\) 数组即可。

点击查看 T2 正解代码
#include<bits/stdc++.h>
#define int long long 
#define get(a,b) (a >> b & 1)
#define Blue_Archive return 0
#define con putchar_unlocked(' ')
#define ent putchar_unlocked('\n')
using namespace std;
constexpr int N = 1e6 + 3;
constexpr int K = 30;
constexpr int INF = 1e18;
constexpr int mod = 1e9 + 7;
constexpr char me[] = "終末なにしてますか?忙しいですか?救ってもらっていいですか?";

int T;
int n;
int ans;
int a[N];
int sum[N];
int dp[32][32][2][2];

inline int read()
{
	int k = 0,f = 1;
	char c = getchar_unlocked();
	while(c < '0' || c > '9')
	{
		if(c == '-') f = -1;
		c = getchar_unlocked();
	} 
	while(c >= '0' && c <= '9') k = (k << 3) + (k << 1) + c - '0',c = getchar_unlocked();
	return k * f;
}

inline void write(int x)
{
	if(x < 0) putchar_unlocked('-'),x = -x;
	if(x > 9) write(x / 10);
	putchar_unlocked(x % 10 + '0');
}

inline void solve()
{
	n = read();
	for(int i = 1;i <= n;i ++)
	{
		a[i] = read();
		sum[i] = sum[i - 1] ^ a[i];
	}
	for(int i = 0;i <= K;i ++)
	{
		for(int j = 0;j <= K;j ++)
		{
			dp[i][j][0][0] = 1;
			dp[i][j][0][1] = 0;
			dp[i][j][1][0] = 0;
			dp[i][j][1][1] = 0;			
		}
	}
	ans = 0;
	for(int i = 1;i <= n;i ++)
	{
		ans = 0;
		for(int j = 0;j <= K;j ++)
		{
			for(int k = 0;k <= K;k ++)
			{
				ans = (ans + (1ll << (j + k)) % mod * dp[j][k][get(sum[i],j) ^ 1][get(sum[i],k) ^ 1] % mod) % mod;
			}
		}
		for(int j = 0;j <= K;j ++)
		{
			for(int k = 0;k <= K;k ++)
			{
				dp[j][k][get(sum[i],j)][get(sum[i],k)] = (dp[j][k][get(sum[i],j)][get(sum[i],k)] + ans) % mod;
			}
		}
	}
	write(ans),ent;
}

signed main()
{
	freopen("partition.in","r",stdin);freopen("partition.out","w",stdout);
	// freopen("data.in","r",stdin);freopen("data.out","w",stdout);
	T = read();
	while(T --) solve();
	Blue_Archive;
}

T3 小 Z 爱优化

首先看到这个题我们有一个基本的思路。

就是枚举最小值去找最小的可能最大值,然后更新答案。

显然最小值只能是所给元素或者相邻两个元素的和,这样的话枚举是 \(O(n)\) 级别的。

每次遍历数组则是 \(O(n)\)

所以暴力是 \(O(n ^ 2)\) 的。(不要跟我一样直接打个 \(O(n!)\) 的暴力然后逃了,\(O(n ^ 2)\) 还是很好打的)

考虑优化。

我们发现每次的最大值更新只与一个元素或者相邻的元素有关。

这启示我们每次答案更新都只需要考虑边界的情况即可。

所以考虑区间二分,也就是线段树!

对于 \(pushup\) 只需要考虑区间交界处的情况即可。

所以设 \(dp_{i,0/1,0/1}\) ,表示枚举到第 i 位,其表示的左右边界分别为 选取一个元素(0)和选取相邻元素 (1)。

这样我们就可以非常简单地求出在该最小值的情况下的可能最小的最大值。

然后如果你每次枚举的时候都建一遍树,将小于枚举的 \(min\) 值的值全部记成 \(inf\) 的话。

时间复杂度直接飙升至 \(O(n ^ 2 log_2{n})\),不如暴力!

所以肯定不能这么干。

考虑改变最小值可能会影响的点。

显然是值处在上个最小值和这个最小值之间的叶子节点!

那么,我们为了使每次修改最少,可以先将最小值排序,然后从小往大枚举,这样我们发现,对于每次枚举最小值,我们最多只会修改两个值,时间复杂度是 \(O(log_2{n})\) 的。

所以总的时间复杂度是 \(O(n log_2{n})\)。可以通过此题。

鲜花:这个题的这种解法之前遇到过一次,可惜这次没看出来,那个题也是个好题!是 \(CF1919F2\)

最后放上代码!

点击查看代码
#include<bits/stdc++.h>
#define int long long 
#define lid (id << 1)
#define rid (id << 1 | 1)
#define Blue_Archive return 0
#define con putchar_unlocked(' ')
#define ent putchar_unlocked('\n')
using namespace std;
constexpr int N = 1e6 + 3;
constexpr int INF = 1e18;
constexpr int mod = 1e9 + 7;
constexpr char me[] = "終末なにしてますか?忙しいですか?救ってもらっていいですか?";

int T;
int n;
int top;
int ans;
int a[N];
int dp[N << 2][2][2];

struct miku
{
	int id1,id2,val;
}b[N];

inline int read()
{
	int k = 0,f = 1;
	char c = getchar_unlocked();
	while(c < '0' || c > '9')
	{
		if(c == '-') f = -1;
		c = getchar_unlocked();
	} 
	while(c >= '0' && c <= '9') k = (k << 3) + (k << 1) + c - '0',c = getchar_unlocked();
	return k * f;
}

inline void write(int x)
{
	if(x < 0) putchar_unlocked('-'),x = -x;
	if(x > 9) write(x / 10);
	putchar_unlocked(x % 10 + '0');
}

inline int max(int a,int b){return a > b ? a : b;}
inline int min(int a,int b){return a < b ? a : b;}

inline void pushup(int id)
{
	dp[id][0][0] = min(max(dp[lid][0][0],dp[rid][0][0]),max(dp[lid][0][1],dp[rid][1][0])); 
	dp[id][1][0] = min(max(dp[lid][1][0],dp[rid][0][0]),max(dp[lid][1][1],dp[rid][1][0])); 
	dp[id][0][1] = min(max(dp[lid][0][0],dp[rid][0][1]),max(dp[lid][0][1],dp[rid][1][1])); 
	dp[id][1][1] = min(max(dp[lid][1][0],dp[rid][0][1]),max(dp[lid][1][1],dp[rid][1][1])); 
}

inline void build(int id,int l,int r,int mn) // 将小于 mn 的点设为 inf
{
	if(l == r)
	{
		dp[id][1][0] = dp[id][0][1] = INF;
		dp[id][0][0] = a[l] < mn ? INF : a[l];
		if(l != 1) dp[id][1][0] = (a[l - 1] + a[l]) < mn ? INF : (a[l - 1] + a[l]);
		if(l != n) dp[id][0][1] = (a[l] + a[l + 1]) < mn ? INF : (a[l] + a[l + 1]);
		dp[id][1][1] = INF;
		return;
	}
	int mid = (l + r) >> 1;
	build(lid,l,mid,mn);
	build(rid,mid + 1,r,mn);
	pushup(id);
}

inline void update(int id,int l,int r,int pos,int flag) // 依次修改 mn 值
{
	if(l == r)
	{
		if(!flag)
		{
			dp[id][1][1] = dp[id][0][0] = INF;
			if(l != 1) dp[id][1][0] = (a[l - 1] + a[l]) < a[l] ? INF : (a[l - 1] + a[l]);
			if(l != n) dp[id][0][1] = (a[l] + a[l + 1]) < a[l] ? INF : (a[l] + a[l + 1]);
		}
		else if(flag == 1)
		{
			dp[id][1][1] = dp[id][0][0] = INF;
			dp[id][0][1] = INF;
			if(l != 1) dp[id][1][0] = (a[l - 1] + a[l]) < (a[l] + a[l + 1]) ? INF : (a[l - 1] + a[l]);
		}
		else 
		{
			dp[id][1][1] = dp[id][0][0] = INF;
			dp[id][1][0] = INF;
			if(l != n) dp[id][0][1] = (a[l] + a[l + 1]) < (a[l - 1] + a[l]) ? INF : (a[l] + a[l + 1]);
		}
		return;
	}
	int mid = (l + r) >> 1;
	if(pos <= mid) update(lid,l,mid,pos,flag);
	else update(rid,mid + 1,r,pos,flag);
	pushup(id);
}

inline void solve()
{
	int mx = 0,mn = INF,top = 0;
	n = read();
	for(int i = 1;i <= n;i ++)
	{
		b[++ top].val = a[i] = read();
		b[top].id1 = i;
		b[top].id2 = 0;
		mx = max(mx,a[i]);
		mn = min(mn,a[i]);
		if(i != 1)
		{
			b[++ top].val = a[i] + a[i - 1];
			b[top].id1 = i - 1;
			b[top].id2 = i;
		} 
	} 
	sort(b + 1,b + top + 1,[](miku x,miku y){return x.val < y.val;});
	ans = mx - mn;
	build(1,1,n,b[1].val);
	ans = min(ans,dp[1][0][0] - b[1].val);
	for(int i = 2;i <= top;i ++)
	{
		if(b[i - 1].id2 != 0)
		{
			update(1,1,n,b[i - 1].id1,1);
			update(1,1,n,b[i - 1].id2,2);
		}
		else update(1,1,n,b[i - 1].id1,0);
		if(dp[1][0][0] == INF) break;
		ans = min(ans,dp[1][0][0] - b[i].val);
	}
	write(ans);ent;
}

signed main()
{
	freopen("opti.in","r",stdin);freopen("opti.out","w",stdout);
	// freopen("data.in","r",stdin);freopen("data.out","w",stdout);
	srand(time(0));
	T = read();
	while(T --) solve();
	Blue_Archive;
}

T4 小 Z 爱考试

还没改,咕咕咕

posted @ 2025-10-15 17:30  MyShiroko  阅读(37)  评论(4)    收藏  举报