比赛链接:
https://codeforces.com/contest/1720
D. Xor-Subsequence
题意:
长为 \(n\) 的序列,找到最长的子序列 \(b\),设序列 \(b\) 长为 \(m\),使得 \(a_{b_p} \bigoplus b_{p + 1} < a_{b_{p + 1}} \bigoplus b_p(0 <= p < m - 1)\),序列下标都从 0 开始。
思路:
\(easy version\)
转化一下关系 \(a[j] \bigoplus i < a[i] \bigoplus j\)。
考虑简单的 \(dp\)。在满足上述条件下,\(dp[i] = max \sum_{j = 0}^i (dp[i] + 1)\)。会超时。
因为 \(a_i <= 200\) 所以意味着 \(i\) 的变化不会超过 8 位,即 \(a_i - 256 <= a_i \bigoplus 200 <= a_i + 256\)。
所以优化一下,\(j\) 不用从 0 开始,从 \(i - 256\) 开始就行。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
void solve(){
int n;
cin >> n;
vector <LL> a(n), dp(n, 1);
for (int i = 0; i < n; i ++ ){
cin >> a[i];
for (int j = max(0, i - 256); j < i; j ++ ){
if ((a[j] ^ i) < (a[i] ^ j)){
dp[i] = max(dp[i], dp[j] + 1);
}
}
}
cout << *max_element(dp.begin(), dp.end()) << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int T;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
当 \(a_i\) 的范围扩大到 1e9 的时候,上述方法就不行了。
\(a[j] \bigoplus i < a[i] \bigoplus j\),意味着在二进制的状态下,前 \(k\) 位是相同的,即对于前 \(k\) 位来说,\(a[j] \bigoplus i = a[i] \bigoplus j\),两边都乘上 \(i \bigoplus j\),得到 \(a[j] \bigoplus j = a[i] \bigoplus i\)。
对于第 \(k + 1\) 位,\(a[j] \bigoplus i < a[i] \bigoplus j\),即 \(a[i] \bigoplus j = 1\),\(a[j] \bigoplus i = 0\)。
得到 \(a[i] != j, a[j] = i\),那么 \(a[i] \bigoplus i = ! a[j] \bigoplus j\)。
考虑将 \(a[j] \bigoplus j\) 插入字典树中,插入的时候,因为 \(a[i] != j\),所以每一位将 \(j\) 都要记录下来,更新对应位置的最大值。
查询的时候,要导入 \(a[i]\),找到 \(a[i] \bigoplus i = ! a[j] \bigoplus j 且 a[i] != j\) 的最大值。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 3e5 + 10;
struct Trie{
LL ch[N * 30][2], f[N * 30][2], idx = 0;
void reset(){
for (int i = 0; i <= idx; i ++ ){
for (int j = 0; j < 2; j ++ ){
ch[i][j] = f[i][j] = 0;
}
}
idx = 0;
}
void insert(LL x, LL t, LL mx){
LL u = 0;
for (int i = 30; ~ i; i -- ){
LL &v = ch[u][x >> i & 1];
if (!v) v = ++ idx;
u = v;
f[u][t >> i & 1] = max(f[u][t >> i & 1], mx);
}
}
LL query(LL x, LL id){
LL u = 0, res = 1;
for (int i = 30; ~ i; i -- ){
LL v = x >> i & 1;
res = max(res, f[ch[u][!v]][!(id >> i & 1)] + 1);
if (!ch[u][v]) break;
u = ch[u][v];
}
return res;
}
}trie;
void solve(){
int n;
cin >> n;
vector <LL> a(n);
for (int i = 0; i < n; i ++ )
cin >> a[i];
vector <LL> dp(n);
for (int i = 0; i < n; i ++ ){
dp[i] = trie.query(a[i] ^ i, a[i]);
trie.insert(a[i] ^ i, i, dp[i]);
}
cout << *max_element(dp.begin(), dp.end()) << "\n";
trie.reset();
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int T;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
E. Misha and Paintings
题意:
\(n * n\) 的矩阵,每个位置有一个数字 \(a_{i, j}\),每次可以选择一个正方形将其中的元素全部改成 \(k(1 \leq k \leq n * n)\),问最少多少次操作可以使得矩阵中的不同数字的值等于 \(t\)。
思路:
统计矩阵中不同数字的个数 \(cnt\),如果 \(cnt \leq t\),那么最少 \(t - cnt\) 次。
否则答案可以通过构造的操作,至多为二。
在矩阵中选择一个矩阵,将其改为某个元素,此操作可以让整个矩阵中不同元素的数量 \(\leq t\),如果为 \(t\) 或者 \(t - 1\),可以根据填充的不同数字使得得到答案 \(t\)。
如果不是,记该矩阵右下角坐标为 \((x, y)\) ,选择 \((x + 1, y + 1)\) 作为第二次操作的矩阵的右下角,往左上扩张,此操作可以让整体矩阵不同元素的数量减少 0 或 1 或 2,由此至多为 2。
矩阵覆盖何时可以让某个值消失,需要记录该元素的横纵坐标的最大最小值,通过二维前缀和/差分去进行快速统计。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int n, k;
cin >> n >> k;
vector<vector<int>> a(n + 1, vector<int>(n + 1));
vector<bool> has(n * n + 1);
int cnt = 0;
for (int i = 1; i <= n; i ++ ){
for (int j = 1; j <= n; j ++ ){
cin >> a[i][j];
if (!has[a[i][j]]){
cnt ++ ;
has[a[i][j]] = true;
}
}
}
if (cnt <= k){
cout << k - cnt << "\n";
return 0;
}
vector<int> xmin(n * n + 1, n + 1), ymin(n * n + 1, n + 1), xmax(n * n + 1, 0), ymax(n * n + 1, 0);
for (int i = 1; i <= n; i ++ ){
for (int j = 1; j <= n; j ++ ){
xmin[a[i][j]] = min(xmin[a[i][j]], i);
ymin[a[i][j]] = min(ymin[a[i][j]], j);
xmax[a[i][j]] = max(xmax[a[i][j]], i);
ymax[a[i][j]] = max(ymax[a[i][j]], j);
}
}
for (int len = 1; len < n; len ++ ){
vector<vector<int>> sum(n + 2, vector<int>(n + 2));
for (int i = 1; i <= n * n; i ++ ){
if (!has[i] || xmax[i] - xmin[i] + 1 > len || ymax[i] - ymin[i] + 1 > len) continue;
int x = max(1, xmax[i] - len + 1);
int y = max(1, ymax[i] - len + 1);
sum[x][y] ++ ;
sum[x][ymin[i] + 1] -- ;
sum[xmin[i] + 1][y] -- ;
sum[xmin[i] + 1][ymin[i] + 1] ++ ;
}
for (int i = 1; i <= n; i ++ ){
for (int j = 1; j <= n; j ++ ){
sum[i][j] += sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];
if (cnt - sum[i][j] == k || cnt - sum[i][j] == k - 1){
cout << "1\n";
return 0;
}
}
}
}
cout << "2\n";
return 0;
}