• 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
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    基本题

    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
    #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]
    			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
    
    */ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    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
    #include
    #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
    
    */ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101

    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
    #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
    
    */ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    进阶题

    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
    #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
    
    */ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    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
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

  • 相关阅读:
    gunicorn的基本使用
    「面试逆袭」MySQL六十六问大汇总!
    docker学习进阶之docker-compose(二)
    Unity之手游UI的点击和方向移动
    css常见居中方式
    《effective c++》学习笔记一
    计算机毕业设计Java机票实时比价系统(源码+系统+mysql数据库+lw文档)
    【python】二:基础学习-组织架构函数等
    Oracle,可保留在快速恢复区(FRA)的文件类型
    Springcloud实战之自研分布式id生成器
  • 原文地址:https://blog.csdn.net/weixin_51937688/article/details/126076130