CF1720 — Codeforces Round #815 (Div. 2)
D. Xor-Subsequence
题意
给定正整数数组 a a a , 构造最长的单调递增数列 b b b 满足 a b p ⊕ b p + 1 < a b p ⊕ b p a_{b_p} \oplus b_{p+1} < a_{b_p} \oplus b_p abp⊕bp+1<abp⊕bp .
(下标从 0 0 0 开始)
复杂度要求 Θ ( n log max ( a ) ) \Theta(n\log \max(a)) Θ(nlogmax(a))
思路
为了统计答案 , 我们使用 v a l i val_i vali 表示以下标为 i i i 结尾的 b b b 的最长构造长度 .
首先原不等式可以化为 ( a b p ⊕ b p ) ⊕ ( b p + 1 ⊕ b p ) < ( a b p + 1 ⊕ b p + 1 ) ⊕ ( b p ⊕ b p + 1 ) (a_{b_p}\oplus b_p) \oplus (b_{p+1}\oplus b_p) <(a_{b_{p+1}}\oplus b_{p+1}) \oplus (b_p\oplus b_{p+1}) (abp⊕bp)⊕(bp+1⊕bp)<(abp+1⊕bp+1)⊕(bp⊕bp+1) .
记 c i = a i ⊕ i c_i = a_i \oplus i ci=ai⊕i , 那么考虑不等式成立时的情况 , 显然此时 c b p c_{b_p} cbp 和 c b p + 1 c_{b_{p+1}} cbp+1 二进制下从高位往低位前面位都一样 , 在某一位开始时不相等 , 且不等式左边在该位异或值为 0 0 0 .
为了寻找可能的值 , 我们将 c i c_i ci 从高向低加入 01 t r i e 01\ trie 01 trie , 对于当前想要计算答案的 j j j , 我们将 c j c_j cj 放入 t r i e trie trie 树中匹配 ,不妨假设当前的点是 u u u , 该走第 s s s 位 , 下一步要走向该节点的 0 0 0 子节点 , 那么此时会对答案产生影响的就是 1 1 1 子节点中的情况(此时就是上述的第一个不相等的位) , 又因为根据上述不等式我们可以断定所有走向 1 1 1 子节点的 c i c_i ci , 在前面的位相同 , 在第 s s s 位一定有 c i ⊕ ( i ⊕ j ) ≠ c j ⊕ ( i ⊕ j ) c_i \oplus (i\oplus j) \neq c_j \oplus (i\oplus j) ci⊕(i⊕j)=cj⊕(i⊕j) , 所以在该位要么前面是 0 0 0 后面是 1 1 1 , 要么相反.
现在考虑原不等式 , 上面推出的结论显然也对原不等式成立 , 所以考虑 a i ⊕ j a_i \oplus j ai⊕j 在第 s s s 位的情况 , 因为 j j j 已知 , 所以如果我们可以知道可以走到上述 1 1 1 子节点中的且在第 s s s 位为 1 1 1 的 c i c_i ci 中最大的 v a l i val_i vali , 那么有 v a l j = m a x ( v a l j , v a l i + 1 ) val_j = max(val_j, val_i+1) valj=max(valj,vali+1) .
那么令 d p [ 0 / 1 ] [ u ] dp[0/1][u] dp[0/1][u] 代表所有经过点 u u u 的 c i c_i ci 中 , 在点 u u u 对应的深度(也就是从高到低的位数)的值为 0 / 1 0/1 0/1 的 c i c_i ci 中最大的 v a l i val_i vali , 那么这就是我们上面所需要的 , 最后来说明如何更新 .
如果使用 d f s dfs dfs 去用 c i c_i ci 走 t r i e trie trie , 假设当前求出了 v a l i val_i vali , 那么在 d f s dfs dfs 回溯且到达点 u u u 时 , 求出 c i c_i ci 在该位的 0 / 1 0/1 0/1 , 然后更新即可 .
代码
#include <bits/stdc++.h>
using namespace std;
std::mt19937 rng(std::random_device{}());
typedef long double ld;
typedef long long ll;
typedef unsigned long long ull;
typedef const int& cint;
typedef const ll& cll;
typedef pair<int, int> pii;
typedef pair<int, ll> pil;
#define ls (loc<<1)
#define rs ((loc<<1)|1)
const int mod1 = 1e9+7;
const int mod2 = 998244353;
const int inf_int = 0x7fffffff;
const int hf_int = 0x3f3f3f3f;
const ll inf_ll = 0x7fffffffffffffff;
const double ept = 1e-9;
int n;
int a[300300];
int val[300300];
int dp[2][300001*30];
int tr[2][300001*30], cnt;
void dfs(int u, int x, int step) {
if(step >= 0) {
int v = (a[x]>>step) & 1;
if(tr[v^1][u]) { val[x] = max(val[x], dp[1&(x>>step)][tr[v^1][u]] + 1); }
if(tr[v][u]) { dfs(tr[v][u], x, step-1); }
}
dp[1&((a[x]^x)>>(step+1))][u] = max(dp[1&((a[x]^x)>>(step+1))][u], val[x]);
}
void solve(cint T) {
cin >> n;
for(int i=0; i<n; i++) { val[i] = 1; }
for(int i=0; i<=cnt; i++) { dp[0][i] = tr[0][i] = dp[1][i] = tr[1][i] = 0; }
cnt = 0;
for(int i=0; i<n; i++) { cin >> a[i]; a[i] ^= i; }
int u, v;
for(int i=0; i<n; i++) {
u = 0;
for(int j=30; j>=0; j--) {
v = 1 & ((a[i])>>j);
if(tr[v][u] == 0) { tr[v][u] = ++cnt; }
u = tr[v][u];
}
dfs(0, i, 30);
}
int ans = 0;
for(int i=0; i<n; i++) { ans = max(ans, val[i]); }
cout << ans << '\n';
}
int main() {
//cout.flags(ios::fixed); cout.precision(8);
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T_=1;
std::cin >> T_;
for(int _T=1; _T<=T_; _T++)
solve(_T);
return 0;
}
E. Misha and Paintings
题意
给你一个 n × n n\times n n×n 的方阵 , 每一次你可以选择一个任意大小的方阵 , 并把其中的数改成任意 , 问 : 要求矩阵中恰好有 k k k ( k ≤ n 2 k\leq n^2 k≤n2) , 个不同的数字的最少次数.
复杂度要求 Θ ( n 3 ) \Theta(n^3) Θ(n3)
思路
记 s u m sum sum 为原矩阵中的不同数的个数 , s u m ≤ k sum \leq k sum≤k 的情况做法显然 .
现在考虑 s u m > k sum > k sum>k , 首先可以证明次数最多不超过两次 , 第一次找到从左上角开始的满足删除其中所有数字后矩阵中的不同数字个数不小于 k k k 的最长的方阵边长 l l l , 如果恰好等于 k k k , 那么就是一次 , 否则考虑以 ( l + 1 , l + 1 ) (l+1, l+1) (l+1,l+1) 为右下角的方阵 , 同样对于考虑的方阵将其中的所有数字删除 , 可以发现 , 长度每增加一时最多只会删除两个数字 , 所以必定有一个长度 r r r 满足删除后要么恰好等于 k k k , 要么等于 k − 1 k-1 k−1 , 此时我们把两次删除的位置填一个没出现过的数就满足了条件 .
那么只需要判定是不是 1 1 1 即可 , 本质上 , 我们想知道删掉某一个方阵对不同数的个数的影响 , 而这和数的分布相关 , 所以我们枚举每一个出现过的数 , 就可以在 Θ ( n 2 ) \Theta(n^2) Θ(n2) 计算出每一个数字的最左最右最上最下 , 显然只有包含这些位置的方阵会容纳这个数产生的贡献 , 而以某个位置作为右下角的方阵能容纳某个数唯一的条件就是方阵的边长的大小 , 所以考虑 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 表示以 i , j i,j i,j 作为右下角的且长度为 k k k 的方阵的容纳的数字个数 , 但是空间会不够 , 所以我们枚举方阵长度 , 对于每一个长度 , 使用 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示上述所说 , 而固定长度之后 , 对于一个数字 , 能容纳它的方阵的右下坐标一定是一个矩阵 , 这显然可以使用二维差分来处理 .
如果存在一个 d p [ i ] [ j ] = = c dp[i][j] == c dp[i][j]==c 且 s u m − c ≤ k sum-c\leq k sum−c≤k , s u m − c ≥ k − 1 sum-c \geq k-1 sum−c≥k−1 , 那么就有 1 1 1 的方案.
代码
#include <bits/stdc++.h>
using namespace std;
std::mt19937 rng(std::random_device{}());
typedef long double ld;
typedef long long ll;
typedef unsigned long long ull;
typedef const int& cint;
typedef const ll& cll;
typedef pair<int, int> pii;
typedef pair<int, ll> pil;
#define ls (loc<<1)
#define rs ((loc<<1)|1)
const int mod1 = 1e9+7;
const int mod2 = 998244353;
const int inf_int = 0x7fffffff;
const int hf_int = 0x3f3f3f3f;
const ll inf_ll = 0x7fffffffffffffff;
const double ept = 1e-9;
int n, k;
int a[505][505];
int num[505*505];
int x[2][505*505];
int y[2][505*505];
int val[502][502];
void solve(cint T) {
cin >> n >> k;
int sum = 0;
memset(x[0], 1, sizeof x[0]);
memset(y[0], 1, sizeof y[0]);
memset(x[1], -1, sizeof x[1]);
memset(y[1], -1, sizeof y[1]);
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++) {
cin >> a[i][j];
++num[a[i][j]];
x[0][a[i][j]] = min(x[0][a[i][j]], i);
x[1][a[i][j]] = max(x[1][a[i][j]], i);
y[0][a[i][j]] = min(y[0][a[i][j]], j);
y[1][a[i][j]] = max(y[1][a[i][j]], j);
if(num[a[i][j]] == 1) { ++sum; }
}
}
if(sum <= k) { cout << k-sum << '\n'; }
else {
int mx;
bool flag = false;
for(int l=1; l<=n; l++) {
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++) {
val[i][j] = 0;
}
}
for(int i=1; i<=n*n; i++) {
if(y[1][i] <= 0 || x[1][i] <= 0) { continue; }
mx = max(y[1][i]-y[0][i], x[1][i]-x[0][i]) + 1;
if(mx <= l) {
val[max(x[1][i], l)][max(y[1][i], l)] += 1;
val[min(x[0][i]+l, n+1)][min(y[0][i]+l, n+1)] += 1;
val[max(x[1][i], l)][min(y[0][i]+l, n+1)] -= 1;
val[min(x[0][i]+l, n+1)][max(y[1][i], l)] -= 1;
}
}
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++) {
val[i][j] += val[i][j-1] + val[i-1][j] - val[i-1][j-1];
if(sum-val[i][j] <= k && sum-val[i][j] >= k-1) { flag = true; break; }
}
}
if(flag) { break; }
}
if(flag) { cout << 1 << '\n'; }
else { cout << 2 << '\n'; }
}
}
int main() {
//cout.flags(ios::fixed); cout.precision(8);
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T_=1;
//std::cin >> T_;
for(int _T=1; _T<=T_; _T++)
solve(_T);
return 0;
}