Codeforces Round 860: 1798C+1798D 贪心

两道甚妙的贪心题请添加图片描述
1798C:传送门
题目大意:有 n n n个货架,对应 n n n种糖果,第 i i i种糖果有 a i a_i ai颗,单价 b i b_i bi。现在要把这些糖果打包,第 i i i种糖果要打包成 d i d_i di颗一包( d i d_i di是未知数),要求 a i a_i ai能被 d i d_i di整除。于是第 i i i种糖果每包的售价为 c i = b i ∗ d i c_i=b_i*d_i ci=bidi
店主要给每个货架贴售价标签,相邻的货架如果每包售价( c i c_i ci)相等,那可以用同一个标签,求最后用的最少标签数。
n ≤ 2 ∗ 1 0 5 , a i ≤ 1 0 9 , b i ≤ 1 0 5 n\le 2*10^5,a_i\le10^9,b_i\le10^5 n2105,ai109,bi105
打比赛的时候想丑了,写了个 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)的恶臭st表+dp+二分搜索,然后边界设错了,FST了(恼)
正解是贪心,可以做到 O ( n log ⁡ n ) O(n\log n) O(nlogn)请添加图片描述
首先看题目要求:要在所有的 a i a_i ai分别能被 d i d_i di整除的前提下,让相邻的 c i c_i ci尽可能相同。为了尽量让 a i a_i ai能被 d i d_i di整除,我们希望 d i d_i di越小越好。假如我们要让某个区间内的 c i c_i ci全部相同,那怎么取到最小的 d i d_i di
由于 c i = b i ∗ d i c_i=b_i*d_i ci=bidi,所以这个区间内的 b i b_i bi都是 c i c_i ci的因数,而 d i = c i b i d_i=\frac{c_i}{b_i} di=bici,因此只需要取 c i c_i ci为这段区间内所有 b i b_i bi的最小公倍数即可,可以证明这是最小的满足条件的 c i c_i ci,而其它满足条件的 c i c_i ci都是这个数的倍数,这样的话 d i d_i di更难整除 a i a_i ai
如何判断某个区间内 a i a_i ai是否都能整除 d i d_i di a i / d i = a i / ( c i b i ) = a i ∗ b i c i a_i/d_i=a_i/(\frac{c_i}{b_i})=\frac{a_i*b_i}{c_i} ai/di=ai/(bici)=ciaibi,而我们已经知道这个 c i c_i ci是这段区间内 b i b_i bi的最小公倍数,所以也就是需要这段区间内 a i ∗ b i a_i*b_i aibi都能整除 l c m ( b i ) lcm(b_i) lcm(bi)。换言之,就是 g c d ( a i ∗ b i ) gcd(a_i*b_i) gcd(aibi)整除 l c m ( b i ) lcm(b_i) lcm(bi)
注意到如果一个售价标签对某段区间都适用,那么排除掉区间内任意一种糖果,这个售价标签仍然适用。所以,可以从左端开始往右遍历,将能用同一个售价标签的糖果都用同一个,否则加一个新的标签。

#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<vector>
#define mod 1000000007
using namespace std;
typedef long long ll;
int read() {
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
ll fpow(ll b,ll p) {
	int ans=1;
	while(p) {
		if(p&1)	ans=(ll)ans*b%mod;
		b=(ll)b*b%mod;
		p>>=1;
	}
	return ans;
}
inline ll inv(ll x) {
	return fpow(x,mod-2);
}
ll gcd(ll x,ll y) {
	if(x%y==0)	return y;
	return gcd(y,x%y);
}
ll lcm(ll x,ll y) {
	if(x==-1 || y==-1)	return -1;
	ll ans=x/gcd(x,y)*y;
	if(ans>1e14)	return -1;
	return ans;
}
const int Size=200005;
const int INF=0x3f3f3f3f;
int n;
ll a[Size],b[Size];
int main() {
//	freopen("data.txt","r",stdin);
//	freopen("WA.txt","w",stdout);
	int T=read();
	while(T--) {
		n=read();
		for(int i=1; i<=n; i++) {
			a[i]=read();
			b[i]=read();
		}
		int ans=0;
		for(int i=1; i<=n; ans++) {
			int r=i+1;
			ll nowg=a[i]*b[i],nowl=b[i];
			while(r<=n) {
				nowg=gcd(nowg,a[r]*b[r]);
				nowl=lcm(nowl,b[r]);
				if(nowl==-1 || nowg%nowl) {
					break;
				}
				r++;
			}
			i=r;
		}
		printf("%d\n",ans);
	}
	return 0;
}

1798D:传送门
题目大意:给出一个 n n n个数的序列 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an,满足 a 1 + a 2 + . . . + a n = 0 a_1+a_2+...+a_n=0 a1+a2+...+an=0。要求重新排列这个数列,使得 max ⁡ 1 ≤ l , r ≤ n ∣ a l + a l + 1 + . . . + a r ∣ ≤ max ⁡ ( a 1 , a 2 , . . . , a n ) − min ⁡ ( a 1 , a 2 , . . . , a n ) \max\limits_{1\le l,r\le n}|a_l+a_{l+1}+...+a_r|\le\max(a_1,a_2,...,a_n)-\min(a_1,a_2,...,a_n) 1l,rnmaxal+al+1+...+armax(a1,a2,...,an)min(a1,a2,...,an)
如果这个序列全是0,那就无解。否则序列里既有正数又有负数。那么这样操作:
首先把左边写成前缀和的形式,也就是 max ⁡ 1 ≤ l , r ≤ n ∣ s u m r − s u m l − 1 ∣ \max\limits_{1\le l,r\le n}|sum_r-sum_{l-1}| 1l,rnmaxsumrsuml1。这个式子等价于 max ⁡ 0 ≤ i ≤ n s u m i − min ⁡ 0 ≤ i ≤ n s u m i \max\limits_{0\le_i\le n}sum_i -\min\limits_{0\le i\le n}sum_i 0inmaxsumi0inminsumi。(将绝对值分类成大于或者小于0拆开)
如果最后我们重排得到的序列满足 max ⁡ 0 ≤ i ≤ n s u m i ≤ max ⁡ ( a 1 , a 2 , . . . , a n ) \max\limits_{0\le i\le n}sum_i\le \max(a_1,a_2,...,a_n) 0inmaxsumimax(a1,a2,...,an) min ⁡ 0 ≤ i ≤ n s u m i > min ⁡ ( a 1 , a 2 , . . . , a n ) \min\limits_{0\le i\le n}sum_i>\min(a_1,a_2,...,a_n) 0inminsumi>min(a1,a2,...,an) min ⁡ ( a 1 , a 2 , . . . , a n ) \min(a_1,a_2,...,a_n) min(a1,a2,...,an)一定是负的),那么这个序列一定满足题目要求。可以这样排列:
从左到右逐个往里面填数,如果当前 s u m ≤ 0 sum\le 0 sum0,就填一个正数,这样仍然满足 max ⁡ ( s u m i ) ≤ max ⁡ ( a i ) \max( sum_i)\le\max(a_i) max(sumi)max(ai)。如果当前 s u m > 0 sum>0 sum>0,就填一个负数,这样仍然满足 min ⁡ ( s u m i ) > min ⁡ ( a i ) \min(sum_i)>\min(a_i) min(sumi)>min(ai)。由于 a 1 + a 2 + . . . + a n = 0 a_1+a_2+...+a_n=0 a1+a2+...+an=0,这样的填数策略一定可以找到相应的正数或负数。最后只剩下0,不影响答案。

#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<vector>
#define mod 1000000007
using namespace std;
typedef long long ll;
int read() {
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
ll fpow(ll b,ll p) {
	int ans=1;
	while(p) {
		if(p&1)	ans=(ll)ans*b%mod;
		b=(ll)b*b%mod;
		p>>=1;
	}
	return ans;
}
inline ll inv(ll x) {
	return fpow(x,mod-2);
}
ll gcd(ll x,ll y) {
	if(x%y==0)	return y;
	return gcd(y,x%y);
}
ll lcm(ll x,ll y) {
	if(x==-1 || y==-1)	return -1;
	ll ans=x/gcd(x,y)*y;
	if(ans>1e14)	return -1;
	return ans;
}
const int Size=300005;
const int INF=0x3f3f3f3f;
int n,a[Size],ans[Size];

int main() {
//	freopen("data.txt","r",stdin);
//	freopen("WA.txt","w",stdout);
	int T=read();
	while(T--) {
		n=read();
		for(int i=1; i<=n; i++)	a[i]=read();
		sort(a+1,a+1+n);
		if(a[1]==0 && a[n]==0) {
			puts("No");
			continue;
		}
		int l=1,r=n,cnt=0;
		ll sum=0;
		while(l<=r) {
			if(sum<=0) {
				sum+=a[r];
				ans[++cnt]=a[r--];
			} else {
				sum+=a[l];
				ans[++cnt]=a[l++];
			}
		}
		puts("Yes");
		for(int i=1; i<=n; i++)	printf("%d ",ans[i]);
		putchar(10);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值