题目描述:
老虎和蒜头是好朋友。
老虎最近得到了长度为 $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][jmod 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 ",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 ",ans); return 0; }