链接:https://ac.nowcoder.com/acm/contest/893/E
来源:牛客网
在ACM队暑假集训的某一天,实验室里有n个人。因为天气很热,大家都很想吃西瓜。
于是Eric买了 m个西瓜拿到了实验室。
Eric把这 n个人分别编号为1,2,3…n,他希望这n个人循环轮流来吃西瓜。
也就是说从 1号开始,然后2号,3号… n号依次吃西瓜,n号吃完后又轮到1号来吃,直到这m个西瓜被吃完。
而这 n个人每个人有一个肚量值,第i个人的肚量值为ai。
lililalala是这 n个人肚量值最大的人,不仅如此,他还非常贪吃,每次轮到他吃西瓜时,都会直接吃掉等同于他的度量值数量的西瓜。如果剩余的西瓜已经不够吃了,那么他会把所有西瓜直接吃完。(是的他很能吃)
除了lililalala以外的其他人,对于第 i号每次吃西瓜可以选择吃掉 [1,ai]中任意整数数量个西瓜。当然,不能超过当前剩余的西瓜数量。
为了使吃西瓜更有意思一些,Eric规定如果在轮到某个人吃西瓜时没有西瓜了,那么由他来打扫一次实验室。(是的大家都很能吃)
其他人都觉得lililalala吃的太多了应该由他来打扫卫生。请问在其他人串通好的情况下能否合理安排每个人的行动使得吃完西瓜后由lililalala来打扫卫生?
先对于问题进行分析,我们假设,如果lililalala(以下简称拉拉)一定是第一个人的话,那问题就会变得很明确:拉拉后面的人要做到在经过x轮之后使得刚好把西瓜吃完。
所以,我们可以对给出的数据进行改造,使得拉拉变成我们想要的第一个人。
怎么改造呢?其实枚举就可以,从每个人吃一个到每个人吃自己的肚量逐一枚举。但由于a[i]的数据范围过大,需要进行一些特判:
if(mx-1>m){
//如果拉拉前面的人数大于m,那就算每个人只吃一个也无法轮到拉拉
printf("NO\n");
continue;
}
if(mx-1<=m&&s[mx-1]>=m){
//如果拉拉前面的人数小于等于m,并且肚量加起来大于等于m,那我一定有办法刚好吃掉m个
printf("YES\n");
continue;
}
经过上面的特判,其实我们已经把枚举范围压缩到了小于m(1e6),在时间复杂度上是允许的。
然后我们就可以开始枚举:
int flag=0;
for(int i=mx-1;i<=s[mx-1];i++){//s为前缀和
if(f(m-i)==1){//f为判断函数
flag=1;
break;
}
}
函数中也加了特判:
1.如果x为0,那就是刚好轮到拉拉时没得吃.
2.如果拉拉吃掉西瓜之后,接下来每个人就算一人只吃一个也无法结束这轮,那就无解.
然后我们求出游戏进行的最大轮数r和最小轮数l,也就是除了拉拉外所有人一次只吃一个和除了拉拉外所有人每次都吃最多的轮数。
显然可得,当这两个轮数不同时必然有解,证明如下:
如果l!=r,那我们肯定有办法控制在任意(l+1,r)轮之间刚好结束,这样就能使得拉拉在下一轮开始无法吃到西瓜,从而胜利。
如果l=r呢?首先我们要考虑到如果 l 或 r 为刚好结束一轮,那样也是直接胜利。否则得话,我们吃再多或者再少也无法在其他轮次结束游戏,也就是说,我们无法使得轮到拉拉时西瓜刚好吃光,所以无解。
int f(LL x){
if(x==0){
return 1;
}
if(x<n-1+a[mx]){
return 0;
}
if(x<=s[n])return 1;
if(x%(n-1+a[mx])==0)return 1;
LL l=x/s[n],r=x/(n-1+a[mx]);
if(l!=r){
return 1;
}
else{
return 0;
}
}
最后,最最重要的一个特判(因为少了这个特判我wa了22发并且没过!!),那就是:如果只有一个人,也就是只有拉拉自己时,必定是他打扫卫生(我恨啊!)。
最后附上完整代码
#include
using namespace std;
#define LL long long
#define FI first
#define SE second
#define PB push_back
#define POP pop_back
#define D double
#define endl '\n'
typedef pair<int,int> PII;
const LL INF=1e18,mod=100000000;
const int N=1e5+7;
int a[N];
LL s[N];
int mx;
int n,m;
int f(LL x){
if(x==0){
return 1;
}
if(x<n-1+a[mx]){
return 0;
}
if(x<=s[n])return 1;
if(x%(n-1+a[mx])==0)return 1;
LL l=x/s[n],r=x/(n-1+a[mx]);
if(l!=r){
return 1;
}
else{
return 0;
}
}
int main()
{
int t;
cin>>t;
while(t--){
cin>>n>>m;
mx=0;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
if(a[mx]<a[i])mx=i;
s[i]=s[i-1]+a[i];//前缀和
}
if(n==1){
//如果只有拉拉一个人,那一定是他打扫卫生
printf("YES\n");
continue;
}
if(mx-1>m){
//如果拉拉前面的人数大于m,那就算每个人只吃一个也无法轮到拉拉
printf("NO\n");
continue;
}
if(mx-1<=m&&s[mx-1]>=m){
//如果拉拉前面的人数小于等于m,并且肚量加起来大于等于m,那我一定有办法刚好吃掉m个
printf("YES\n");
continue;
}
int flag=0;
for(int i=mx-1;i<=s[mx-1];i++){
if(f(m-i)==1){
flag=1;
break;
}
}
if(flag==1){
printf("YES\n");
}
else printf("NO\n");
}
return 0;
}