杭电5288如何查找一个数字的最左边因子和最右边因子的下标,先处理100以下的数字,100以上的数字则是sqrt(n)

对于区间问题,一般统计满足区间的区间对结果的贡献值为多少,这个题就可以统计每个数字的因子的左右最近的值得下标

l[i],r[i],则这个数字对答案的贡献值为(i-l[i])*(r[i]-i),这个题o(n*n)的算法显然不行,于是看到输入数据为10000从这里下手,

先处理1~100,按照先输入的必在后输入的前面的关系,正向扫描统计l[i],同时反向扫描统计r[i],然后按照ai的倍数关系,

由于现在ai>100且第二层循环是按照ai的倍数递增的,所以总的时间为O(n^sqrt(n)),由于提前储存了每个数出现的下标,

而且先存入的坐标肯定在后存入的前面,所以就按照倍数关系正向扫描,起初的下标必定小于i,所以更新他们的左边因子,

同理反向更新右边因子。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cctype>
#include<cstdlib>
#include<cctype>
#include<string>
#include<set>
#include<map>
#include<vector>
#include<algorithm>
using namespace std;
#define LL long long
const LL mod=1e9+7;
const int N=1e5+10;
int a[N],p[N],l[N],r[N];
vector<int> vec[N];
int main()
{
   LL n;
   while(cin>>n)
   {
       for(int i=101;i<=10000;i++)
        vec[i].clear();
       for(int i=1;i<=n;i++)
       {
            scanf("%d",&a[i]);
            l[i]=0;
            r[i]=n+1;
            if(a[i]>100)
                vec[a[i]].push_back(i);
       }
       for(int j=1;j<=100;j++)
       {
           int tem=0;
           for(int i=1;i<=n;i++)
           {
               if(a[i]%j==0) l[i]=max(l[i],tem);
               if(a[i]==j)
                tem=i;
           }
           tem=n+1;
           for(int i=n;i>0;i--)
           {
               if(a[i]%j==0) r[i]=min(r[i],tem);
               if(a[i]==j)
                tem=i;
           }
       }
       memset(p,0,sizeof(p));
       for(int i=1;i<=n;i++)
       {
          if(a[i]>100)
          {
              for(int j=a[i];j<=10000;j+=a[i])
              {
                  while(p[j]<vec[j].size()&&vec[j][p[j]]<i)
                  {
                      r[vec[j][p[j]]]=min(r[vec[j][p[j]]],i);
                      if(p[j]<vec[j].size()-1&&(vec[j][p[j]+1]<i))
                        p[j]++;
                      else
                        break;
                  }
              }
          }
       }
       for(int i=10000;i>=101;i--)
        p[i]=vec[i].size()-1;
       for(int i=n;i>0;i--)
       {
           if(a[i]>100)
           {
               for(int j=a[i];j<=10000;j+=a[i])
               {
                   while(p[j]>=0&&vec[j][p[j]]>i)
                   {
                       l[vec[j][p[j]]]=max(l[vec[j][p[j]]],i);
                       if(p[j]>0&&vec[j][p[j]-1]>i)
                        p[j]--;
                       else
                        break;
                   }
               }
           }
       }
       LL sum=0;
       for(int i=1;i<=n;i++)
        sum=(sum+((i-l[i])*(r[i]-i))%mod)%mod;
        cout<<sum<<endl;
   }
   return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值