2021牛客多校第二场题解

K题

题目大意:给出n,k,表示一共n个数,生成单调栈序列b,接下来k行每行输入p和v表示b[p] = v,请写出一个满足要求的序列。n<1e6。

思路:k可能小于n,那么将b数组都填满,按如下的规则:若当前b有值,则从此位置到前一个有值的b之间填充b[i]-1,b[i]-2…,最后一个有值的b右边都用最后一个b的值填充。比如已知b[2]=2,b[5]=4,则完整的填充序列为1,2,2,3,4。
接下来按从小到大,从右到左的原则排序并赋值。

ac代码:

#include <bits/stdc++.h>

using namespace std;

const int N = 5e6+6;
int b[N],a[N];
bitset<N> vis;
struct node{
	int id,v,ans;
	bool operator<(const node&t)const{
		if(v == t.v)return id > t.id;
		return v < t.v;
	}
}c[N];
bool cmp(node t1,node t2){
	return t1.id<t2.id;
}
int main(){
	int n,k;
	scanf("%d%d",&n,&k);
	for(int i = 1;i <= k;i++){
		int p,v;
		scanf("%d%d",&p,&v);
		b[p] = v,vis[p] = true;
	}
	for(int i = 1;i <= n;i++)
		if(vis[i])
			for(int j = i - 1;j >= 1&&!vis[j];j--)
				b[j] = max(1,b[j + 1] - 1);
	for(int i = 1;i <= n;i++)
		if(!b[i])b[i] = b[i - 1];
	int f = 0;
	for(int i = 2;i <= n;i++)	
		if(b[i] >1 + b[i - 1]){
			f = 1;
			break;
		}
	if(b[1]>1)f = 1;
	if(f){
		cout << "-1" << endl;
	}
	else{
		for(int i = 1;i <= n;i++)
			c[i].id = i,c[i].v = b[i];
		sort(c+1,c+n+1);
		int idx = 1;
		for(int i = 1;i <= n;i++)c[i].ans = idx++;
		sort(c+1,c+1+n,cmp);
		for(int i = 1;i <= n;i++)printf("%d ",c[i].ans);
		printf("\n");
	}
	return 0;
}

p.s. 当时没有想到填满b数组,其实如果不填满b数组,b[2]=2和b[2]=3得到的序列可能一样,这并不是我们想要的答案。

J题

题目大意:t组输入,每组给出s,k,p,有s个数,求所有大小为k的子集中,每个子集的所有元素的最大公约数,输出它们的乘积。t<=60,s<=4e4,k<=30,1e6<=p<=1e14.

思路:用容斥原理求出每个数作为最大公约数可以出现在几个集合中。如i作为最大公约数出现在ki个集合中,则本题要求的是∏(i=1,i=maxn)(i ^ ki),其中maxn为s个数中的最大值。由于p较大且不一定为质数,考虑用欧拉降幂(a ^ b(mod m)=a ^ (b mod phi + phi) mod m),其中phi为b的欧拉函数值。

ac代码(玄学,同样的代码有可能1001ms也有可能800ms,看评测机发挥了)

#include <bits/stdc++.h>

using namespace std;

const int N = 8e4+4;

typedef long long LL;

LL cnt[N];
LL c[N][35],dp[N];
LL S,k,res,phi;
int T;
LL P;
LL total = 0,factor[100];
//LL euler_phi(LL n) {
//  LL ans = n;
//  for (LL i = 2; i * i <= n; i++)
//    if (n % i == 0) {
//      ans = ans / i * (i - 1);
//      while (n % i == 0) n /= i;
//    }
//  if (n > 1) ans = ans / n * (n - 1);
//  return ans;
//}

LL ksc(LL a, LL b, LL mod) {
	return ((a * b - (long long)((long long)((long double)a / mod * b + 1e-3) * mod)) % mod + mod) % mod;
}
LL gcd(LL a,LL b){
	return b==0?a:gcd(b,a%b);
}
//LL ksc(LL a,LL b,LL c){
//	LL res = 0;
//	while(b){
//		if(b&1)res = (res + a)%c;
//		a = (a + a)%c;
//		b>>=1;
//	}
//	return res%c;
//}
LL ksm(LL a,LL b,LL c){
	LL res = 1;
	while(b){
		if(b&1)res =ksc(res,a,c);
		b >>= 1;
		a = ksc(a,a,c);
	}
	return res%c;
}
//LL ksm(LL a,LL b,LL c){
//	LL res = 1;
//	while(b){
//		if(b&1)res = ksc(res,a,c);
//		b >>= 1;
//		a = ksc(a,a,c);
//	}
//	return res%c;
//}


int a[] = { 2, 3, 5, 7, 11 };
bool miller_rabin(LL n) {
	if (n == 1) return 0;
	for (int i = 0; i < 5; ++i) {
		if (n == a[i]) return 1;
		if (!(n % a[i])) return 0;
	}
	LL d = n - 1;
	while (!(d & 1)) d >>= 1;
	for (int i = 1; i <= 5; ++i) {
		LL a = rand() % (n - 2) + 2;
		bool tmp_k = false;
		LL tmp_d = d;
		LL tmp = ksm(a, tmp_d, n);
		if (tmp == 1)	tmp_k = true;
		// 先把所有的2给提出来,然后在一个一个乘上去,这样比直接做快
		while (tmp != 1 && tmp != n - 1 && tmp_d != n - 1) {
			tmp = ksc(tmp, tmp, n);
			tmp_d <<= 1;
		}// 二次探测
		if (tmp == n - 1)	tmp_k = true;

		if (!tmp_k)
			return 0;
	}
	return 1;
}
// 找出n的一个因数
const int M = (1 << 7) - 1;// 一个玄学值

LL pollard_rho(LL n) {
	for (int i = 0; i < 5; ++i) if (n % a[i] == 0) return a[i];
	LL x = rand(), y = x, t = 1, a = rand() % (n - 2) + 2;
	for (int k = 2; ; k <<= 1, y = x) {
		LL q = 1;
		// k用于倍增
		for (int i = 1; i <= k; ++i) {
			x = (ksc(x, x, n) + a) % n;// f(x) = x^2+a
			q = ksc(q, abs(x - y), n);// 每一次的|x-y|累加
			// 如果次数超过M
			if (!(i & M)) {
				t = gcd(q, n);
				if (t > 1) break;
			}
		}
		if (t > 1 || (t = gcd(q, n)) > 1) break;
	}
	return t;
}
// 找出x的所有因数
void find(LL x) {
	if (x == 1 || x <= 0) return;
	if (miller_rabin(x)) {
		factor[++total] = x;
		return;
	}
	LL p = x;
	while (p == x) p = pollard_rho(x);
	while (x % p == 0) x /= p;
	find(p);// 递归遍历所有的因数
	find(x);
}
LL C(LL n,LL k){
	if (n < k)return 0;
	return c[n][k];
}
inline LL read() {
	LL s = 0;
	char ch = getchar();
	while (ch < 48 || ch>57)ch = getchar();
	while (ch > 47 && ch < 58)s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
	return s;
}
int main(){
//	scanf("%d",&T);
	T = read();
	while(T--){
		memset(cnt,0,sizeof cnt);		
//		scanf("%lld%lld%lld",&S,&k,&P);
		S = read(),k = read(),P = read();
		int maxn = 0;
		for(int i = 1;i <= S;i++){
			int tm;
//			scanf("%d",&tm);
			tm = read();
			cnt[tm]++;
			maxn = max(maxn,tm);
		}
		res = 1;
		total = 0;
		find(P);// 寻找p的约数
		phi = P;// 计算phi(p)
		for (int i = 1; i <= total; i++) {
			phi = phi / factor[i] * (factor[i] - 1);
		}
		c[0][0] = 1;
		for (int i = 1; i <= S; i++) {
			c[i][0] = 1;
			if (i <= 31)	c[i][i] = 1;
			// k的值不会超过30,不需要计算更多位
			for (int j = 1; j < i && j <= 31; j++) {
				c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]);// 组合数的递推公式
				while (c[i][j] - phi >= phi) c[i][j] -= phi;
			}

		}
//		for (int i = 0; i <= S; i ++ )
//	        for (int j = 0; j <= 30&&j<=i; j ++ )
//	            if (!j) c[i][j] = 1;
//	            else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1])%phi;
		for(int i = maxn;i >= 1;i--){
			int tp = 0;
			for(int j = i;j <= maxn;j+=i){
				tp+=cnt[j];
			}
			dp[i] = C(tp,k)%phi;
			for(int j = i + i;j <= maxn;j+=i){
				dp[i]-=dp[j];
			}
			if(dp[i]<0)dp[i] = (dp[i]%phi + phi)%phi;
			if(dp[i]>phi)res = ksc(res,(ksm(i,dp[i]%phi+phi,P)),P);
			else res = ksc(res,(ksm(i,dp[i],P)),P);		
		}
		printf("%lld\n",res);
	}
	return 0;
}

p.s. 真的是一言难尽,从写完第一份到debug完,前前后后四个小时。factor数组记得开long long;快速乘的玄学写法,很快;米勒罗宾素数筛当板子用了,求某个大数的欧拉函数;大体思路都没问题了就慢慢调整细节,尝试各种写法,说不定就快个几十ms就卡过了。

F题

题目大意:给出三维的四个点ABCD的坐标和k1,k2,要求求出同时满足|AP|>=k1|BP|,|CP|>=k2|DP|的点P的可取的区域的体积。

思路:推式子,设A(x1,y1,z1),B(x2,y2,z2),p(x,y,z),则有(x-x1)²+(y-y1)²+(z-z1)²>=k1²[(x-x2)²+(y-y2)²+(z-z2)²],整理可得(k1² - 1)(x² + y² + z²)-(2k1²x2 - 2x1)x - (2k1²y2 - 2y1)y - (2k1²z2 - 2z1)z <= 0,我们发现这是形如x²+y²+z²+Ax+By+Cz+D<=0的球面方程,而该方程的几何意义是P为该球体内部(含边界)的点。该球体半径为sqrt((A² + B² + C² - 4*D)/4),球心为(-A/2,-B/2,-C/2)。接下来分情况讨论:两球相切、包含、相交。

ac代码:

#include <bits/stdc++.h>

using  namespace std;

const int N = 1010;
const double pi = acos(-1);
int t;
double x[4],y[4],z[4],k1,k2;
int main(){
	scanf("%d",&t);
	while(t--){
		for(int i = 0;i < 4;i++)scanf("%lf%lf%lf",&x[i],&y[i],&z[i]);
		scanf("%lf%lf",&k1,&k2);
		double k11 = k1*k1-1,c1x,c1y,c1z,r1;
		c1x = (k1*k1*x[1]-x[0])/k11,c1y = (k1*k1*y[1]-y[0])/k11,c1z = (k1*k1*z[1]-z[0])/k11;
		r1 = sqrt(c1x*c1x+c1y*c1y+c1z*c1z-(k1*k1*((x[1]*x[1])+(y[1]*y[1])+(z[1]*z[1]))-x[0]*x[0]-y[0]*y[0]-z[0]*z[0])/k11);
		double k22 = k2*k2-1,c2x,c2y,c2z,r2;
		c2x = (k2*k2*x[3]-x[2])/k22,c2y = (k2*k2*y[3]-y[2])/k22,c2z = (k2*k2*z[3]-z[2])/k22;
		r2 = sqrt(c2x*c2x+c2y*c2y+c2z*c2z-(k2*k2*((x[3]*x[3])+(y[3]*y[3])+(z[3]*z[3]))-x[2]*x[2]-y[2]*y[2]-z[2]*z[2])/k22);
		double dis = sqrt((c1x-c2x)*(c1x-c2x)+(c1y-c2y)*(c1y-c2y)+(c1z-c2z)*(c1z-c2z));
		double ans = 0.000;
		if(dis>=r1+r2)ans = 0;
		else if(dis + r1 <= r2)ans = (4.000/3.000)*pi*r1*r1*r1;
		else if(dis + r2 <= r1)ans = (4.000/3.000)*pi*r2*r2*r2;
		else{
			double h1 = r1*(1-(r1*r1+dis*dis-r2*r2)/(2.000*dis*r1));//余弦定理求角度,再算h
			double h2 = r2*(1-(r2*r2+dis*dis-r1*r1)/(2.000*dis*r2));
			ans+=(1.000/3.000)*pi*((3.000*r1-h1)*h1*h1+(3.000*r2-h2)*h2*h2); 
		}
		printf("%.3f\n",ans);
	}
	return 0;
}

p.s. 赛场上没来得及看这个题…补题的时候感觉挺可做的。学习了球缺的计算公式:V=pi(3R-h)/3,其中h为球缺(那个小片)的高,R为球的半径。注意求h的时候要用余弦定理间接求一下,不能草率的直接用(r1+r2-dis)/2,因为球的半径是不一定一样大,不一定能均分滴~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值