26.1.1

D. Very Different Array

贪心

让绝对值最大,就是让值都尽可能分开,如果两个数组长度相同,结论是经典的,一个升序,一个降序,一一对应。但这里长度不一样,还是一个升序,一个降序,但是问题在于 A A A和 B B B的哪里对齐,极端情况就是开头和开头对齐,或结尾和结尾对齐,答案就是这两种取 m a x max max

Reverse XOR

位运算,思维

手玩可以发现如果 n n n的某一位是 1 1 1,其对称位置是 0 0 0,那么 s , r e v ( s ) s,rev(s) s,rev(s)在这两个位置就是 11 , 01 11,01 11,01或者 10 , 00 10,00 10,00,这与 s , r e v ( s ) s,rev(s) s,rev(s)矛盾,所以 n n n的二进制必须是回文的。

并且如果 n n n长度是奇数,那 r e v ( s ) , s rev(s),s rev(s),s在中心肯定是相等的, n n n在中心也就必须是 0 0 0

前后缀有 0 0 0无所谓,可以补 0 0 0使得两侧的 0 0 0个数相同,对结果无影响

所以总结就是,去掉忽略前后缀的 0 0 0,必须是二进制回文串,如果此时长度奇数,中心还必须是 0 0 0

Cobb

枚举+思维/sosdp

简单做法是,注意到 i ∗ j i*j i∗j的数量级别比 k ∗ ( a i o r a j ) k*(a_i or a_j) k∗(aioraj)大,所以答案肯定是在 i j ij ij都较大的区域取到的,可以只枚举 i , j i,j i,j较大的区间,暴力计算,大概只用枚举 [ n − k , n ] [n-k,n] [n−k,n]这一段,复杂度 O ( k 2 ) O(k^2) O(k2)

但这个只适用于 k k k较小,如果 k k k任意取,正解是 s o s d p sosdp sosdp。我们前面都在枚举 i , j i,j i,j,转换思路,不妨枚举 a i o r a j a_i or a_j aioraj,这样后一项就是确定的了,我们只需要确定,对于一个确定的 o r or or值, i ∗ j i*j i∗j的最大值,也就是维护,满足 a i , a j a_i,a_j ai,aj都是 o r or or值的子集的,最大和次大的 i , j i,j i,j

维护每个 m a s k mask mask的所有子集的最值,需要我们较快的遍历每个 m a s k mask mask的所有子集,这就是经典的 s o s d p sosdp sosdp。每个状态维护最大和次大的下标,转移时,两个状态的四个下标,取较大的两个。

初始化时,每个 m a s k mask mask赋值为这个值的最大的两个下标,不存在的话赋值 0 0 0。

最后计算答案,如果一个 m a s k mask mask至少有两个有效的下标,更新答案。

c 复制代码
pii f[1 << 20];
void solve() {
	int n, k;
	cin >> n >> k;
 
	map<int, set<int>>mp;
	int mx = 0;
	for (int i = 1; i <= n; i++) {
		int x;
		cin >> x;
 
		mx = max(mx, __lg(x) + 1);
		mp[x].insert(i);
		if (mp[x].size() > 2) {
			mp[x].erase(mp[x].begin());
		}
	}
 
	for (int i = 0; i < (1 << mx); i++) {
		if (mp.count(i)) {
			if (mp[i].size() == 2) {
				f[i] = {*mp[i].begin(), *mp[i].rbegin()};
			} else {
				f[i] = {*mp[i].begin(), 0};
			}
		} else {
			f[i] = {0, 0};
		}
	}
 
 
	for (int i = 0; i < mx; i++) {
		for (int j = 0; j < (1 << mx); j++) {
			if (j >> i & 1) {
				vi t = {f[j].fi, f[j].se, f[j ^ (1 << i)].fi, f[j ^ (1 << i)].se};
				sort(t.begin(), t.end());
				f[j] = {t[3], t[2]};
			}
		}
	}
 
	int ans = -inf;
	for (int i = 0; i < (1 << mx); i++) {
//		cout<<i<<' '<<f[i].fi * f[i].se - k * i<<'\n';
		if (f[i].fi && f[i].se) {
			ans = max(ans, f[i].fi * f[i].se - k * i);
		}
	}
	cout << ans << '\n';
}

E. Segment Sum

数位dp+状压+维护方案数和答案

出现的数位不超过 k k k种,用一个集合维护出现过的数位。要求的是合法数字的和,不是个数了,分别维护个数和数字的和,每一位数字的贡献可以根据,子问题方案数和这个数字的位置算出来。

c 复制代码
int p[30];
int work(int x, int k) {
	string s = to_string(x);
	int n = s.size();
 
	vvi f(n, vi(1 << 10, -1));//数位和
	vvi g(n, vi(1 << 10, -1));//方案数
	auto &&dfs = [&](auto &&dfs, int i, int mask, bool lim, bool lead)->pii{
		if (i == n) {
			return {0, (lead == 0)&&(__builtin_popcount(mask) <= k)};
		}
 
		if (!lim && !lead && f[i][mask] != -1) {
			return {f[i][mask], g[i][mask]};
		}
 
		int up;
		if (lim) {
			up = s[i] - '0';
		} else {
			up = 9;
		}
 
		int cnt = 0, sum = 0;
		for (int d = 0; d <= up; d++) {
			pii res;
			if (d == 0 && lead) {
				res = dfs(dfs, i + 1, mask, 0, 1);
			} else {
				res = dfs(dfs, i + 1, mask | (1 << d), lim && (d == up), 0);
			}
			cnt = (cnt + res.se) % M2;
			sum = (sum + res.se * d % M2 * p[n - i - 1] % M2 + res.fi) % M2;
		}
 
		if (!lim && !lead) {
			f[i][mask] = sum;
			g[i][mask] = cnt;
		}
 
		return {sum, cnt};
	};
 
	return dfs(dfs, 0, 0, 1, 1).fi;
}
void solve() {
	int l, r, k;
	cin >> l >> r >> k;
	p[0] = 1;
	for (int i = 1; i <= 20; i++) {
		p[i] = p[i - 1] * 10 % M2;
	}
 
	cout << (work(r, k) - work(l - 1, k) + M2) % M2;
}

Subsequences Galore

sosdp+容斥原理

一个字符串数组 b b b,至少是其中一个字符串子序列的字符串个数,不太好求,但这是个并集大小,可以转化为容斥原理,计算出他所有元素的交集,然后乘上容斥系数累加即可。

但我们这里要对所有集合 m a s k mask mask都进行容斥,也就是枚举所有状态的所有子集,暴力枚举 O ( 4 n ) 或 O ( 3 n ) O(4^n)或O(3^n) O(4n)或O(3n)的,可以用 s o s d p sosdp sosdp降低到 O ( n 2 n ) O(n2^n) O(n2n)。这里就相当于用 s o s d p sosdp sosdp进行容斥原理,需要注意转移系数需要是容斥系数。

接下来唯一的问题就是怎么算元素交集的答案了:

对于一个字符串,他的所有子序列,由于字符串是升序的,就是每一段可以选任意个,答案是每一段的长度+1相乘,加一是因为这一段也可以不选,也是一种选择。

对于多个字符串,他们的公共子序列,需要每一种字符的出现次数都不超过任何一个字符串,所以把每种字符出现次数取最小值,然后类似前面的式子,加一,相乘。

C. Count Good Numbers

容斥

正难则反,计算至少包含一个一位数质因子的个数。一位数质因子只有 2 , 3 , 5 , 7 2,3,5,7 2,3,5,7,现在相当于求并集大小,计算交集大小容斥即可,交集大小就是区间内包含 2 , 3 , 5 , 7 , 2 ∗ 3 , 2 ∗ 5.. 2,3,5,7,2*3,2*5.. 2,3,5,7,2∗3,2∗5..等因子的数字个数,用 f l o o r ( x / f a c t o r ) floor(x/factor) floor(x/factor)即可得到

P5322 [BJOI2019] 排兵布阵

分组背包 排序 贪心

b b b数组的分配就是一个背包,每个位置有 [ 0 , m ] [0,m] [0,m]种选择,相当于一个分组背包,但这样的话复杂度就有 O ( n m 2 ) O(nm^2) O(nm2)了,需要优化

注意到得分只需要比 a [ i ] [ j ] ∗ 2 a[i][j]*2 a[i][j]∗2大,那么最节约的方式就是恰好 2 a [ i ] [ j 1 ] + 1 2a[i][j_1]+1 2a[i][j1]+1,更大的,直到下一个 2 a [ i ] [ j 2 ] + 1 2a[i][j_2]+1 2a[i][j2]+1之间的取值都是不优的,可以不用枚举,那么 b b b的每个位置,可能取值只有 O ( s ) O(s) O(s)个,总复杂度 O ( s n m ) O(snm) O(snm)

为了知道一个 b i b_i bi超过多少个 a ( i , j ) a(i,j) a(i,j),需要对 a a a的每一列排序。

E. Tufurama

离线+树状数组/在线+可持久化线段树

离线做法,本质是二位数点,满足 i < j , i < = a j , j < = a i i<j,i<=a_j,j<=a_i i<j,i<=aj,j<=ai,我们枚举 i i i,统计合法 j j j,首先为了满足 i < j i<j i<j,初始把所有 j j j插入树状数组,枚举到一个 j j j就把他删除;为了满足 i < = a j i<=a_j i<=aj,把 j j j按 a j a_j aj升序排序,双指针,把比 i i i小的 a j a_j aj删掉,注意一个 j j j不能被多次删除,维护一个标记记录每个 j j j是否被删除,此时剩下的都是合法 j j j,做 j < = a i j<=a_i j<=ai查询即可

在线做法,变换下条件: j < i , i < = a j , j < = a i j<i,i<=a_j,j<=a_i j<i,i<=aj,j<=ai,枚举 a i a_i ai,插入可持久化线段树,每次查询合法 j j j,首先 j j j下标要在 [ 1 , a i ] [1,a_i] [1,ai],由于 a i = 1 e 9 a_i=1e9 ai=1e9,准确说是 [ 1 , min ⁡ ( n , a i ) ] [1,\min(n,a_i)] [1,min(n,ai)],对于在这个区间内的元素,查值满足 a j > = i a_j>=i aj>=i的个数,这是一个区间上的值域查询,可持久化线段树解决。

相关推荐
csuzhucong2 小时前
圆柱三阶魔方、六棱柱魔方
算法
mit6.8242 小时前
vector<int> dfs
算法
ullio3 小时前
div1+2. 2178F - Conquer or of Forest
算法
Leweslyh3 小时前
制导算法开发实践指南:从入门到精通
算法·开发·武器·制导律设计
chushiyunen4 小时前
快慢双指针算法笔记
数据结构·笔记·算法
烟锁池塘柳04 小时前
一文总结模型压缩技术:剪枝、量化与蒸馏的原理、实践与工程思考
算法·机器学习·剪枝
独自破碎E4 小时前
Leetcode1438绝对值不超过限制的最长连续子数组
java·开发语言·算法
東雪木4 小时前
编程算法学习——数组与排序算法
学习·算法
你撅嘴真丑4 小时前
方格取数 矩阵取数游戏 -动态规划
算法·动态规划