codeforces刷题(五)
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,c\in \{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;
}