Codeforces Round 943 (Div. 3) C-G

文章介绍了C++编程中的几个技术问题,包括通过数组长度和数值范围分析数组排列,使用枚举解决排列游戏和细胞排列,以及涉及异或和区间划分的EqualXORSegments问题,还讨论了z函数在Division+LCP中的应用,以及区间划分的单调性和分治策略。
摘要由CSDN通过智能技术生成

C. Assembly via Remainders

在这里插入图片描述

思路:

我们可以注意到,数组的长度只有 500 500 500 ,并且每个数的大小都在 500 500 500 以内,再看向这题,容易知道,当第一个数确定之后,之后所有的数字都会确定下来,我们枚举第一个数,然后得到整个序列,去判断序列是否合法即可。

#include <bits/stdc++.h>
 
using namespace std;
const int N = 1e6 + 5;
typedef long long ll;
typedef pair<int, int> pll;
typedef array<ll, 3> p3;
// int mod = 998244353;
const int maxv = 4e6 + 5;
// #define endl "\n"
 
void solve()
{	
	int n;
	cin>>n;
	vector<int> a(n);
	for(int i=1;i<n;i++) cin>>a[i];
	vector<int> ans(n);
	for(int i=1;i<=500+5;i++){
		ans[0]=i;
		for(int j=1;j<n;j++) ans[j]=ans[j-1]+a[j];
		int f=1;
		for(int j=1;j<n;j++){
			if(ans[j]%ans[j-1]!=a[j]){
				f=0;
				break;
			}
		}
		if(f){
			for(auto x: ans) cout<<x<<" ";
			cout<<endl;
			return ;
		}
	}
}
 
 
 
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int t;
	t = 1;
	cin >> t;
	while (t--)
	{
		solve();
	}
	system("pause");
	return 0;
}

D. Permutation Game

在这里插入图片描述

思路:

容易知道,最优解一定是走到某个格子然后呆在该格子上即可,所以我们可以直接枚举走到的格子,然后取最大值。

#include <bits/stdc++.h>
 
using namespace std;
const int N = 2e5 + 5;
typedef long long ll;
typedef pair<int, int> pll;
typedef array<ll, 3> p3;
// int mod = 998244353;
const int maxv = 4e6 + 5;
#define endl "\n"
 
vector<int> e[N];
vector<ll> a(N);
int n,k;
 
 
void add(int u,int v){
	e[u].push_back(v);
}
 
bool st1[N],st2[N];
ll ans1=0,ans2=0;
void dfs1(int x,int time,ll sum)
{
	ll res=0;
	st1[x]=1;
	res=sum+a[x]*time;
	ans1=max(ans1,res);
	for(auto u: e[x]){
		if(st1[u]) continue;
		if(time==1) return  ;
		dfs1(u,time-1,sum+a[x]);
	}
}
void dfs2(int x,int time,ll sum)
{
	ll res=0;
	st2[x]=1;
	res=sum+a[x]*time;
	ans2=max(ans2,res);
	for(auto u: e[x]){
		if(st2[u]) continue;
		if(time==1) return ;
		dfs2(u,time-1,sum+a[x]);
	}
}
 
 
void solve()
{	
	int s1,s2;
	cin>>n>>k>>s1>>s2;
	// k=min(k,n);
	memset(st1,0,sizeof st1);
	memset(st2,0,sizeof st2);
	ans1=ans2=0;
	for(int i=1;i<=n;i++) e[i].clear();
	vector<int> p(n+5);
	for(int i=1;i<=n;i++){
		cin>>p[i];
		add(i,p[i]);
	}
	for(int i=1;i<=n;i++) cin>>a[i];
	// cin>>s1>>s2;
	dfs1(s1,k,0);
	dfs2(s2,k,0);
	// cout<<ans1<<" "<<ans2<<endl;
	if(ans1>ans2) cout<<"Bodya"<<endl;
	else if(ans1<ans2 ) cout<<"Sasha"<<endl;
	else cout<<"Draw"<<endl;
}
 
 
 
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int t;
	t = 1;
	cin >> t;
	while (t--)
	{
		solve();
	}
	system("pause");
	return 0;
}

E. Cells Arrangement

在这里插入图片描述

思路:

**构造没什么好说的。

#include <bits/stdc++.h>
 
using namespace std;
const int N = 1e6 + 5;
typedef long long ll;
typedef pair<int, int> pll;
typedef array<ll, 3> p3;
// int mod = 998244353;
const int maxv = 4e6 + 5;
// #define endl "\n"
 
void solve()
{	
	int n;
	cin>>n;
	if(n==2){
		cout<<1<<" "<<1<<endl;
		cout<<1<<" "<<2<<endl;
	}
	else{
		for(int i=1;i<=n;i++){
			if(i==n-1){
				cout<<n-1<<" "<<n<<endl;
			}
			else cout<<i<<" "<<i<<endl;
		}
	}
}
 
 
 
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int t;
	t = 1;
	cin >> t;
	while (t--)
	{
		solve();
	}
	system("pause");
	return 0;
}

F. Equal XOR Segments

在这里插入图片描述

思路

题目要求我们把给定的区间划分成 k k k 个,保证每一部分的异或值都相同, q q q 次询问,涉及到多次询问,还有区间异或问题,我们很容易想到,需要把异或前缀和数组给处理出来。
进而,我们考虑最简单的一种情况,即,整个区间的异或和为 0 0 0 时,这时候肯定是一个好数组,那么若区间异或和不为 0 0 0 ,这种时候,我们肯定只能把数组划分成奇数段,同时我们也会发现,当 k = 3 , 5 , 7 , 9 k=3,5,7,9 k=3,5,7,9 等等都是等效的,拿 5 5 5 来举例,我们可以将两个合并成一个 0 0 0 ,然后这样就只剩下三个部分。所以综上所述,这题只存在划分成两段或者是三段的情况。
再来考虑三段的情况如何进行处理:
在这里插入图片描述
如图,很容易得到, s [ x ] = s [ r ] s[x]=s[r] s[x]=s[r] s [ l − 1 ] = s [ y ] s[l-1]=s[y] s[l1]=s[y],所以我们可以把前缀异或值相同的下标放入一个桶中,然后我们去二分查找对应的 x , y x,y x,y 是否合法即可,即 l ≤ x < y ≤ r − 1 l\leq x<y\leq r-1 lx<yr1

#include <bits/stdc++.h>
 
using namespace std;
const int N = 1e6 + 5;
typedef long long ll;
typedef pair<int, int> pll;
typedef array<ll, 3> p3;
// int mod = 998244353;
const int maxv = 4e6 + 5;
#define endl "\n"
 
void solve()
{	
	int n,q;
	cin>>n>>q;
	vector<ll> a(n+5),s(n+5);
	map<ll,vector<int> > mp;
	mp[0].push_back(0);
	for(int i=1;i<=n;i++){
		cin>>a[i];
		s[i]=s[i-1]^a[i];
		mp[s[i]].push_back(i);
	}
	while(q--){
		int l,r;
		cin>>l>>r;
		if((s[l-1]^s[r])==0){
			cout<<"YES"<<endl;
			continue;
		}
		auto &v1=mp[s[l-1]];
		auto &v2=mp[s[r]];
		int y=lower_bound(v1.begin(),v1.end(),r)-v1.begin()-1;
		int x=lower_bound(v2.begin(),v2.end(),l)-v2.begin();
		if(y==v1.size()||y<0||x<0||x==v2.size()){
			cout<<"NO"<<endl;
			continue;
		}
		if(v1[y]<r&&v2[x]>=l&&v1[y]>v2[x]){
			cout<<"YES"<<endl;
		}
		else cout<<"NO"<<endl;
	}
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int t;
	t = 1;
	cin >> t;
	while (t--)
	{
		solve();
	}
	system("pause");
	return 0;
}

G2. Division + LCP (hard version)

在这里插入图片描述

思路:

首先考虑简单版本,即 l = r l=r l=r 的情况,首先我们对整个字符串求出其 z z z 函数,其表示为从当前位置 i i i 开始与整个字符串 s s s 的最长公共前缀,那么接下来我们可以去二分 l c p lcp lcp 的长度,得到二分后的段数 c n t cnt cnt ,然后将 c n t cnt cnt l l l ,进行比较即可,很明显这个东西具有单调性,当我们二分的长度越小时,得到的 c n t cnt cnt 一定是越大的。

#include <bits/stdc++.h>
 
using namespace std;
const int N = 1e6 + 5;
typedef long long ll;
typedef pair<int, int> pll;
typedef array<ll, 3> p3;
// int mod = 998244353;
const int maxv = 4e6 + 5;
// #define endl "\n"
 
vector<int> z_algorithm(const string &s)
{
	int n = s.size();
	vector<int> a(n);
	a[0] = n;
	for (int i = 1, l = 0, r = 0; i < n; i++)
	{
		if (i <= r)
			a[i] = min(a[i - l], r - i + 1);
		while (i + a[i] < n && s[i + a[i]] == s[a[i]])
			++a[i];
		if (i + a[i] - 1 > r)
			l = i, r = i + a[i] - 1;
	}
	return a;
}
void solve()
{
	int n, kl, kr;
	cin >> n >> kl >> kr;
	string s;
	cin >> s;
	vector<int> z = z_algorithm(s);
	int l = 1, r = n;
	auto check = [&](int x)
	{
		int cnt = 0;
		for (int i = 0; i < n; i++)
		{
			if (z[i] >= x)
			{
				cnt++;
				i = i + x - 1;
			}
		}
		return cnt >= kl;
	};
	int ans = 0;
	while (l <= r)
	{
		int mid = (l + r) / 2;
		if (check(mid))
		{
			ans = mid;
			l = mid + 1;
		}
		else
		{
			r = mid - 1;
		}
	}
	cout << ans << endl;
}
 
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int t;
	t = 1;
	cin >> t;
	while (t--)
	{
		solve();
	}
	system("pause");
	return 0;
}

再考虑复杂版本,我们需要求一段区间的 f f f值,我们是否还是可以利用简单版本的做法,当然是可以的,容易发现,最终的一个答案也具有单调性,即随着段数 k k k的上升,我们对应得到的答案一定是越来越小的,即 l c p lcp lcp 的长度一定会越来越小,其长度为 n k n\over k kn 。由此我们完全可以进行根号分治,当 k k k 小于某个阈值时,我们暴力的去运用简单版本的二分得到答案,当 k k k 大于该阈值时,我们就暴力的去枚举 l c p lcp lcp 的长度即可,因为最多只会枚举 n k n\over k kn次。
最终的时间复杂度为 n n l o g n + n n \sqrt nnlogn+n \sqrt n n nlogn+nn

#include <bits/stdc++.h>

using namespace std;
const int N = 1e6 + 5;
typedef long long ll;
typedef pair<int, int> pll;
typedef array<ll, 3> p3;
// int mod = 998244353;
const int maxv = 4e6 + 5;
// #define endl "\n"

vector<int> z_algorithm(const string &s)
{
	int n = s.size();
	vector<int> a(n);
	a[0] = n;
	for (int i = 1, l = 0, r = 0; i < n; i++)
	{
		if (i <= r)
			a[i] = min(a[i - l], r - i + 1);
		while (i + a[i] < n && s[i + a[i]] == s[a[i]])
			++a[i];
		if (i + a[i] - 1 > r)
			l = i, r = i + a[i] - 1;
	}
	return a;
}

int cal(int tar,int n,vector<int> &z)//二分暴力计算
{
	int l = 1, r = n;
	auto check = [&](int x)
	{
		int cnt = 0;
		for (int i = 0; i < n; i++)
		{
			if (z[i] >= x)
			{
				cnt++;
				i = i + x - 1;
			}
		}
		return cnt >= tar;
	};
	int ans = 0;
	while (l <= r)
	{
		int mid = (l + r) / 2;
		if (check(mid))
		{
			ans = mid;
			l = mid + 1;
		}
		else
		{
			r = mid - 1;
		}
	}
	return ans;
}

void solve()
{
	int n, kl, kr;
	cin >> n >> kl >> kr;
	string s;
	cin >> s;
	vector<int> z = z_algorithm(s);//计算z函数
	int bk=(int)sqrt(n);//阈值
	vector<int> f(n+5);
	for(int len=1;len<=bk;len++){//大于阈值,暴力枚举长度
		int cnt=0;
		for(int i=0;i<n;i++){
			if(z[i]>=len){
				i=i+len-1;
				cnt++;
			}
		}
		f[cnt]=len;
	}
	for(int i=n;i>=0;i--) f[i]=max(f[i],f[i+1]);//后缀取最大值即可,因为具有单调性
	for(int i=kl;i<=kr;i++){
		if(i<=bk){
			cout<<cal(i,n,z)<<" ";
		}
		else cout<<f[i]<<" ";
	}
	cout<<endl;
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int t;
	t = 1;
	cin >> t;
	while (t--)
	{
		solve();
	}
	system("pause");
	return 0;
}
  • 25
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值