Codeforces Round #716 Div.2 部分题解

这场难度比之前几场友好了许多。

A. Perfectly Imperfect Array

题意: 给出一个数组 a a a,判断其是否存在一个子序列 b b b使得 b b b中的元素乘积不是平方数。若是,输出YES,否则输出NO。
做法: 直接考虑单个元素即可。若 a a a中元素有一个不是平方数,输出YES,否则输出NO。证明可以从质因子分解入手(考虑每个质因子次数的奇偶性,代码中判断是否是平方数也用的是质因子分解的方法)。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n, a[120], f; 
int main()
{
	ios::sync_with_stdio(false);
	int T;
	cin >> T;
	while(T--)
	{
		f = 0;
		cin >> n;
		for(int i = 1; i <= n; i++)
		{
			cin >> a[i];
			for(int j = 2; j * j <= a[i]; j++)
			{
				if(a[i] % j) continue;
				int cnt = 0;
				while(a[i] % j == 0) a[i] /= j, cnt++;
				if(cnt & 1) f = 1;
			}
			if(a[i] != 1) f = 1;
		}
		cout << (f? "YES" : "NO") << endl;
	} 
	return 0;
}

B. AND 0, Sum Big

题意: 给出 n , k n,k n,k,要求输出满足以下条件的数组的数量,答案模 1 e 9 + 7 1e9+7 1e9+7

  • 数组的元素大小在 [ 0 , 2 k − 1 ] [0,2^{k}-1] [0,2k1]中。
  • 所有元素按位与之后的结果是0。
  • 数组元素的和尽量大。

做法: 结合按位与之后的结果是0,说明二进制k位中,每一位都有0,但要是得所有数组的元素和尽量大,那么每一个位有且只有一个0。所以,对于k个可以选择的二进制位来说,每一位选出一个0的方案有n种,因此答案就是 n k n^k nk

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e6 + 10, mod = 1e9 + 7;
ll fac[N], n, k, ans;
ll qpow(ll x, ll n)
{
	ll res = 1;
	while(n)
	{
		if(n & 1) res = res * x % mod;
		x = x * x % mod;
		n >>= 1;
	}
	return res;
}
int main()
{
	ios::sync_with_stdio(false);
	int T;
	cin >> T;
	while(T--)
	{
		cin >> n >> k;
		cout << qpow(n, k) << endl;
	}
	return 0;
}

C. Product 1 Modulo N

题意: 给出 n n n,要求在 [ 1 , 2 , . . . , n − 1 ] [1,2,...,n-1] [1,2,...,n1]中,选出最长的子序列,使得子序列中所有数的乘积模n的结果为1。
做法: 首先可以明确一点,子序列的元素和 n n n的公因子都是1,若不是1,由斐蜀定理,乘积模 n n n不可能是1。另外,1一定在任何 n n n的答案序列里。赛时打了个表,很容易看出规律。

2: 1
3: 1
4: 1
5: 1 2 3
6: 1
7: 1 2 3 4 5
8: 1 3 5 7
9: 1 2 4 5 7
10: 1 3 7

可以看到,若 i i i是答案序列中的元素,那么 n − i n-i ni也是,不过有时候 i i i可以取1,有的时候不能。我们先将符合条件的 i i i加入到答案序列并计算 ∏ i g c d ( i , n ) = 1   a n d   2 ≤ i ≤ n − i ( n − i ) ∗ i m o d    n \prod \limits_{i}^{gcd(i,n)=1\ and \ 2\le i\le n-i}(n-i)*i \mod n igcd(i,n)=1 and 2ini(ni)imodn。若结果是1,那么就不加 n − 1 n-1 n1到答案序列里,只加入 1 1 1,否则就将 1 1 1 n − 1 n-1 n1加入到答案序列中。
赛时的代码特判了质数,其实不特判也是ok的。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
bool ispr(int x)
{
	for(int i = 2; i * i <= x; i++)
	{
		if(x % i == 0) return false;
	}
	return true;
}
int main()
{
	ios::sync_with_stdio(false);
	cin >> n;
	if(n <= 4 or n == 6)
	{
		cout << 1 << endl << 1 << endl;
		return 0; 
	}
	if(ispr(n))
	{
		if(n <= 3) cout << 1 << endl << 1 << endl;
		else
		{
			cout << n - 2 << endl;
			for(int i = 1; i <= n - 2; i++)
			{
				cout << i;
				if(i == n - 2) cout << endl;
				else cout << ' ';
			}
		}
	}
	else
	{
		vector<int> ans;
		ll p = 1;
		for(int i = 2; i <= n - i; i++)
		{
			if(__gcd(i, n) == 1 and __gcd(n, n - i) == 1)
			{
				p = p * i * (n - i) % n;
				ans.push_back(i);
				ans.push_back(n - i);
			}
		}
		ans.push_back(1);
		if(p != 1) ans.push_back(n - 1);
		sort(ans.begin(), ans.end());
		cout << ans.size() << endl;
		for(int i = 0; i < ans.size(); i++)
		{
			cout << ans[i];
			if(i == ans.size() - 1) cout << endl;
			else cout << ' ';
		}
	}
	return 0;
}

D. Cut and Stick

题意: 给出一个长度为 n n n的数组 a a a q q q个询问。每次询问给出一个 l , r l,r l,r,问,将数组区间 [ l , r ] [l,r] [l,r]切成若干个子序列,使得每个子序列里的数出现次数都不超过 ⌈ m 2 ⌉ \lceil \frac {m}{2} \rceil 2m m m m为该子序列的长度,最少需要切分几次。
做法(官方题解): 首先,将 [ l , r ] [l,r] [l,r]中的元素切分出来,如果序列已经满足要求,那么输出1即可。然后为了顺利A掉题,需要在做题过程中推出下面的结论。我们假设长度为 m m m的序列中存在一个出现次数超过半数的元素 s s s,其出现频率为 f f f,那么最优的切分方法是将剩下的 m − f m-f mf元素搭配上 m − f + 1 m-f+1 mf+1 s s s,其余的 s s s切分成单个数的序列。共需要 1 + f − ( m − f + 1 ) = 2 f − m 1+f - (m - f + 1)=2f-m 1+f(mf+1)=2fm次。可以证明这样是最优的。
那么就是如何找到这个 f f f的问题了。首先,我们得能够高效统计出 a i a_i ai [ l , r ] [l,r] [l,r]内的出现频率。注意到 a i a_i ai的范围不超过 3 e 5 3e5 3e5,可以使用类似权值线段树的思想,用一个数组 v a i v_{a_i} vai来记录 a i a_i ai在原数组中出现的下标,显然每个 v a i v_{a_i} vai的元素是单调递增的。那么 a i a_i ai [ l , r ] [l,r] [l,r]内的出现次数可以通过二分查找来实现, c n t a i = u p p e r _ b o u n d ( v a i , r ) − l o w e r _ b o u n d ( v a i , l ) cnt_{a_i}=upper\_bound(v_{a_i},r)-lower\_bound(v_{a_i},l) cntai=upper_bound(vai,r)lower_bound(vai,l)
然后是怎么快速查询区间 [ l , r ] [l,r] [l,r]中,最高的出现次数的问题。
可以使用随机化算法,生成一个处在 [ l , r ] [l,r] [l,r]内的随机数 d d d,然后把 a [ d ] a[d] a[d]当作出现次数最高的元素并统计其出现次数,计算出一个答案 t m p tmp tmp。重复随机选取 d d d的操作30次,将每次的 t m p tmp tmp与答案取最大值即可(取最大是如果之前的答案比 t m p tmp tmp小,说明这次选到的数的出现次数比之前的都大,更接近最大出现次数)。
可以建线段树查询。线段树内维护区间 [ l , r ] [l,r] [l,r]出现次数最大的(维护的是最大出现次数的话儿子节点合并得到父亲节点的答案会有问题),查询的时候再返回最大的出现次数即可。

这里用到了C++的一个很神奇的区间随机数生成函数,学习一个。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 10;
int a[N], n, q, l, r, t[N << 2];
vector<int> G[N];
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
inline int cnt(int k, int l, int r)
{
	return upper_bound(G[k].begin(), G[k].end(), r) - lower_bound(G[k].begin(), G[k].end(), l);
}

int solve_random(int l, int r)
{
	int ans = 1;
	
	for(int i = 1; i <= 30; i++)
	{
		int pos = uniform_int_distribution<int>(l,r)(rng);
		ans = max(ans, 2 * cnt(a[pos], l, r) - r + l - 1);
	}
	return ans;
}
void build(int no, int l, int r)
{
	if(l == r)
	{
		t[no] = a[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(2 * no, l, mid);
	build(2 * no + 1, mid + 1, r);
	t[no] = cnt(t[2 * no], l, r) > cnt(t[2 * no + 1], l, r)? t[2 * no] : t[2 * no + 1];
}
int query(int no, int l, int r, int _l = 1, int _r = 3e5)
{
	if(r < l or _r < l or _l > r) return 0;
	if(_l >= l and _r <= r) return cnt(t[no], l, r);
	int mid = (_l + _r) >> 1;
	return max(query(2 * no, l, r, _l, mid), query(2 * no + 1, l, r, mid + 1, _r));
}
int solve_determine(int l, int r)
{
	int f = query(1, l, r);
	return max(1, 2 * f - (r - l + 1));
}
int main()
{
	ios::sync_with_stdio(false);
	cin >> n >> q;
	
	for(int i = 1; i <= n; i++)
	{
		cin >> a[i];
		G[a[i]].push_back(i);
	}
	build(1, 1, 3e5);
	while(q--)
	{
		cin >> l >> r;
		cout << solve_random(l, r) << endl;
		//cout << solve_determine(l, r) << endl;
	}	
	return 0;
}
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看REAdMe.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看REAdMe.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看READme.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 、 1资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看READmE.文件(md如有),本项目仅用作交流学习参考,请切勿用于商业用途。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值