戳我传送
图片说明


思路:


传送门图片说明看这位大佬的题解看懂了,太秀了。
子序列首先我们会想到动态规划,状态dp[i]表示以a[i]结尾符合条件的子序列个数。
状态转移方程不难写出是dp[i]=1+图片说明 。建议仔细看清楚a的上下标,a[i]的上标是j,a[i]的下标是i,如果像我一样没看清楚的话,真不知道题目在说什么。接着是判断图片说明 是不是成立,比较常用的是做差法和做商法,当遇到最差的情况100^100显然做差法是不现实的,会直接溢出,取余也不行,可以考虑做商法,K=图片说明 ,(假设图片说明 ),如果如果y>=x,图片说明 一定不成立,接着就是考虑y<x的时候,y/x<1可以先求出y/x的幂然后乘y到K>1结束,或者直到乘完了y,复杂度图片说明 (n)。由数论知道在等式图片说明 两边取对数不等号不改变(只要两边的数不为0或负数),于是转为直接判断i * log(aj) < j* log (ai)。复杂度图片说明 (log n)。仔细观察发现等式可以化为log(aj)/j < log (ai)/i,等式一边就化为了只有i或j的式子了,于是在状态转移的时候只要比较log (ai)/i的值就可以了。但是上述过程都是建立在两层循环的基础上的,复杂度图片说明 (n^2)。其实可以离散化加树状数组就可以完成了,复杂度图片说明 (n log n)。


原理:


1.树状数组处理的一般都是整数区间,所以需要用到离散化将log (ai)/i的值映射到一个整数上c[i],同时不改变他们的大小关系。
2.从前往后计算当前状态时,可以看成一个维护区间和还有求和的过程,计算dp[i]其实是求区间[1,c[i]-1]的和,也就是求前面的子序列最后一个数的log (aj)/j值比i的要小的个数,之和把dp[i]的值加进去维护区间和。
3.状态转移方程加1是因为可以自己成一个序列。
4.离散化就是一个映射的过程。
5.树状数组是一种利用数的二进制特征进行检索的树状结构。可以快速的修改区间元素值和区间求和。

Code:

#include <bits/stdc++.h>
#define lowbit(x)   (x&-x)
#define mod 1000000007
using namespace std;
template <class T>
inline void read(T &res) {
    char c; T flag = 1;
    while ((c = getchar()) < '0' || c > '9')
        if (c == '-')
            flag = -1;
    res = c - '0';
    while ((c = getchar()) >= '0' && c <= '9')
        res = res * 10 + c - '0';
    res *= flag;
}
double b[105],a[105];
long long dp[105],tree[105],ans;
int n,c[105];
void add(int x,int y) {
    while(x<=100) {
        tree[x]=(tree[x]+y)%mod;
        x+=lowbit(x);
    }
}
long long sum(int x) {
    long long ans=0;
    while(x) {
        ans=(ans+tree[x])%mod;
        x-=lowbit(x);
    }
    return ans;
}
int main() {
    read(n);
    for(int i=1;i<=n;++i) {
        read(a[i]);
        a[i]=log(a[i])/i;
        b[i]=a[i];
    }
    sort(b+1,b+1+n);
    int cnt=unique(b+1,b+1+n)-b-1;
    for(int i=1;i<=n;++i)    c[i]=lower_bound(b+1,b+1+cnt,a[i])-b;
    ans=1; add(c[1],1);
    for(int i=2;i<=n;++i) {
        dp[i]=(1+sum(c[i]-1))%mod;
        add(c[i],dp[i]);
        ans=(ans+dp[i])%mod;
    }
    printf("%lld\n",ans);
}