P4093 [HEOI2016/TJOI2016] 序列 题解
题目链接:序列
对于 LIS 问题,很显而易见的有 dp方程为:
\[dp_i=\max{dp_j}+1 \ (j<i,a_j \le a_i) \text{ dp表示以某个位置结尾的最长 LIS}
\]
本题考虑到对于转移的两位置,如果能从 \(j \rightarrow i\),那么在以上条件成立的基础情况下,我们由于可以更改二者中的任意一个值(因为同一时刻只能更改一个值),所以如果更改前者那么要保证更改后满足 \(max_j \le a_i\),依旧满足上述的对应关系。如果更改的是 \(i\) 位置的数,保证更改后也满足 \(a_j \le min_i\) 。(因为题目说的是任意一种变化,我们只需要考虑极端的变化即可)。那么转移方程 (\(dp_j \rightarrow dp_i\)) 的条件就为:
-
\(j < i\)
-
\(max_j \le a_i\)
-
\(a_j \le min_i\)
很显而易见,偏序问题,用 cdq 分治来做。注意到 dp 是从左往右,自带有序,所以我们 cdq 分治也应该从原有的两边合并转化为优先处理完左侧的数据再更新右侧数据,类似于二叉树先遍历完左子树。
细节
稍微注意一下,在排序时,我们不应该直接对两侧状态进行排序,因为我们是先遍历左半边的,再遍历右半边的,但遍历左半边对右半边的影响时,排序时会排序右半边导致右半边的 dp 状态量不再是从左往右了,导致遍历右半边时更新错误,所以应该开一个存取相对位置的下标数组进行排序而不影响原数组。
另外注意到我们的偏序条件,左边是 \(max_j\) 右边是 \(val_i\),所以左右两边排序条件的关键字是不一样的。查询我们可以考虑使用树状数组查询 \(val_j \le min_i\) 的 \(\max{dp_j}\) 用于更新当前的 \(dp_i\)。(值域 \(min_i,val_i\le n\))
参照代码
#include <bits/stdc++.h>
//#pragma GCC optimize("Ofast,unroll-loops")
#define isPbdsFile
#ifdef isPbdsFile
#include <bits/extc++.h>
#else
#include <ext/pb_ds/priority_queue.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/trie_policy.hpp>
#include <ext/pb_ds/tag_and_trait.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/list_update_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/exception.hpp>
#include <ext/rope>
#endif
using namespace std;
using namespace __gnu_cxx;
using namespace __gnu_pbds;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef tuple<int, int, int> tii;
typedef tuple<ll, ll, ll> tll;
typedef unsigned int ui;
typedef unsigned long long ull;
typedef __int128 i128;
#define hash1 unordered_map
#define hash2 gp_hash_table
#define hash3 cc_hash_table
#define stdHeap std::priority_queue
#define pbdsHeap __gnu_pbds::priority_queue
#define sortArr(a, n) sort(a+1,a+n+1)
#define all(v) v.begin(),v.end()
#define yes cout<<"YES"
#define no cout<<"NO"
#define Spider ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
#define MyFile freopen("..\\input.txt", "r", stdin),freopen("..\\output.txt", "w", stdout);
#define forn(i, a, b) for(int i = a; i <= b; i++)
#define forv(i, a, b) for(int i=a;i>=b;i--)
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
#define endl '\n'
//用于Miller-Rabin
[[maybe_unused]] static int Prime_Number[13] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};
template <typename T>
int disc(T* a, int n)
{
return unique(a + 1, a + n + 1) - (a + 1);
}
template <typename T>
T lowBit(T x)
{
return x & -x;
}
template <typename T>
T Rand(T l, T r)
{
static mt19937 Rand(time(nullptr));
uniform_int_distribution<T> dis(l, r);
return dis(Rand);
}
template <typename T1, typename T2>
T1 modt(T1 a, T2 b)
{
return (a % b + b) % b;
}
template <typename T1, typename T2, typename T3>
T1 qPow(T1 a, T2 b, T3 c)
{
a %= c;
T1 ans = 1;
for (; b; b >>= 1, (a *= a) %= c)if (b & 1)(ans *= a) %= c;
return modt(ans, c);
}
template <typename T>
void read(T& x)
{
x = 0;
T sign = 1;
char ch = getchar();
while (!isdigit(ch))
{
if (ch == '-')sign = -1;
ch = getchar();
}
while (isdigit(ch))
{
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
x *= sign;
}
template <typename T, typename... U>
void read(T& x, U&... y)
{
read(x);
read(y...);
}
template <typename T>
void write(T x)
{
if (typeid(x) == typeid(char))return;
if (x < 0)x = -x, putchar('-');
if (x > 9)write(x / 10);
putchar(x % 10 ^ 48);
}
template <typename C, typename T, typename... U>
void write(C c, T x, U... y)
{
write(x), putchar(c);
write(c, y...);
}
template <typename T11, typename T22, typename T33>
struct T3
{
T11 one;
T22 tow;
T33 three;
bool operator<(const T3 other) const
{
if (one == other.one)
{
if (tow == other.tow)return three < other.three;
return tow < other.tow;
}
return one < other.one;
}
T3() { one = tow = three = 0; }
T3(T11 one, T22 tow, T33 three) : one(one), tow(tow), three(three)
{
}
};
template <typename T1, typename T2>
void uMax(T1& x, T2 y)
{
if (x < y)x = y;
}
template <typename T1, typename T2>
void uMin(T1& x, T2 y)
{
if (x > y)x = y;
}
constexpr int N = 1e5 + 10;
struct DP
{
int val, max, min, dp;
bool operator<(const DP& other) const
{
return dp < other.dp;
}
} node[N];
int n, m;
int bit[N];
inline void add(int x, const int val)
{
while (x <= n)uMax(bit[x], val), x += lowBit(x);
}
inline void clear(int x)
{
while (x <= n)bit[x] = 0, x += lowBit(x);
}
inline int query(int x)
{
int ans = 0;
while (x)uMax(ans, bit[x]), x -= lowBit(x);
return ans;
}
int pos[N]; //拷贝一份地址数组
inline void cdq(const int L, const int R)
{
const int mid = L + R >> 1;
if (L == R)return;
cdq(L, mid);
forn(i, L, R)pos[i] = i;
stable_sort(pos + L, pos + mid + 1, [&](const int i, const int j)
{
return node[i].max < node[j].max;
});
stable_sort(pos + mid + 1, pos + R + 1, [&](const int i, const int j)
{
return node[i].val < node[j].val;
});
int l = L;
forn(r, mid+1, R)
{
while (l <= mid and node[pos[l]].max <= node[pos[r]].val)add(node[pos[l]].val, node[pos[l]].dp), l++;
uMax(node[pos[r]].dp, query(node[pos[r]].min) + 1);
}
forn(i, L, mid)clear(node[i].val);
cdq(mid + 1, R);
}
inline void solve()
{
cin >> n >> m;
forn(i, 1, n)
{
cin >> node[i].val;
node[i].max = node[i].min = node[i].val;
node[i].dp = 1;
}
forn(i, 1, m)
{
int pos, val;
cin >> pos >> val;
uMax(node[pos].max, val);
uMin(node[pos].min, val);
}
cdq(1, n);
cout << max_element(node + 1, node + n + 1)->dp;
}
signed int main()
{
Spider
//------------------------------------------------------
int test = 1;
// read(test);
// cin >> test;
forn(i, 1, test)solve();
// while (cin >> n, n)solve();
// while (cin >> test)solve();
}
\[时间复杂度为 O(n\log^2{n})
\]