题目描述:
有一个N的全排列
每个位置数的价值是他的阶乘 mod 998857459
现在给出m个询问,每次给出一个数,问最短的子段区间和大于等于这个数的时候,这个最短子段的长度是多少, 不存在请输出 -1 (子段和也要 mod 998857459)
题目分析:
首先 对于M个询问,值大的子段区间长度一定不小于值小的,即如果我们把M个询问离线,按照值升序排列,最后的答案是一个单调不降序列
由于mod是一个和数,当一个值大于这个mod的最大因子的时候,那么它的阶乘mod 998857459 一定是0 这里我为了方便取了 3000为上限
我们可以把这个序列的0去掉然后组成一个没有0的序列,这样我们就可以 3000^2的复杂度去枚举出每种组合情况,然后把这个值对于的长度放到询问里最后一个小于子段和的答案里面(这里可以二分实现)。
最后对答案取一个后缀最小值即可。
O
(
300
0
2
∗
l
o
g
2
m
)
O(3000^2*log_{2}m)
O(30002∗log2m)
题目链接:
AC代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#define int long long
const int mod=998857459;
const int maxm=1e5+100;
int ans[maxm],num[maxm],Ans[maxm];
int cnt[maxm];
int js[maxm],tot;
struct node{
int id,val,ans;
}a[maxm];
int n,m;
inline int solve(int x)
{
int l=1,r=m;
int ans=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(a[mid].val<=x) ans=mid,l=mid+1;
else r=mid-1;
}
return ans;
}
inline bool comp(node x,node y)
{
return x.val<y.val;
}
signed main()
{
//freopen("1.txt","r",stdin);
//freopen("test.out","w",stdout);
scanf("%lld%lld",&n,&m);
js[0]=1;
for(int i=1;i<=3000;i++)
js[i]=(js[i-1]*i*1ll)%mod;
for(int i=1,x;i<=n;i++)
{
scanf("%lld",&x);
if(x<=3000) num[++tot]=js[x],cnt[tot]=i;
}
for(int i=1;i<=m;i++)
scanf("%lld",&a[i].val),a[i].id=i,a[i].ans=0x7fffffff;
std::sort(a+1,a+m+1,comp);
for(int i=1;i<=tot;i++)
{
int sum=0;
for(int j=i;j<=tot;j++)
{
sum=(sum+num[j])%mod;
int poi=solve(sum);
a[poi].ans=std::min(a[poi].ans,cnt[j]-cnt[i]+1);
}
}
Ans[a[m].id]=a[m].ans;
for(int i=m-1;i>=1;i--) a[i].ans=std::min(a[i+1].ans,a[i].ans),Ans[a[i].id]=a[i].ans;
for(int i=1;i<=m;i++) printf("%lld\n",Ans[i]==0x7fffffff?-1:Ans[i]);
return 0;
}