[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;
}

浙公网安备 33010602011771号