2022牛客多校联赛第四场 题解

比赛传送门
作者: fn


签到题

N题 Particle Arts / 粒子艺术

题目大意
𝑛 𝑛 n 个粒子,每个粒子有一个能量 𝑎 𝑖 𝑎_𝑖 ai 。两两随机碰撞,每碰撞一次两粒子的能量变为 𝑎 & 𝑏 , 𝑎 ∣ 𝑏 𝑎\&𝑏, 𝑎|𝑏 a&b,ab
求所有粒子能量稳定后的方差。 𝑛 ≤ 1 0 5 , 0 ≤ 𝑎 𝑖 < 2 15 𝑛≤10^5,0≤𝑎_𝑖<2^{15} n105,0ai<215

考察内容
贪心

分析
每次碰撞后的 𝑎 & 𝑏 , 𝑎 ∣ 𝑏 𝑎\&𝑏, 𝑎|𝑏 a&b,ab 和原先相比,小的更小而大的更大,所以每次碰撞后方差单调增加。
预处理每个二进制位的数量,把他们尽可能放到少的数字里面去,得到的结果即最终结果,此时方差最大。

求方差的时候开__int128就行。

#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=1e6+10;
ll n,m,a[N];
ll b[N];
ll num[17];

__int128 gcd(__int128 a,__int128 b){
    if(b==0)
        return a;
    else
        return gcd(b,a%b);
}

void print(__int128 x){  
    if(!x){  
        cout<<"0";
    }  
    string ret="";  
    while(x){  
        ret+=x%10+'0';  
        x/=10;  
    }  
    reverse(ret.begin(),ret.end());  
    cout<<ret;  
} 

int main(){ 
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);

	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i]; 
		int p=0;
		while(a[i]>0){
			num[p]+=a[i]%2;
			a[i]/=2;
			p++;
		}
	}
	
	for(int i=1;i<=n;i++){
		for(int j=0;j<=15;j++){
			if(num[j]>0){
				num[j]--;
				b[i] += (1<<j);
			}
		}
	}
	
	__int128 sum=0;
	for(int i=1;i<=n;i++){
		sum+=b[i];
	}
	
	__int128 d1=0,d2=n*n*n;
	for(int i=1;i<=n;i++){
		__int128 t1=b[i]*n-sum;
		d1+=t1*t1;
	}
	
	__int128 g1=gcd(d1,d2);
	d1/=g1; d2/=g1;
	
	if(d1==0){
		d2=1;
	}
    
	print(d1);
    cout<<"/";
    print(d2);
    
	return 0;
}

基本题

K题 NIO’s Sword / NIO的剑

题目大意
玩家初始有一把攻击力为 0 0 0 的剑,需要依次击杀 𝑛 ( 1 ≤ n ≤ 1 0 6 ) 𝑛 (1\le n\le 10^{6}) n(1n106) 个敌人,仅当攻击力模 𝑛 𝑛 n 𝑖 𝑖 i 同余才能击杀第 𝑖 𝑖 i 个敌人。
玩家可以升级剑,每次升级相当于在当前攻击力后面添加一个数字,问最少需要几次升级。

考察内容
贪心,暴力

分析
1 ≤ n ≤ 1 0 6 1\le n\le 10^{6} 1n106,所以最多升级7次(在后面加7个数字)肯定可以杀掉一个敌人。对于每一个敌人,直接枚举升级的次数即可。

#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=1e6+10;
ll n,m,a[N];
ll po[10];

int main(){ 
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	po[0]=1;
	for(int i=1;i<=7;i++){
		po[i]=po[i-1]*10; // 预处理10的幂次 
	}

	cin>>n;
	
	ll ans=0; 
	ll cnt=0; // 当前武器的数值 
	
	for(int i=1;i<=n;i++){ // 枚举每个怪物 
		if(cnt%n==i%n)continue; // 不用升级就能击败 
		
		for(int j=1;j<=7;j++){ // 最多升级7次肯定可以找到方案打败怪物 
			cnt*=10;
			cnt%=n;
			
			if(po[j]>=n){
				cnt=i%n; 
				ans+=j;
				break;
			}
			
			// po[j]<n
			if(cnt<=i%n && cnt+po[j]-1>=i%n){ // 可以击败	
				cnt=i%n; 
				ans+=j;
				break;
			} 
			else if(cnt<=i%n+n && cnt+po[j]-1>=i%n+n){ // 可以击败	
				cnt=i%n; 
				ans+=j;
				break;
			} 
		}	
	}
	
	cout<<ans<<endl;
	
	return 0;
}
/*
输入: 
1000000

输出: 
5877904

*/ 

D题 Jobs (Easy Version) / 工作(简单版)

题目大意
𝑛 𝑛 n 个公司,第 𝑖 𝑖 i 个公司有 𝑚 𝑖 𝑚_𝑖 mi 个工作,每个工作对三个能力分别有数值要求,必须三个能力都达标才能胜任这份工作。一个人只要能胜任一个公司的任意一份工作就可以去这个公司工作。
询问 𝑞 𝑞 q 次,每次询问随机给出一个人三个能力的数值,求这个人可以去多少个公司工作。
强制在线。
𝑛 ≤ 10 , ∑ 𝑚 𝑖 ≤ 1 0 6 , 𝑞 ≤ 2 × 1 0 6 𝑛≤10,∑𝑚_𝑖≤10^6 , 𝑞≤2×10^6 n10,mi106,q2×106 ,能力的数值不超过400

考察内容
贪心,排序,暴力,复杂度优化

分析
解法不唯一。

对每一家单位的所有工作先按照三个能力的总和sum从小到大排序,然后暴力判断即可。

一个人的三个能力不到sum时,后面的工作肯定也无法胜任,可以直接break掉。
快读优化一下。

#include<bits/stdc++.h>
#include<random>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+5;
const int maxq=2e6+5;
const ll mod=998244353;
ll read(ll &n){
	char ch=' '; ll q=0,w=1;
	for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
	if(ch=='-')w=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar())q=q*10+ch-48;
	n=q*w; return n;
}

ll n,q,seed; 
ll m[N];
ll po[maxq];

struct node{
	ll q1,q2,q3;
	ll sumq; // q1+q2+q3
}a[11][N];

ll solve(ll IQ,ll EQ,ll AQ){ // 计算能被多少家公司录用 
	ll sum0=IQ+EQ+AQ; // 三个商之和 
	ll num=0;
	
	for(int i=1;i<=n;i++){ // 判断是否能被第i家公司录用 
		int F=0;
		
		// 枚举m[i]份工作 
		for(int j=1;j<=m[i];j++){
			if(sum0<a[i][j].sumq){ // 三个商之和不够,这家公司不行 
				break;
			}
			
			// sum0>=a[i][j].sumq时 
			if(IQ>=a[i][j].q1 && EQ>=a[i][j].q2 && AQ>=a[i][j].q3){
				F=1; // 可以胜任 
				break;
			}
		}
		if(F)num++;
	}
	return num;
}


bool cmp(node x,node y){
	return x.sumq<y.sumq; // "三个和"更小的工作放前面 
}

signed main(){ // wa..
	read(n); read(q);
	for(int i=1;i<=n;i++){
		read(m[i]);
		for(int j=1;j<=m[i];j++){
			read(a[i][j].q1); read(a[i][j].q2); read(a[i][j].q3);
			a[i][j].sumq=a[i][j].q1+a[i][j].q2+a[i][j].q3;
		}	
		sort(a[i]+1,a[i]+m[i]+1,cmp); // 排序 
	}
	read(seed); // 读入随机种子seed 
	std::mt19937 rng(seed);
	std::uniform_int_distribution<> u(1,400);
	
	ll seedmod=seed%mod; // 防止爆long long 
	po[0]=1;
	for(int i=1;i<=q;i++){
		po[i]=po[i-1]*seedmod%mod; // 预处理seed的幂次 
	}
	
	ll ans=0;
	
	int lastans=0;
	for (int i=1;i<=q;i++){ // 询问q次 
	    int IQ=(u(rng)^lastans)%400+1;  // The IQ of the i-th friend
	    int EQ=(u(rng)^lastans)%400+1;  // The EQ of the i-th friend
	    int AQ=(u(rng)^lastans)%400+1;  // The AQ of the i-th friend
	    lastans=solve(IQ,EQ,AQ)%mod;  // The answer to the i-th friend
		
		ans+=lastans*po[q-i]%mod;
		ans%=mod;
	}
	
	cout<<(ans+mod)%mod<<endl;

	return 0;
}
/*
3 5
3 2 1 2 400 400 400 1 1 2 
3 1 3 2 2 3 1 2 3 3
2 2 3 1 3 2 1
998244353

*/ 

H题 Wall Builder II / 墙体建造师II

题目大意
n ( n ≤ 100 ) n (n \leq 100) n(n100) 1 ∗ 1 1*1 11 的矩形, n − 1 n-1 n1 1 ∗ 2 1*2 12 的矩形, n − 2 n-2 n2 1 ∗ 3 1*3 13 的矩形…, 1 1 1 1 ∗ n 1*n 1n 的矩形,把这些矩形拼成一个大矩形,求大矩形的最小周长。

考察内容
贪心,构造

分析
n n n 已经给定了,所以总面积可以直接枚举计算出来。
容易发现,给了这么多 1 ∗ n 1*n 1n 的小块的情况下,把这个总面积摆成任意一个 x ∗ y x*y xy 的大矩形,都是能用小块拼出来的。直接枚举总面积的因数,选最接近正方形的那个轮廓即可。

摆的时候贪心一下,一层层摆,每层优先用剩下来的小块里面,能放得下的最长的那个即可。

#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=105;
ll n;
ll num[N]; // num[i]记录1*i的积木数量 

int p=0;
struct node{
	ll x1,y1,x2,y2;	
}a[N*N];

int main(){ 
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	int t; cin>>t;
	while(t--){
		p=0; // 初始化p 
		
		cin>>n; // 最小周长
		ll sum=0; // 总面积, sum<=171700
		for(int i=1;i<=n;i++){
			sum+=i*(n+1-i);	
		}
		for(int i=1;i<=n;i++){
			num[i]=n+1-i;
		}
		
		ll len1=(n+1)*2; // 记录最小周长 
		
		ll h=sqrt(sum);
		for(int i=h;i>=1;i--){ // 枚举sum的因数 
			if(sum%i==0){ // 可以整除 
				ll xall=sum/i;
				ll yall=i;
				
				for(int i=1;i<=yall;i++){
					int j=0;
					while(j<xall && p<=n*(n+1)/2){
						for(int k=min(n,xall-j);k>=1;k--){ // 贪心,尽量用长的积木 
							if(num[k]==0)continue;
							
							// num[k]>0时 
							num[k]--;
							
							a[++p].x1=j;
							a[p].y1=i-1; 
							a[p].x2=j+k;
							a[p].y2=i;
							
							j+=k;
							break; 
						}
					}
					// j>=xall时, 接着看上一行 
				}
				
				len1=(i+sum/i)*2; // 更新len1
				break;
			}
		}
		
		cout<<len1<<endl;
		for(int i=1;i<=p;i++){
			cout<<a[i].x1<<' '<<a[i].y1<<' '<<a[i].x2<<' '<<a[i].y2<<endl;
		} 
	}
	return 0;
}
/*
1
4

*/ 

进阶题

A题 Task Computing / 任务计算

题目大意
n n n 个任务中选 m m m ( 𝑎 1 , 𝑎 2 , . . . . . . , 𝑎 𝑚 ) (𝑎_1,𝑎_2,......,𝑎_𝑚) (a1,a2,......,am) 出来并任意排序,收益是 ∑ 𝑖 = 1 𝑚 𝑤 𝑎 𝑖 ∏ 𝑗 𝑖 − 1 𝑝 𝑎 𝑗 ∑_{𝑖=1}^𝑚 {𝑤_{𝑎_𝑖 }}∏_𝑗^{𝑖−1}{𝑝_{𝑎_𝑗}} i=1mwaiji1paj ,求最大收益。

1 ≤ n ≤ 1 0 5 , 1 ≤ m ≤ min ⁡ ( n , 20 ) 1\le n\le 10^5, 1\le m \le \min(n,20) 1n105,1mmin(n,20)

考察内容
动态规划,排序

分析
先对整个数组,用类似 “国王游戏” 里面用到的方法进行一遍排序。

然后上动态规划。
状态:
f [ i ] f[i] f[i] 表示选上了 i i i 个任务时的最大收益。

边界:
f [ 0 ] = 0 f[0]=0 f[0]=0 ,不选的时候没有收益。

转移:
f [ j ] ∗ a [ i ] . p + a [ i ] . w > f [ j + 1 ] f[j]*a[i].p+a[i].w>f[j+1] f[j]a[i].p+a[i].w>f[j+1] 时, f [ j + 1 ] = f [ j ] ∗ a [ i ] . p + a [ i ] . w f[j+1]=f[j]*a[i].p+a[i].w f[j+1]=f[j]a[i].p+a[i].w
倒着进行转移,取消后效性。

#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=1e5+10;
ll n,m;
struct node{
	ll w,q;
}a[N];

bool cmp2(node x,node y){ // 类似"国王游戏"中的排序 
	return x.w*(10000-y.q)>y.w*(10000-x.q); // 
} 

double f[25];

int main(){ // "国王游戏" + dp
	cin>>n>>m; // 选m个,m<=20 
	for(int i=1;i<=n;i++){
		cin>>a[i].w;	
	}
	for(int i=1;i<=n;i++){
		cin>>a[i].q;	
	}
	sort(a+1,a+n+1,cmp2); // 排序 

	f[0]=0;
	for(int i=1;i<=m;i++){
		f[i]=-1e9-10;
	}
	
	for(int i=n;i>0;i--){
		for(int j=m-1;j>=0;j--){
			f[j+1]=max(f[j+1], f[j]*a[i].q/10000+a[i].w);
		}
	}	
	printf("%.7f\n",f[m]);
	return 0;
}
/*
输入: 
3 2
1000000000 1000000000 999999999
8000 8000 12000

输出:
2199999999

*/ 

L题 Black Hole / 黑洞

题目大意
求边长为 𝑎 𝑎 a 的凸正 𝑛 𝑛 n 面体收缩 𝑘 𝑘 k 次后的面数和边长。
一次收缩定义为作该几何体所有面中心点的三维凸包*。
𝑇 𝑇 T 组询问。
1 ≤ 𝑛 ≤ 200 , 1 ≤ 𝑎 ≤ 1000 , 1 ≤ 𝑘 ≤ 20 , 1 ≤ 𝑇 ≤ 100 1≤𝑛≤200,1≤𝑎≤1000,1≤𝑘≤20,1≤𝑇≤100 1n200,1a1000,1k20,1T100

*三维凸包: 给定一堆三维空间中的点,包含它们的最小凸多面体称为这些点的三维凸包。

考察内容
数学知识,计算几何

分析
凸正多面体只有5种,面数为:4,6,8,12,20

收缩的定义下:
正四面体→正四面体,
正八面体→正六面体,
正六面体→正八面体,
正十二面体→正二十面体,
正二十面体→正十二面体。

用立体几何知识对五种情况求出两个面的连心线长度即可。
连心线长度

#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=205;
double f[N],h;
int to[N];

int main(){ // 数学知识 
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	
	// 预处理答案 
	to[4]=4,to[6]=8,to[8]=6,to[12]=20,to[20]=12; 
	
	f[4]=1.0/3;
	f[6]=sqrt(0.5);
	f[8]=sqrt(2.0/9);
	h=sqrt(5);
	f[12]=sqrt(2/(35-15*h));
	f[20]=(1+h)/6;
	
	int t; scanf("%d",&t);
	int x,k;
	double a;
	while(t--){
		scanf("%d%lf%d",&x,&a,&k);
		if(!to[x]){ // 不属于5种情况之一 
			printf("impossible\n");
			continue;
		}
		
		printf("possible ");
		while(k--){
			a*=f[x];
			x=to[x];
		}
		printf("%d %.11lf\n",x,a);
	}
	return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值