T1飞跃树林 && 【最长等差子序列】

solution by Mr.gtf

一道简单的递推
首先我们对树高从大到小排序
很容易得到递推式
ans[i]=Σans[j] (j<i && h[j]-h[i]<=K)
朴素做法暴力枚举,暴力for j from 1 to i-1
聪明一点能发现满足条件的j一定是i之前连续的一段,只要从i-1开始向前for到不满足为止
这样做最坏情况依然是O(nK)或者O(n^2)
事实上,只要再深入思考,就能发现,对于每个i,满足条件的最小的j是单调不降的。

所以我们只需要准备一个队列,在求i答案时将队列首不满足条件的j踢出,在i求出答案后入队,需要维护一个队列内的下标的ans和。
复杂度O(n)

My Code

#define N 1000010
#define MOD 1000000007

using namespace std;

int n , a[N] , f[N] , k;

int z[N] , head , tail , done;

int main()
{
//  freopen("tree.in" , "r" , stdin);
//  freopen("tree.out" , "w" , stdout);
    scanf("%d%d",&n,&k);
    for (int i = 1 ; i <= n ; i++)
        cin >> a[i];
    sort(a + 1 , a + 1 + n);
    a[0] = 0;
    f[0] = 1;
    done = 1;
    for (int i = 1 ; i <= n ; i++)
    {
        while(a[z[head]] + k < a[i] && z[head] < i)
        {
            head++;
        }
        f[i] = done;
        z[++tail] = i;
        done += f[i];
        done %= MOD;
        /*for (int j = i - 1 ; j >= 0 ;j--)   //不优化40分
        {
            if(a[i] - a[j] <= k)
            {
                f[i] += f[j];
                f[i] %= MOD;
            }
            else
                break;
        }*/
    }
    printf("%d
" , f[n]);
    return 0;
}

Teacher's Code

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int MaxN = 1e6 + 50;
const int MOD = 1e9 + 7;
int n, K, h[MaxN];
int ans[MaxN];

struct Queue
{
    const static int MAX_SIZE = 1e6;
    int data[MAX_SIZE];
    int head, tail;

    Queue() : head(0), tail(0) {}

    inline static int inc(int p)
    {
        ++p;
        return (p == MAX_SIZE) ? 0 : p;
    }

    void push(int x);

    void pop();

    int front();

    inline bool empty();
};

Queue q;

int main()
{
    freopen("tree.in", "r", stdin);
    freopen("tree.out", "w", stdout);

    scanf("%d%d", &n, &K);
    for (int i = 0; i < n; ++i) scanf("%d", &h[i]);
    sort(h, h + n, greater<int>());
    h[n] = 0;
    ans[0] = 1;
    q.push(0);
    int qSum = ans[0];

    for (int i = 1; i <= n; ++i) {
        while (!q.empty()) {
            int j = q.front();
            if (h[j] - h[i] > K) {
                qSum = ((qSum - ans[j]) % MOD + MOD) % MOD;
                q.pop();
            } else break;
        }
        ans[i] = qSum;
        q.push(i);
        qSum = (qSum + ans[i]) % MOD;
    }

    printf("%d
", ans[n]);
    return 0;
}
//队列操作
void Queue::push(int x)
{
    if (inc(tail) == head) throw x;
    data[tail] = x;
    tail = inc(tail);
}

void Queue::pop()
{
    if (empty()) throw head;
    head = inc(head);
}

int Queue::front()
{
    if (empty()) throw head;
    return data[head];
}

inline bool Queue::empty()
{
    return head == tail;
}

【例】最长等差子序列

题目描述

给你一个以正整数构成的序列,求它的最长等差子序列的长度
难度1 n<=1000,a[i]<=1000
难度2 n<=100,a[i]<=10^9

解析

首先离散化!

注意到难度二n只有100,而a很大

大致就是把一个数组如
a 111 44 76538 0 342566677

转成
a 3 2 4 1 5
再调用

然后发现这题与上题的区别就在于 公差不定 和 必须等差

公差不定其实就枚举公差,再重复上个题的操作

必须等差就把上个题的判断条件改掉(h[j] - h[i] > K 改成 h[j] - h[i] == k)

复杂度

公差个数nn
难度一:1000 * 1000 * 1000
难度二:10^9 ^ 100 ^ 100
第一层循环公差,第二层循环序列右端点,第三层循环上一个元素的下表
优化:把第三层循环用数组存起来
f[i]:到第i个元素的最长等差序列
k:公差
(f[k][i] = max{f[k][j] | a[j] + k == a[i]})
定义p[k][a[j]] = max{f[k][j] | a[j] + k = a[i]}
就能把O(n)的循环变成 O(1)的查询
写的时候可以滚掉一维公差k,省空间

注意负公差

原文地址:https://www.cnblogs.com/ZhengkunJia/p/12228459.html