2020 CCPC Wannafly Winter Camp Day5 部分题解

A Alternative Accounts

题意简述
一共有 n 个账号,k (k <= 3) 场比赛,一个人可能拥有多个账号,但是一个人不会用多个账号参加同一场比赛。
给定 k 场比赛的参与账号,请问这 n 个账号最少属于多少人。

解题思路
k 只有 3 种情况,1,2,3。
当 k = 1 时,有多少账号参赛,就最少有多少人。
当 k = 2 时,每一个账号可能参与:0 场比赛,1 场比赛,2 场比赛;当参与 0 场时该账号可以忽略,当参与 2 场时则必须记为 1 人,当参与 1 场时则可能与另一场的某个账号是同一人,假设只参与第一场有 a 人, 只参与第二场的有 b 人,那么显然最少有 max(a , b)人。

当 k = 3 时,最为复杂。设 cnt12 表示只参与 1、2 场比赛的人数,cnt13,cnt23 同理。设 ans = 0,我们只累加对答案有贡献的账号。如果一个账号一场比赛都没参加,那么就没贡献,如果全参加了,ans 就要累加 1;当只参与 1 场时,我们可以把它看作潜在的“小号”,而参与了 2 场的看作“大号",显然“大号”是一定要计数的,然后我们在此基础上来看看能否把“小号”归于某一个“大号"。

于是我们 ans += cnt12 + cnt13 + cnt23。而只参与了第一场的账号,如果是小号则只可能是参与了第二场或第三场的某个人的小号,假设是同时参与了 23 场的某人的小号: cnt1 - min(cnt1, cnt23)(此操作相当于把小号给合并到大号后还剩多少‘无主小号’),cnt2 和 cnt3 同理。最后,1 还有可能是 2 或 3 的小号,于是我们再将 cnt1 ,cnt2,cnt3 合并,显然合并后最少共有 max(cnt1, cnt2 ,cnt3) 个用户。

Code

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int n,k,a[N][5],m, ans;
int cnt[4][4];	//cnt[1][2]为只参加了1和2的人数
int solve(){
	if(k == 1) return m;
	if(k == 2){
		for(int i = 1; i <= n;i++){
			if(a[i][1] && a[i][2]) ans++;
			else if(a[i][1]) cnt[1][1]++;
			else if(a[i][2]) cnt[2][2]++; 
		}
		return ans + max(cnt[1][1],cnt[2][2]);
	}
	for(int i = 1;i <= n;i++){
		if(a[i][1] && a[i][2] && a[i][3]) ans++;
		else if(a[i][1] && a[i][2]) cnt[1][2]++;
		else if(a[i][1] && a[i][3]) cnt[1][3]++;
		else if(a[i][2] && a[i][3]) cnt[2][3]++;
		else if(a[i][1]) cnt[1][1]++;
		else if(a[i][2]) cnt[2][2]++;
		else if(a[i][3]) cnt[3][3]++;
	}
	cnt[3][3] -= min(cnt[3][3],cnt[1][2]);
	cnt[2][2] -= min(cnt[2][2],cnt[1][3]);
	cnt[1][1] -= min(cnt[1][1],cnt[2][3]);
	ans += max(cnt[1][1],max(cnt[2][2],cnt[3][3]));
	ans += cnt[1][2] + cnt[2][3] + cnt[1][3];
	return ans;
}
int main(){
	scanf("%d%d",&n,&k);
	for(int i = 1;i <= k;i++){
		scanf("%d",&m);
		for(int j = 1,x;j <= m;j++) scanf("%d",&x),a[x][i] = 1;
	}
	printf("%d\n",solve());
	return 0;
}
E Matching Problem

题意简述
给定一个长度为 n 的序列a,和长度为 4 的序列 b 。我们定义两个序列相等,当且仅当:

  1. 两个序列长度一样
  2. 对于任意的1 <= i ,j <= n,a[i] = a[j] <==> b[i] = b[j]。

其中 n 小于301。

解题思路
由于数据范围很小,我们可以利用三层循环+前缀和预处理来暴力解决,共O(N^3)。

Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 310;
int a[N],b[5],num[N],n;
int cnt[N][N];
inline ll calc(int i,int j,int k){
	ll res = n-k;	//res为k位后不等于a[i],a[j],a[k]的元素数量
	if(b[4] == b[1]) return cnt[n][a[i]] - cnt[k][a[i]];
	res -= cnt[n][a[i]] - cnt[k][a[i]];
	if(b[4] == b[2]) return cnt[n][a[j]] - cnt[k][a[j]];
	//为了避免重复减去,需要判断
	if(a[i] != a[j]) res -= cnt[n][a[j]] - cnt[k][a[j]];
	
	if(b[4] == b[3]) return cnt[n][a[k]] - cnt[k][a[k]];
	//为了避免重复减去,需要判断
	if(a[k] != a[i] && a[k] != a[j]) res -= cnt[n][a[k]] - cnt[k][a[k]];
	return res;
}
ll solve(){
	ll ans = 0;
	for(int i = 1;i <= n;i++){
		for(int j = 1;j <= n;j++) cnt[i][j] = cnt[i-1][j];
		cnt[i][a[i]]++;
	}
	for(int i = 1;i <= n;i++)
		for(int j = i+1;j <= n;j++){
			if((b[1] == b[2])^(a[i] == a[j])) continue;
			for(int k = j+1;k < n;k++){
				if((b[1] == b[3])^(a[i] == a[k])) continue;
				if((b[2] == b[3])^(a[j] == a[k])) continue;
				ans += calc(i,j,k);
			}
		}
	return ans;
}
int main(){
	scanf("%d",&n);
	for(int i = 1;i <= n;i++) scanf("%d",a+i),num[a[i]]++;
	for(int i = 1;i <= 4;i++) scanf("%d",b+i);
	printf("%lld\n",solve());
	return 0;
}
G Cryptographically Secure PRNG

题意简述
给定一个素数 p,请找出所有满足 f(x) = min{ f(k) } , 2 <= k < p 的 x 以及对应的 f(x),定义 f(x) 为 x 关于 p 的逆元。

解题思路
由于 p 很大,所以线性时间不行,其实像这类题目都有一些技巧性。
虽然单调递增的元素对应的逆元并不一定具有单调性,但是逆元是相互的,x 的逆元是 inv,那么 inv 的逆元就是 x;所以 f(x) = inv , f(inv) = x,而我们知道 x 是单调递增的,所以 inv 之前若没有被计算过的值域,则 f(inv) = x 也是答案,所以我们可以用一个 u 表示边界,所有大于 u 的 f 值一定都是无用的(因为有更小的 f(inv) = x)。

通过这样不断的动态的更新 u,可以避免许多不必要的计算,再通过 O(1) 求线性逆元,总的时间复杂度就可以接受了。

Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6+10;
const ll INF = 1e18;
ll p ,inv[N];
int T, tot ;
pair<ll,ll> ans[N];
ll solve(){
	if(p <= 2) return 0;
	inv[0] = inv[1] = 1; tot = 0;
	ll u = p-1, mi = INF;
	for(int i = 2;i <= u;i++){
		inv[i] = (p - p/i)*inv[p%i]%p;
		u = min(inv[i],u);
		if(inv[i] <= u) ans[++tot] = make_pair(inv[i],i);
		if(inv[i] <= mi){
			ans[++tot] = make_pair(i,inv[i]);
			mi = inv[i];
		}
	}
	sort(ans+1,ans+1+tot); int cnt = 0;
	for(int i = 1;i <= tot;i++) 
		if(ans[i] == ans[i-1]) ans[i].first = INF,cnt++;
	sort(ans+1,ans+1+tot); tot -= cnt;
	return tot;
}
int main(){
	scanf("%d",&T);
	while(T--){
		scanf("%lld",&p);
		printf("%lld\n",solve());
		for(int i = 1;i <= tot;i++) 
			printf("%lld %lld\n",ans[i].first,ans[i].second);
	}
	return 0;
}
I Practice for KD Tree

题意简述
给定一个 n x n 的矩阵,有 m 次矩阵修改操作,q 次询问矩阵最大值,在规定时间内解决。

解题思路
题目名字也说了是 KD 树的练习,也可以说是二维线段树,需要一个在线维护矩阵的数据结构。

Code

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

迷亭1213

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值