1、Maximum Subsequence Value
从(n)个数中挑(k)个数,使得(sum2^i)最大(将这(k)个数用二进制表示,如果二进制第 (i) 位为1的数的个数多于(max(1,k-2))个,就加上(2^i))。
题解
性质:选高位为1的数比选多个低位为1的数更优,(1 + 2^1 + 2^2+···+2^{n-1} <2^n)
当(n>=3)时,(k =3)能取得最大的(sum2^i)。也就是说,当(k>3)时,多选的(k-3)个数不能使得原本第(i)位的0((k =3)时)变为有效的1,既第(i)位全为1,也只有(k-3)个数,小于(max(1,k-2))。
int main()
{
int n;
cin >> n;
long long a[505];
myfor(i, 1, n) cin >> a[i];
if (n <= 3) {
long long ans = a[1];
myfor(i, 1, n) ans |= a[i];
cout << ans << endl;
}
else {
long long ans = 0;
myfor(i, 1, n) myfor(j, i + 1, n) myfor(k, j + 1, n) ans = max(ans, a[i]|a[j]|a[k]);
cout << ans << endl;
}
return 0;
}
2、Solve The Maze
给一张二维迷宫,出口是((n,m))
- . 表示这个位置可以行走
- #表示这个位置是墙
- G表示这个位置上有一个好人
- B表示这个位置上有一个坏人
问是否存在一种方案:在某些位置上放墙,既令(map[i][j] = '#'),使得好人走出迷宫而坏人不能走出迷宫。
题解
在坏人的周围都放上墙,然后从出口出发,看是否能走到所有好人所在的位置。
3、Tree Shuffling(好题)
一棵树有(n)个节点,每个节点有三个值:(a)(花费),(b)(已有的值),(c)(想要变成的值)
操作:在任意一颗子树中选择(k)个节点,然后按照你的意愿排列这(k)个节点
操作代价:假设这颗子树的根是(u),(operation\_cost = 2 * k * a[u])
问任意次操作后,将所有的节点的(b)变成(c)的最小代价是多少?
(b,cin {0,1})
题解
贪心,从花费较小的节点开始操作起
思路比较简单,就是不会写
pair<ll, ll> DFS(int u, int pa, int cost) {
pair<int, int> now; // 统计这颗子树需要对几个节点进行重新排列
if (b[u] != c[u]) {
now.first += b[u];
now.second += c[u];
}
for (int i = head[u]; i != -1; i = e[i].next) {
int v = e[i].to;
if (v == pa) continue;
pair<ll, ll> nex = DFS(v, u, min(cost, a[v]));
now.first += nex.first;
now.second += nex.second;
}
int cnt = min(now.first, now.second);
ans += 2ll * cnt * cost; // cost: root -> u 这条链上的最小花费
now.first -= cnt;
now.second -= cnt;
return now;
}
4、Odd-Even Subsequence
给一个序列(a),从中挑选(k)个数构成序列(s)(不改变数的位置)。定义(x = min(max{s_1,s_3,s_5,s_7,···},max{s_2,s_4,s_6,s_8,···})),问最小的(x)是多少?
题解
最小化最大值,考虑二分枚举答案,检查以下两种情况即可,换言之,我们需要构造出这样一个子序列(s)
- 答案在奇序列中
- 答案在偶序列中
我的想法是:假设每次枚举的答案(mid)一定在(a)中(用(mid)构造子序列(s)),然后发现(check)的时候,非常不好做,因为必须判断假设是否成立。
/*
不断缩小答案的上界
*/
bool check(int x) {
// 答案在偶序列
for (int i = 1, cnt = 0; i <= n; i++) {
if (((cnt & 1) && (a[i] <= x)) || (cnt % 2 == 0)) cnt++; // 构造合适的s(贪心)
if (cnt >= k) return true;
}
// 答案在奇序列
for (int i = 1, cnt = 0; i <= n; i++) {
if (((cnt % 2 == 0) && (a[i] <= x)) || (cnt & 1)) cnt++;
if (cnt >= k) return true;
}
return false;
}
5、AND, OR and square sum
给一个序列(a),有如下操作:
- 任选两个数(a_i),(a_j),令(a_i^{'}= a_i & a_j),(a_j^{'} = a_i |a_j)
可以操作任意次,令(ans = sum_{i = 1}^na_i^{'2}),最大化(ans)。
题解
假设(x = a_i |a_j),有(x >= a_i)且(x >= a_j),所以我们可以让(a)中的最大值不断变大,而(ans)不会变小。按照这个思路有:令(a_1 = a_{max})(按降序排序),有(a_1 = a_1 | a_2),(a_2 = a_1 & a_2)、(a_1 = a_1 | a_3),(a_3 = a_1 & a_3)、······、(a_1 = a_1 | a_n),(a_n = a_1 & a_n)。用相同的办法求出(a_2^{'}),(a_3^{'}),...,(a_n^{'}),既能求得最大的(ans)。
显然,这样计算需要两次循环,复杂度(O(n^2))。实际上,给出的操作有个性质:不会改变每位包含1的个数,既(a_i^{'} = a_i & a_j),(a_j^{'} = a_i | a_j) 把 (a_i) 的二进制位上的 1 给了 (a_j),但总的1的个数并没有改变。
所以,我们只需要统计二进制每位有多少个1,然后组成一个个数就行了。
妙啊,做(cf)的题始终差那临门一脚!
int main()
{
int x;
cin >> n;
myfor(i, 1, n) {
cin >> x;
int t = 0;
while(x) {
cnt[t++] += x & 1;
x >>= 1;
}
}
ll ans = 0;
myfor(i, 1, n) {
ll x = 0;
myfor(j, 0, 20) if (cnt[j]) {
x += (1 << j);
cnt[j]--;
}
ans += x * x;
}
cout << ans << endl;
return 0;
}