Loading

[J组模拟赛 #002 T4]分组选数

分组选数

题目大意:

\(n\) 个数,第 \(i\) 个数是 \(a_i\),属于第 \(b_i\) 个集合中。对于每个集合,若从中选出若干个数,则价值为这些数的异或和,总共的价值就是所有集合的价值的和。现在最多可以选 \(m\) 个数,问可以获得的最大价值是多少。

数据范围:

对于 \(100\%\) 的数据:

  • \(n, m \leq 2000\)
  • \(a_i \leq 2000\)
  • \(b_i \leq 2000\)

解题分析:

朴素做法:

假设 \(a_{i,j}\) 为第 \(i\) 个集合中的第 \(j\) 个数。
首先,最质朴的想法就是设 \(f_{i,j,k,l}\) 为第 \(i\) 个集合里,前 \(j\) 个数中选择 \(k\) 个数能否凑成 \(l\)。由于异或运算符是可逆的,所以转移方程就是:

\[f_{i,j,k,l} = f_{i,j-1,k,l} | f_{i,j-1,k-1,l \ \texttt{xor} \ a_{i,j}} \]

然后再进行一个背包,这里 \(g_{i,j}\) 为前 \(i\) 个集合中选 \(j\) 个数的最大答案:

\[g_{i,j}=\max(g_{i,j}, g_{i - 1,j - k} + \text{第 i 个集合里选 k 个数的答案}) \]

PS:这里 \(k\) 是任意一个数

正解:

但很明显,朴素做法时间复杂度是 \(O(n^3)\),会超时,所以我们不妨观察一下状态。由于异或并不满足最优子结构,所以我们把目光转移到了这个:选择 k 个数
在尝试将状态变成 f[i][j][l] 表示第 i 个集合中,前 j 个数异或和为 l 最少要选择多少个数 后,我们发现这是可以计算的:

\[f_{i,j,l}=\min(f_{i,j-1,l \ \texttt{xor} \ a_{i,j}}, f_{i,j-1,l}) \]

然后对于第 \(i\) 个集合中选择 \(k\) 个数的异或和,我们不妨把它设为 \(c_{i,k}\),然后很明显:

\[c_{i,f_{\text{第 i 个集合的大小}, j}} = \max(c_{i,f_{\text{第 i 个集合的大小}, j}}, j) \]

注意:由于答案一定是最优的,所以只更新 最少要取多少个数 是没有问题的,因为大一点的更新后要用的数量更多反而会不好。
然后 \(g\) 的更新还是没有区别:

\[g_{i,j}=\max(g_{i,j}, g_{i-1,j-k}+c_{i,k}) \]

然后这道题就做完了,时间复杂度 \(O(n^2)\) (大概吧,反正是这个量级的)
AC Code:

# include <bits/stdc++.h>
using namespace std;

# define ll long long
# define lf double
# define int ll
# define GO(i,a,b) for(int i = a; i <= b; i ++)
# define RO(i,b,a) for(int i = b; i >= a; i --)
# define FO(i,u,head,e) for(int i = head[u]; i; i = e[i].next)
# define CI const int
# define pii pair<int,int>
# define F first
# define S second
# define PB(x) push_back(x)
# define mem(a,x) memset(a, x, sizeof a)

CI maxn = 2007;

int n, m;
map <int, vector <int> > p;
int x, y;
int f[3007][3007]; // 一个集合中前 i 个元素异或和为 j 所用的最少个数 
int s[3007]; // 用 i 个数最大的异或和 
int g[3007][3007]; // 前 i 个集合选 j 个数最大和 

signed main(){
	cin >> n >> m;
	GO (i, 1, n){
		scanf("%lld %lld", &x, &y);
		p[y].PB(x);
	}
	GO (i, 0, 3000)
		GO (j, 0, 3000)
			g[i][j] = -2e18;
	g[0][0] = 0;
	int ii = 0;
	int ans = -2e18;
	for (auto i : p){
		ii ++;
		auto x = (i.S);
		GO (j, 0, (int) (x.size()))
			GO (k, 0, 3000)
				f[j][k] = 2e18, s[k] = 0;
		f[0][0] = 0;
		GO (j, 1, (int) (x.size()))
			GO (k, 0, 2000)
				f[j][k] = min <int> (f[j - 1][k ^ x[j - 1]] + 1, f[j - 1][k]);
		GO (k, 0, 2000) if (f[(int)(x.size())][k] != 2e18) s[f[(int)(x.size())][k]] = max <int> (s[f[(int)(x.size())][k]], k);
		GO (j, 0, 2000)
			GO (k, 0, min <int> (j, (int) (x.size())))
				g[ii][j] = max <int> (g[ii][j], g[ii - 1][j - k] + s[k]);
	}
	GO (j, 0, m) ans = max <int> (ans, g[ii][j]);
	printf("%lld", ans);
	return 0;
}
posted @ 2022-10-17 14:42  DE_aemmprty  阅读(77)  评论(0)    收藏  举报