Gym100204C Fibonacci Subsequence(dp + hash + 卡常 + 序列自动机)

Description

给定一个长度为 n n n 的序列 a a a,求它的最长斐波那契子序列。空间限制 64MB。

1 ≤ n ≤ 3000 , 1 ≤ ∣ a i ∣ ≤ 1 0 9 1 \leq n \leq 3000, 1 \leq |a_i| \leq 10^9 1n3000,1ai109

Solution

可以发现确定了子序列的前两项,那么后面的可以推出来。所以令 f i , j ( i < j ) f_{i,j} \quad (i<j) fi,j(i<j) 为前两项为 a i a_i ai a j a_j aj 的最长斐波那契子序列的长度 − 2 -2 2。转移时找一个满足 a k = a i + a j ∧ k > j a_k = a_i + a_j \land k > j ak=ai+ajk>j 的最小的下标 k k k,那么 f i , j = f j , k + 1 f_{i,j} = f_{j,k} + 1 fi,j=fj,k+1。 那么找到 f i , j f_{i,j} fi,j 最大的 ( a i , a j ) (a_i,a_j) (ai,aj) 将最长斐波那契子序列推出来即可。

考虑如何求 k k k我会暴力! 可以搞个序列自动机, n x t i , j nxt_{i,j} nxti,j 为下标 i i i 之后第一个 j j j 的下标,不包括 i i i。可是 1 ≤ ∣ a i ∣ ≤ 1 0 9 1 \leq |a_i| \leq 10^9 1ai109我会离散化! 喂喂,离散化是不能保证两数之和等于另一个数的。我会 map! ,看我的绝招 unordered_map。

于是写了一发 MLE on test 16。原来是序列自动机超空间了,没关系倒着 dp 不需要 i i i 这一维,但是要把 unordered_map 清空。再来——MLE on test 16。没关系,我把 int 换成 short。一定能过了——TLE on test 16。读写优化,卡常!还是 TLE on test 16?唉,好像可以变换 i i i j j j 的枚举顺序,这样每次不用清空 unordered_map 了。好的奈斯,过第十六个 … TLE on 27 开玩笑吧?看来老虎不发威你当我是病猫啊 手写 HashTable 散列表,终于过啦。

Solution

#include <bits/stdc++.h>
using namespace std;
const int N = 3000 + 1;
inline int read() {
	int x = 0, f = 0; char ch = 0;
	while (!isdigit(ch)) f |= ch == '-', ch = getchar();
	while (isdigit(ch)) x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
	return f ? -x : x;
}
inline void write(int x) {
	if (x < 0) putchar('-'), x = -x;
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
int n, a[N]; 
short f[N][N]; 
int mxl = -1, ansa, ansb;
struct HashTable {
    static const int p = 999997;
    int head[p], sz;
    struct Node {
    	int nxt, val, key;
    }data[N];
    HashTable() {
        memset(head, -1, sizeof(head));
        sz = 0;
    }
    int get(int key) {
        return (key % p + p) % p;
    }
    void insert(int key, int val) {
    	data[++sz] = (Node){head[get(key)], val, key};
        head[get(key)] = sz;
    }
    int find(int key) {
        for (int i = head[get(key)]; i != -1; i = data[i].nxt) 
            if (data[i].key == key) return data[i].val;
        return -1;
    }
} pos;
int main() {
	freopen("fibsubseq.in", "r", stdin);
    freopen("fibsubseq.out", "w", stdout);
	n = read();
	for (register short i = 1; i <= n; ++i) a[i] = read();
	if (n == 1) {
		printf("1\n%d\n", a[1]); return 0;
	}
	for (register short i = n; i >= 1; --i) {
		for (register short j = i - 1; j >= 1; --j) {
			short k = pos.find(a[i] + a[j]);
			if (k != -1) f[j][i] = f[i][k] + 1;
			if (f[j][i] > mxl) {
				mxl = f[j][i];
				ansa = a[j], ansb = a[i];
			}
		} 
		pos.insert(a[i], i);
	}
	write(mxl + 2); puts("");
	write(ansa); putchar(' '); write(ansb); putchar(' ');
	while(mxl--) {
		write(ansa + ansb); putchar(' ');
		int t = ansa;
		ansa = ansb, ansb += t;
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值