比赛链接:

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;
}
posted on 2022-09-12 15:33  Hamine  阅读(21)  评论(0编辑  收藏  举报