Codeforces Round 901 div2 C + D

C. Jellyfish and Green Apple

C

题意

n n n 个均为 1 k g 1kg 1kg 的苹果,要平均分给 m m m 个人,每次操作可以将一块苹果平分成两份。问最少需要多少次操作才能平均分配?(或者 − 1 -1 1表示无法平均分配)

思路

对于 n > m n>m n>m 的时候,我们只需要给每个人都分一个苹果,直到 n < m n<m n<m。所以我们只需考虑 n < m n<m n<m 的情况
每个人可以分到 n m \frac{n}{m} mn 的苹果,我们先将其约分成最简分数的形式,也就是分子分母同时除 g c d ( n , m ) gcd(n,m) gcd(n,m),此时如果发现分母 m g c d ( n , m ) \dfrac {m}{gcd(n,m)} gcd(n,m)m 不是 2 2 2 的整数次幂,那么答案很显然就是 − 1 -1 1。否则我们可以使用 n g c d ( n , m ) \dfrac {n}{gcd(n,m)} gcd(n,m)n m g c d ( n , m ) \dfrac {m}{gcd(n,m)} gcd(n,m)m 来构成小数部分。

判断一个数是否是 2 2 2 的整数次幂可以使用:__builtin_popcount()

可以注意到,每切一次,苹果块的数量就会增加 1 1 1。所以切的次数最少 ⇔ \Leftrightarrow 最后苹果块的数量最少。

可以证明对于 n g c d ( n , m ) \dfrac {n}{gcd(n,m)} gcd(n,m)n m g c d ( n , m ) \dfrac {m}{gcd(n,m)} gcd(n,m)m,一定是以 n g c d ( n , m ) \dfrac {n}{gcd(n,m)} gcd(n,m)n二进制形式来构成是最优的。比如 n = 3 , m = 4 n = 3,m = 4 n=3,m=4 n = [ 11 ] 2 n = [11]_2\quad n=[11]2。那么一定是 2 个 0.25 = 0.5 2 个 0.25 = 0.5\quad 20.25=0.5 + 1 个 0.25 \quad1 个 0.25 10.25 来构成小数部分是最优的。

那么最后除了一开始的不用切割的整个完整苹果的分配,小数部分最后每个人都有若干个苹果块的话,减去一开始用来切割成小数部分的 n n n 个,就是增加的苹果块数量,也即是切割数字了。

// Problem: C. Jellyfish and Green Apple
// Contest: Codeforces - Codeforces Round 901 (Div. 2)
// URL: https://codeforces.com/contest/1875/problem/C
// Memory Limit: 256 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n' 

const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;

typedef long long ll;

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int t;
    std::cin>>t;
    while(t--){
    	int n,m;
    	std::cin>>n>>m;
    	n %= m;
    	int a = n / std::__gcd(n,m);
    	int b = m / std::__gcd(n,m);
    	if(__builtin_popcount(b) > 1) std::cout<<-1<<endl;
    	else std::cout<<1ll * __builtin_popcount(a)*m - n<<endl;
    }
	return 0; 
}

D. Jellyfish and Mex

D

题意

有一个长度为 n n n 的非负整数数组 a a a m m m 初始为 0 0 0
做下列操作 n n n 次:

  • 删除 a a a 中的一个元素
  • M E X ( a ) MEX(a) MEX(a) 加到 m m m

求最后 m m m 的最小值

思路

任何长度为 n n n 的非负整数数组 a a a,其 M E X ( a ) MEX(a) MEX(a) ≤ n \leq n n

对于当前的 M E X ( a ) MEX(a) MEX(a),我们一定是选择一个小于 M E X ( a ) MEX(a) MEX(a) x x x 来删除,否则就是无用功。一旦选择了一个 x x x ,剩下的操作就一定是优先将 x x x 全部删除,从而将 M E X ( a ) MEX(a) MEX(a) 更新为 x x x。对于那些大于 x x x 的元素,我们可以留到 M E X ( a ) MEX(a) MEX(a) 变为 0 0 0 后再删除。那么现在要考虑的就是 x x x 的选择顺序问题,可以肯定的是: x x x 的选择一定是递减的。

考虑 D P DP DP:定义 d p [ i ] dp[i] dp[i] 代表在 M E X ( a ) = i MEX(a) = i MEX(a)=i 并且还没有删除任何 x < i x < i x<i 的元素的条件下,表示的 m m m 的最小值。那么转移方程为:对于某个值 j j j ∀ i > j , d p [ j ] = m i n ( d p [ j ] , d p [ i ] + i ⋅ ( c n t [ j ] − 1 ) + j ) \forall i>j,dp[j] = min(dp[j],dp[i] +i\cdot(cnt[j]-1) + j) i>j,dp[j]=min(dp[j],dp[i]+i(cnt[j]1)+j),意思就是将 c n t [ j ] cnt[j] cnt[j] j j j 全部删除,除了最后一次删除后 M E X ( a ) MEX(a) MEX(a) 变为 j j j,前面全部的操作 M E X ( a ) MEX(a) MEX(a) 都是 i i i

// Problem: D. Jellyfish and Mex
// Contest: Codeforces - Codeforces Round 901 (Div. 2)
// URL: https://codeforces.com/contest/1875/problem/D
// Memory Limit: 256 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n' 

const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;

typedef long long ll;

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int t;
    std::cin>>t;
    while(t--){
    	int n;
    	std::cin>>n;
    	std::vector<ll> cnt(n+5,0);
    	int x;
    	fore(i,1,n+1){
    		std::cin>>x;
    		if(x <= n)	++cnt[x];
    	}
    	int mex = 0;
    	while(cnt[mex])	 ++mex;
    	std::vector<ll> dp(n+5,INFLL);
    	dp[mex] = 0;
    	for(int i=mex;i>=1;--i)
    		for(int j=0;j<i;++j)
    			dp[j] = std::min(dp[j],dp[i] + 1ll*i*(cnt[j]-1) + j);
    	std::cout<<dp[0]<<endl;
    }
	return 0; 
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值