O(n ^ 2) 的类似最长上升子序列的做法
这个题其实乍一看非常像最长上升子序列这种题: 都是找一段最长的子序列 ,于是就有了如下90分的代码:
// web:
#include <bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 1e5 + 10;
const int INF = 1e18;
// int dp[32];
int a[N], dp[N];
inline void solve()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i], dp[i] = 1;
int res = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j < i; j++)
{
if (a[j] & a[i])
dp[i] = max(dp[i], dp[j] + 1);
res = max(res, dp[i]);
}
cout << res << endl;
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int t = 1;
// cin >> t;
while (t--) solve();
return 0;
}
优化(重点)
动态规划集合的设计
对于 & 操作,可以想到只要 其中的一位为1 ,那么就可以接上前面的同样该位为 1 的数字,那么dp数组可以设计成二维的 dp[i, j], 代表以第 i 个数字结尾,并且只考虑 第j位 是否满足条件的所有子序列的集合,其中数组存储的值就是子序列长度的 最大值;
动态规划转移的设计
那么我们该如何考虑转移呢?对于 第 i 个数字,若其第 j 为1,那么他一定可以接上前面的第 j 位同样为1的数字,所以 当第j位为1的时候 dp[i, j] = dp[i - 1, j] + 1;但是我们最终不只是仅仅考虑第 j 位,我们考虑的是第 i 个数是否能接上之前的,并且找到最大值,所以: 第i个数字的最大值就是所有位数里面的最大值,即k = max(dp[i, j]); 这个时候,我们还要再进行一步操作,把所有位上位1的数更新成k,为什么呢? 可以想一下在第i + 1个数的时候,如果第i + 1个数和第i个数的第j位为1的时候,那么他的值就是从第i个数的第j位转移过去的;如果第i + 1个数的第j位为1但是第i个数的第j位不是1的时候,那么这个位置就是从第i - 1(或之前的)转移过去的;
最后再用一下滚动数组,最终代码就是这样:
// web:https://www.luogu.com.cn/problem/P4310
#include <bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 1e5 + 10;
const int INF = 1e18;
inline void solve()
{
int n;
cin >> n;
vector<int> dp(32, 0);
int ans = -1;
for (int i = 1; i <= n; i++)
{
int b;
cin >> b;
int k = 1;
for (int j = 0; j <= 30; j++)
if ((1 << j) & b)
k = max(dp[j] + 1, k);
for (int j = 0; j <= 30; j++)
if ((1 << j) & b)
dp[j] = k;
ans = max(ans, k);
}
cout << ans;
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int t = 1;
// cin >> t;
while (t--) solve();
return 0;
}
1万+



被折叠的 条评论
为什么被折叠?



