hdu 5288 OO’s Sequence

OO has got a array A of size n ,defined a function f(l,r) represent the number of i (l<=i<=r) , that there's no j(l<=j<=r,j<>i) satisfy ai mod aj=0,now OO want to know

∑(i=1->n)∑(j=i->n)f(i,j) mod 109+7.

Input
There are multiple test cases. Please process till EOF.
In each test case: 
First line: an integer n(n<=10^5) indicating the size of array
Second line:contain n numbers a i(0<a i<=10000)
 
Output
For each tests: ouput a line contain a number ans.
 
Sample Input
5
1 2 3 4 5
 
Sample Output
23

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5288

题意:给一个大小为n的数组a[i]以及n各数,求所有f(le,ri)的和([le,ri]是[1,n]的所有子区间),f(le,ri)是指区间[le,ri]里每一个满足一定条件的a[i]的个数,

条件是区间[le,ri]里没有一个不等于i的j使得a[i]%a[j]=0;(如果le==ri,区间只有一个a[i],这个是满足条件的,因为确实是没有一个不等于i的j,那就更不用谈什么模了。)

思路:数据大,暴力无解,从a[i]%a[j]=0入手,这样的a[j]一定是a[i]的因子,也就是说一个区间一旦有了a[i]的因子a[i]对答案就没有贡献了,那么对于每一个a[i]
向两边扩张区间,遇到离a[i]最近的一个因子,存下位置le[i],ri[i],那么a[i]有用的最大区间就是[le[i],ri[i]],然后这个怎么算a[i]对答案的贡献,只要在这个区间包含到a[i]

的所有子区间都满足条件并且贡献答案1,那么就算包含a[i]的区间个数,既然包含a[i],那么子区间的起点le可以从le[i]取到i,终点从i取到ri[i],总共有(i-le[i])*(ri[i]-i)个;

这样还有个问题,如从i开始直接往两边去一个一个找端点还是会超时的,需要优化,a[i]只有10000,不大因子个数也不多,那么用筛法的思想对每个数把其位置添加到这个数的

倍数后面,以表示它的倍数有这个因子;

#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<set>
#include<map>
#include<queue>
#include<vector>
#include<iterator>
#include<utility>
#include<sstream>
#include<iostream>
#include<cmath>
#include<stack>
using namespace std;
const int INF=1000000007;
const double eps=0.00000001;
const int maxn=1e5+5;
const int mod=1e9+7;
typedef __int64 ll;
typedef struct
{
    int val,pos;
}aa;
aa a[maxn];
int l[maxn],r[maxn];
vector<int> v[10005];//保存每个数i的因子的在a[]数组里的下标
bool cmp(aa x,aa y)
{
    if(x.val==y.val)//注意如果有相同的数,优先前面的,为了找到最近的因子
        return x.pos<y.pos;
    return x.val<y.val;
}
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        ll ans=0;
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i].val),a[i].pos=i;
        for(int i=1;i<=10005;i++) v[i].clear();
        for(int i=1;i<=n;i++)
            l[i]=0,r[i]=n+1;
        //用结构体存了下标对应的数,排序是为找因子
        //每个数的因子一定小于等于它,因子先出现放在前面;
        //当考虑到一个数的时候,它的因子已经全部找出来了
        sort(a,a+n,cmp);
        for(int i=1;i<=n;i++)
        {
            int tmp=a[i].val;
            for(int j=tmp;j<=10000;j+=tmp)//添加因子,数不多
                v[j].push_back(a[i].pos);
        }
        for(int i=1;i<=n;i++)
        {
            int tmp=a[i].val;
            for(int j=v[tmp].size()-1;j>=0;j--)//暴搜所有因子的位置,找最近的
            {
                if(v[tmp][j]<a[i].pos) l[a[i].pos]=max(l[a[i].pos],v[tmp][j]);
                if(v[tmp][j]>a[i].pos) r[a[i].pos]=min(r[a[i].pos],v[tmp][j]);
            }
        }
        for(int i=1;i<=n;i++)
        {
            int t=a[i].pos;
            ans=(ans+(ll)(a[i].pos-l[a[i].pos])*(r[a[i].pos]-a[i].pos))%mod;
        }
        printf("%I64d\n",ans);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值