在题解里我会放两版代码,可以看一下我最后卡常卡过去做出的一些优化
先讲题目本身好了
两数之和 s 最小是2(2个1),最大是2000(2个1000),数不是特别大,我们可以枚举它
我们枚举每一个可能的 s,分别求出在 固定和等于 s 的条件下,能够选出的最长对称子序列的长度,最后取所有 s 中的最大值。
然后我们发现这题可以用区间dp来解,设 dp[l][r] 表示在原数组的下标区间 [l,r] 内,能够选出的、满足所有配对和等于 s 的最长子序列的长度。
对于区间 [l,r] 的状态转移共三种情况:
1.取两端的元素配对
如果 a[l] + a[r] = s,那么我们可以将这两个元素作为子序列的最外一对,再配上区间 [l+1,r−1] 内能选出的最优子序列(它本身也满足所有配对和等于 s)。
所以转移方程为 dp[l][r] = max(dp[l][r], dp[l+1][r-1])(前提区间 [l+1, r−1]不是空集,即至少有一个元素,r-1 >= l+1)
2.不取左端点
子序列完全由区间 [l+1, r] 构成:
所以转移方程为 dp[l][r] = max(dp[l][r], dp[l+1][r])
3.不取右端点
子序列完全由区间 [l, r-1] 构成:
所以转移方程为 dp[l][r] = max(dp[l][r], dp[l][r-1])
然后还有一个边界情况要考虑
当 l = r 的时候,区间里只有一个元素a[l],如果有2 * a[l] = s的话,它自己也是合法的对称子序列
所以初始化的时候,若2 * a[i] = s的话,那么dp[i][i]要初始化为1,其他情况时,dp数组初始化都是0
然后我们按区间长度从小到大计算,长度从1到n,每个区间从更短的区间处转移过来
我们最后的答案就是dp[1][n]
然后就写出来了现在这个初版代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define debug(x) cerr << #x << ": " << x << '\n';
// #define int long long
#define ctz __builtin_ctzll // 返回二进制表示中末尾连续0的个数
#define clz __builtin_clzll // 返回二进驻表示中先导0的个数
#define count1 __builtin_popcountll // 返回二进制表示中1的个数
// 上面仨不是ll的时候记得调整
typedef long long ll;
typedef unsigned long long ull;
typedef __int128 lll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int hash_base = 881;
const int N = 1e6 + 10000;
const double EPS = 1e-8;
const ll MOD = 676767677;
// const ll MOD = 1e9 + 7;
// const ll MOD = 998244353;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll dir[4][2] = {{1, 0}, {-1, 0}, {0, -1}, {0, 1}};
ll dirr[8][2] = {{1, 0}, {-1, 0}, {0, -1}, {0, 1}, {1, 1}, {1, -1}, {-1, -1}, {-1, 1}};
void LiangBaiKai()
{
}
void Aiden()
{
ll m, n, k, sum = 0, ans = 0, num = 0, mi = INF, ma = -INF, cnt = 0, x, y, z, len, t, l, r, cur;
string s1, s2;
cin >> n;
vector<ll> a(n + 1);
for (ll i = 1; i <= n;i++)
cin >> a[i];
ans = 1;
for (ll s = 2; s <= 2000;s++)
{
vector dp(n + 1, vector<ll>(n + 1));
for (ll i = 1; i <= n;i++)
{
if (2 * a[i] == s)
dp[i][i] = 1;
}
for (ll len = 2; len <= n;len++)
{
for (ll l = 1; l + len - 1 <= n;l++)
{
r = l + len - 1;
if (a[l] + a[r] == s)
{
ll in = (r - l >= 2) ? dp[l + 1][r - 1] : 0;
dp[l][r] = max(dp[l][r], in + 2);
}
dp[l][r] = max(dp[l][r], dp[l + 1][r]);
dp[l][r] = max(dp[l][r], dp[l][r - 1]);
}
}
ans = max(ans, dp[1][n]);
}
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
LiangBaiKai();
int _ = 1;
cin >> _;
while (_--)
Aiden();
return 0;
}
然后果不其然t了,这个时候我们极限运算次数是 2000 * 500 * 500 * 5 = 2.5e9 这显然是要t的,然后蒟蒻的主包想了很久怎么卡过去,下面是我做出亿些优化
1.统计了所有a[i]中出现的最大值max,这样我们的第一层循环就不用跑满 2000,从 2 到 2 * max 即可
2.跑一个 O(n^2) 的双重循环,用 vis 数组记录可能出现的两数之和 s 会有哪些值,跑循环的时候如果 vis[x] = 0 就直接 continue 掉,因为压根没有 2 个数的和会是 x
3.统计 ans 的时候,由于 ans 的最大值肯定也只有 n ,所以如果我们在循环的过程中发现 ans 已经为 n 了,就直接 break 掉,进行可行性剪枝
4.改用静态的 dp 数组,不进行 O(n ^ 2) 的 dp 数组初始化,只初始化对角线 dp[i][i] 的值,其余状态通过直接赋值覆盖掉,不取旧值了,拿一个 best 变量来计算最优情况以后直接给 dp[l][r] 赋值为 best (这个优化非常重要!!!是目前所有优化里最能显著优化时间的,因为把一次 O(n ^ 2) 优化为了 O(n) )
5.快读好像有可能也对这题有帮助,我没测试,但是好像看到有群友用完快读直接从 tle 变成 1000ms 左右了,有点神秘了,数据量明明很小啊
最后修改完以后的代码如下:
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define debug(x) cerr << #x << ": " << x << '\n';
// #define int long long
#define ctz __builtin_ctzll // 返回二进制表示中末尾连续0的个数
#define clz __builtin_clzll // 返回二进驻表示中先导0的个数
#define count1 __builtin_popcountll // 返回二进制表示中1的个数
// 上面仨不是ll的时候记得调整
typedef long long ll;
typedef unsigned long long ull;
typedef __int128 lll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int hash_base = 881;
const int N = 1e6 + 10000;
const double EPS = 1e-8;
const ll MOD = 676767677;
// const ll MOD = 1e9 + 7;
// const ll MOD = 998244353;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll dir[4][2] = {{1, 0}, {-1, 0}, {0, -1}, {0, 1}};
ll dirr[8][2] = {{1, 0}, {-1, 0}, {0, -1}, {0, 1}, {1, 1}, {1, -1}, {-1, -1}, {-1, 1}};
void LiangBaiKai()
{
}
bool vis[2005];
ll dp[505][505]; // 用静态数组
void Aiden()
{
ll m, n, k, sum = 0, ans = 0, num = 0, mi = INF, ma = -INF, cnt = 0, x, y, z, len, t, l, r, cur;
string s1, s2;
cin >> n;
vector<ll> a(n + 1);
for (ll i = 1; i <= n; i++)
{
cin >> a[i];
ma = max(a[i], ma); // 统计a[i]里的最大值
}
ans = 1; // ans 至少为 1
for (ll i = 1; i <= 2000;i++)
vis[i] = 0; // 重置vis数组
for (ll i = 1; i <= n; i++)
{
for (ll j = i + 1; j <= n; j++)
vis[a[i] + a[j]] = 1; // 可能的s值用vis数组去标记
}
for (ll s = 2; s <= ma * 2; s++) // 不用跑满2000,最多跑到2*ma即可
{
if (!vis[s]) // 没标记过的直接continue掉
continue;
for (ll i = 1; i <= n; i++)
dp[i][i] = (2 * a[i] == s) ? 1 : 0; // 只初始化对角线,其余状态通过直接赋值覆盖掉,这样就不会取旧值了
for (ll len = 2; len <= n; len++) // 枚举区间长度
{
for (ll l = 1; l + len - 1 <= n; l++) // 枚举区间左端点的起点
{
r = l + len - 1;
ll best = max(dp[l][r - 1], dp[l + 1][r]); // 当我们在判断dp[l][r]的时候,dp[l+1][r],dp[l][r-1],dp[l+1][r-1]都一定已经被考虑过了,所以直接取值即可,用best统计
if (a[l] + a[r] == s)
{
ll in = (r - l >= 2) ? dp[l + 1][r - 1] : 0; // 空区间视作是0
best = max(best, in + 2);
}
dp[l][r] = best; // 直接把best赋值给dp[l][r]
}
}
ans = max(ans, dp[1][n]);
if (ans == n) // ans如果已经到上限n了可以直接break了就
break;
}
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
LiangBaiKai();
int _ = 1;
cin >> _;
while (_--)
Aiden();
return 0;
}
/*
@@@@@@
@@@@@@@@@@
@@@@@@@@@@@@@
@@@@@@@@@@@@@@@
@@@@@@@@@@@
@@@@
@
@@@@@@@@@@ @@ @@@@@@@@@@@@
@@@@@ @ @@@@@
@@@ @ @@@
@@@ @@ @@@
@@ @@@
@@ @@
@@ @@
@@ @@
@ @@
@ @@@@@@@@ @@
@ @@@ @@@@@@@@@ @
@@ @@@@@@@ @@@@@@@@ @@@@@@ @
@ @@@@@@@@@@@@@@@@@@@@@@@@@@ @@
@ @@@@@@@@@@@ @@@@@@@@@@@ @
@@@@@@@@@@@@@ @@@@@@@@@@@@@@ @ @@@@@@ @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ @@@@@@ @@@ @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@ @@@ @@@ @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@
@@@@@@ @@@@@@@@@@ @@@@@@@@@@@@@@ @@
@@@@@ @@@@@@@ @@@@@@ @@ @@@@@@@@@@@ @@@
@@@@@ @@@@@@@@ @@@@ @@@@@@@ @@@@@@@@@@@ @@
@@@@@@ @@@@@@@ @@@@@ @@@@@@@ @@@@@@@@@@@@ @@@@@@@
@@@@@@@ @ @@@@@@ @@@@@@@ @@@@@@@@@@@@@@@@ @@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@ @ @@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@ @@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ @ @ @@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ @@@@@ @ @@@@ @ @@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ @@ @@ @@@ @@@@@ @@@@@@@@@@
@@@@@@@ @@@@@@@@@@@@@@ @@@@@@ @@@@@ @ @@@@@@@@
@@@@@@ @@@@@@@@@@@ @ @@@@@@@@
@@@@ @@@@@@@@ @@ @@@@@@@
@@ @@ @@@@@
@@@ @@
@@@ @@@
@@@@@@@@@@@@ @@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
*/

京公网安备 11010502036488号