2022-04-15每日刷题打卡
代码源——每日一题
最长公共子序列 - 题目 - Daimayuan Online Judge
给出从 1 到 n 的两个排列 P1 和 P2,求它们的最长公共子序列。
输入格式
第一行是一个正整数 n。
接下来两行,每行为 n 个数,为自然数 1,2,…,n 的一个排列。
输出格式
一个数,即最长公共子序列的长度。
数据范围
1≤n≤10^5
输入样例
5
3 2 1 4 5
2 1 3 4 5
输出样例
4
第一眼看这题以为是个模板题,但一看这数据范围就知道这题不简单。
大火可能都写过二维dp求最长公共子序列,它的时间复杂度是n^2。但在这里数据被提到了1e5,先不说超不超时,我们也开不了1e5*1e5的二维数组。那么这题该怎么做?有没有什么方法可以不用二维数组,还能把复杂度降到nlogn(或者更低?)。
首先,有两点,一是这里给的两个数组,都是自然数1、2、3……n的排列,只是两个数组中的位置不一样罢了。二是我们要考虑一下公共子序列的意思,公共序列换个说法就是,一个序列在两个数组中的相对位置都是一样的。比如样例这里,2都是在1后面,4在2后面,5在2后面。所以它们的最长公共序列就是2145,长度为4。然后更直观点来说,如果我们把一个数组的元素的按照另一个数组的出现位置做一个数组,那么最长公共子序列就是这个数组的最长上升子序列。
样例举例来说,第二个数组b:21345,元素2在第一个数组a里的下标为1,1的下标是2,……5的下标是4,这样我们就可以得到一个数组:1 2 0 3 4,然后第一个数组下标为1 2 3 4的元素就是第二个数组的最长公共子序列,因为它们的出现顺序是一样的(在a中是上升序列,b中也是)
那么现在问题就从求最长公共子序列变成了就最长上升子序列了。很幸运的是我们恰好知道最长上升子序列的nlogn的写法。
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include<numeric>
#include<string>
#include<string.h>
#include<iterator>
#include<map>
#include<unordered_map>
#include<stack>
#include<list>
#include<queue>
#include<iomanip>
#define endl '\n';
typedef long long ll;
typedef pair<ll, ll>PII;
const int N = 100100;
int a[N], b[N], dp[N];
unordered_map<int, int>mymap;
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int n;
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> a[i];
mymap[a[i]] = i;
}
for (int i = 0; i < n; i++)
{
int x;
cin >> x;
b[i] = mymap[x];
}
int len = 1;
dp[0] = -1e7;
for (int i = 0; i < n; i++)
{
int l = 0, r = len;
while (l < r)
{
int mid = l + r + 1 >> 1;
if (dp[mid] < b[i])l = mid;
else r = mid - 1;
}
len = max(len, r + 1);
dp[r + 1] = b[i];
}
cout << len-1 << endl;
return 0;
}
CodeForces——线段树专题
C - Segment Tree, part 1 - Codeforces
Given an array of 2n numbers, each number from 1 to n in it occurs exactly twice. We say that the segment y is nested inside the segment x if both occurrences of the number y are between the occurrences of the number x. Find for each segment ii how many segments that are nested inside it.
Input
The first line contains the number n (1≤n≤10^5), the second line contains 2n numbers. It is guaranteed that every number from 1 to n occurs exactly twice.
Output
Print n numbers, the i-th number is equal to the number of segments nested inside the segment i.
Example
input
5
5 1 2 2 3 1 3 4 5 4
output
1 0 0 0 3
这题意思是说,有2n个数,数组是由1~n组成的,每个数有两个,相同的两个数被称作一段,问你一个段里包含了多少段,比如5和5之间包括了1、2、3段,4不算,因为4的右端在外面。
一开始先准备一个叶子全为0的线段树。我们从左往右走,先记录下每个段的左端的下标,当走到右端的时候,记录左端到右端这一段区间的区间和,区间和为多少,说明里面有多少段。计算完区间和后,我们把左段点的位置从0变成1。
首先,我们只有走完了一个段的左端和右端后,我们才会把这个段的左端点的值从0变成1。如果我们一个段的区间和不为0,那么区间和为几,就说明里面已经包含了多少完整的段。如果只包含了左端点,那么还没走到右端点,左端点的值依然是0,如果只包含右端点,但我们只把左端点的值变成了1,所以右端点也不会被记上。
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include<numeric>
#include<string>
#include<string.h>
#include<iterator>
#include<map>
#include<unordered_map>
#include<stack>
#include<list>
#include<queue>
#include<iomanip>
#define endl '\n';
typedef long long ll;
typedef pair<int, int> PII;
const int N = 300050;
int f[4 * N], a[N], n;
void revise(int k, int l, int r, int x)
{
if (l == r)
{
f[k] = 1;
return;
}
int m = (l + r) / 2;
if (x <= m)revise(k + k, l, m, x);
else
revise(k + k + 1, m + 1, r, x);
f[k] = f[k + k] + f[k + k + 1];
}
int calc(int k, int l, int r, int x, int y)
{
if (l == x && y == r)
{
return f[k];
}
int m = (l + r) / 2;
if (y <= m)return calc(k + k, l, m, x, y);
else
if (x > m)return calc(k + k + 1, m + 1, r, x, y);
else return calc(k + k , l, m, x, m) + calc(k + k+1, m + 1, r, m + 1, y);
}
int main()
{
unordered_map<int, int>mymap;
map<int, int>cnt;
cin >> n;
for (int i = 1; i <= 2*n; i++)
{
cin >> a[i];
if (mymap[a[i]] == 0)mymap[a[i]] = i;
}
for (int i = 1; i <= 2*n; i++)
{
if (cnt[a[i]] == 0)
cnt[a[i]] = 1;
else
{
cnt[a[i]] = calc(1, 1, 2*n, mymap[a[i]], i);
revise(1, 1, 2*n, mymap[a[i]]);
}
}
for (auto i : cnt)cout << i.second << " ";
return 0;
}