两道甚妙的贪心题
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=bi∗di。
店主要给每个货架贴售价标签,相邻的货架如果每包售价(
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
n≤2∗105,ai≤109,bi≤105
打比赛的时候想丑了,写了个
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=bi∗di,所以这个区间内的
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)=ciai∗bi,而我们已经知道这个
c
i
c_i
ci是这段区间内
b
i
b_i
bi的最小公倍数,所以也就是需要这段区间内
a
i
∗
b
i
a_i*b_i
ai∗bi都能整除
l
c
m
(
b
i
)
lcm(b_i)
lcm(bi)。换言之,就是
g
c
d
(
a
i
∗
b
i
)
gcd(a_i*b_i)
gcd(ai∗bi)整除
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)
1≤l,r≤nmax∣al+al+1+...+ar∣≤max(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}|
1≤l,r≤nmax∣sumr−suml−1∣。这个式子等价于
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
0≤i≤nmaxsumi−0≤i≤nminsumi。(将绝对值分类成大于或者小于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)
0≤i≤nmaxsumi≤max(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)
0≤i≤nminsumi>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
sum≤0,就填一个正数,这样仍然满足
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;
}