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\n" , 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\n", 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,省空间
#define N 1010
#define A 10010
using namespace std;
int n , a[N] , mx , mn = INF , f[N] , ans;
int p[A];
int main()
{
cin >> n;
mn = INF;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
mx = max(mx , a[i]);
mn = min(mn , a[i]);
}
for (int k = 0 ; k <= mx - mn ; k++)
{
memset (p , 0 , sizeof(p));
for (int i = 1 ; i <= n ; i++)
{
f[i] = p[a[i] - k] + 1;
p[a[i]] = max(p[a[i]] , f[i]);
/*//直接扫一遍
for (int j = 1 ; j < i ; j++)
{
if(a[i] - a[j] == k)
{
f[k][i] = f[k][j] + 1;
}
}
*/
}
ans = max(ans , f[n]);
}
cout << ans;
return 0;
}
标签:head,树林,int,T1,Queue,tail,ans,飞跃,MOD 来源: https://www.cnblogs.com/ZhengkunJia/p/12228459.html