dtoj#4211. 一排数(array)

题目描述:

老虎和蒜头是好朋友。
老虎最近得到了长度为 $n$ 的数列 $a$,对于一个数列,老虎定义了一个优秀度,其通过如下方式计算: 令 $x_1 = a_1,x_i = x_{i−1} \mod a_i$,那么这个优秀度就是 $x_n$。 现在老虎想要知道在任意排列 $a$ 的情况下,最大的可能优秀度是多少。

数据范围:

对于 $100\%$ 的数据,$1 \leq n , a _ { i } \leq 10 ^ { 5 }$ 。

 算法标签:DP,二进制优化更新dp

思路:

首先取模操作只有比当前数小的才有用,所以我们呢可以把数从大往小排。

如果最小的数只有一个,那么答案就是这个数字了,因为其他数在对最小的数取模最大也只能得到最小的数 $-1$ 的答案。

其他情况我们可以把相同的数都去掉。

$f[i][j]$ 表示经过前 $i$ 个数取模或不取模能否的都当前这个数 $j$ 。

有两种转移:

$if(f[i-1][j]==1)$

$f[i][j]=1$  $f[i][j\mod a[i]]=1$

看到这个式子我们发现可以把第一维省去。

然后对于每一次转移其实是以a[i]为单位每一块跟第一块取&,所以考虑二进制优化。

以下代码:

#include<bits/stdc++.h>
#define il inline
#define _(d) while(d(isdigit(ch=getchar())))
using namespace std;
const int N=1e5+5;
bitset<N> f,h,g;
int n,a[N],c[N],tot,mx;
il int read(){
    int x,f=1;char ch;
    _(!)ch=='-'?f=-1:f;x=ch^48;
    _()x=(x<<1)+(x<<3)+(ch^48);
    return f*x;
}
bool cmp(int t1,int t2){return t1>t2;}
il void work(int x,int y){
    g=f;
    for(int i=0;(i<<1)<=y;y-=(1<<i),i++)g|=g>>(x*(1<<i));
    if(y)g|=g>>x*y;
    f|=g&h;
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++)a[i]=read();
    sort(a+1,a+1+n,cmp);
    if(a[n]!=a[n-1]){printf("%d\n",a[n]);return 0;}
    tot=unique(a+1,a+1+n)-a-1;mx=a[1];
    for(int i=0;i<mx;i++)h[i]=1;
    for(int i=1;i<=tot;i++)f[a[i]]=1;
    for(int i=1;i<=tot;i++){
        for(int j=a[i];j<a[i-1];j++)h[j]=0;
        work(a[i],mx/a[i]);
    }
    int ans=0;
    for(int i=mx-1;i;i--){
        if(f[i])ans=max(ans,i%a[tot]);
    }
    printf("%d\n",ans);
    return 0;
}
View Code

 

posted @ 2019-02-27 13:40  Jessiejzy  阅读(479)  评论(0编辑  收藏  举报