Codeforces Round #646 (Div. 2) A - E 解题报告
题目链接
\(A. Odd Selection\)
\(Description:\)
给定长度为 \(n\) 的数列,选出 \(x\) 个数,问是否存在一种方案使得这 \(x\) 个数的和为奇数?
\(Solution:\)
计算出数列中的奇数和偶数的个数,然后优先选择偶数,最后再选择奇数个奇数即可。
\(Code:\)
/*
@Author: nonameless
@Date: 2020-06-01 08:10:05
@Email: 2835391726@qq.com
@Blog: https://www.cnblogs.com/nonameless/
*/
#include <bits/stdc++.h>
#define x first
#define y second
#define pb push_back
#define sz(x) (int)x.size()
#define all(x) x.begin(), x.end()
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PLL;
typedef pair<int, int> PII;
const double eps = 1e-8;
const double PI = acos(-1.0);
const int INF = 0x3f3f3f3f;
const ll LNF = 0x3f3f3f3f3f3f;
inline int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
inline ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }
inline int lcm(int a, int b) { return a * b / gcd(a, b); }
const int N = 1e3 + 10;
int a[N];
int main(){
int t; cin >> t;
while(t --){
int n, x; cin >> n >> x;
int odd = 0, even = 0;
for(int i = 1; i <= n; i ++){
int num; cin >> num;
if(num & 1) odd ++;
else even ++;
}
int t = max(0, x - even); // 我们需要选择的奇数的个数
if(even && t % 2 == 0) t ++; // 个数为偶数要变为奇数,条件是必须有偶数存在
if(odd >= t && t & 1) puts("YES");
else puts("NO");
}
return 0;
}
\(B. Subsequence Hate\)
\(Description:\)
给定一个二进制字符串,满足任何一个 \(1\) 的两边不能同时有 \(0\),任何一个 \(0\) 两边不能同时有 \(1\) 就是好字符串,你可以修改原字符串(\(0 -> 1\ or\ 1 -> 0\))。问最少几次操作使得原字符串变为好字符串?
\(Solution:\)
最后的字符串显然变成了:\(000011111\) 或者 \(111110000\) 这两种类似的格式,所以我们可以枚举中间变化的那个点,计算代价可以通过预处理出每个位置的左右有多少个 \(01\)。
\(Code:\)
/*
@Author: nonameless
@Date: 2020-06-01 08:39:38
@Email: 2835391726@qq.com
@Blog: https://www.cnblogs.com/nonameless/
*/
#include <bits/stdc++.h>
#define x first
#define y second
#define pb push_back
#define sz(x) (int)x.size()
#define all(x) x.begin(), x.end()
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PLL;
typedef pair<int, int> PII;
const double eps = 1e-8;
const double PI = acos(-1.0);
const int INF = 0x3f3f3f3f;
const ll LNF = 0x3f3f3f3f3f3f;
inline int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
inline ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }
inline int lcm(int a, int b) { return a * b / gcd(a, b); }
const int N = 1e3 + 10;
int f1[N], f2[N], f3[N], f4[N];
char s[N];
int main(){
int t; cin >> t;
while(t --){
scanf("%s", s + 1);
int len = strlen(s + 1);
memset(f1, 0, sizeof f1); // f1[i]: [1, i] 有多少个 0
memset(f2, 0, sizeof f2); // f2[i]: [i, n] 有多少个 1
memset(f3, 0, sizeof f3); // f3[i]: [1, i] 有多少个 1
memset(f4, 0, sizeof f4); // f4[i]: [i, n] 有多少个 0
for(int i = 1; i <= len; i ++){
f1[i] = f1[i - 1] + (s[i] == '0');
f3[i] = f3[i - 1] + (s[i] == '1');
}
for(int i = len; i >= 1; i --){
f2[i] = f2[i + 1] + (s[i] == '1');
f4[i] = f4[i + 1] + (s[i] == '0');
}
int ans = INF;
for(int i = 1; i <= len; i ++){
int t1 = f1[i] + f2[i + 1]; // 1111100000 形式
int t2 = f3[i] + f4[i + 1]; // 0000011111 形式
ans = min({ans, t1, t2});
}
cout << ans << endl;
}
return 0;
}
\(C. Game On Leaves\)
\(Descrition:\)
给定一棵树和一个结点 \(x\),两人每次轮流删除一个度为 \(1\) 的点(所在边随之删除),谁先删除 \(x\) 谁赢,两人都采取最优策略,问谁赢?
\(Solution:\)
首先可以想到的是如果 \(x\) 的度为 \(1\),那么先手赢。排除掉这种情况之后,又由于两人都采取的最优的策略,那么显然 \(x\) 会是最后删除的一个点,而由于是树,总共有 \(n - 1\) 条边,所以只需要 \(n - 1\) 步就可以把 \(x\) 删去,显然当 \(n - 1\) 是奇数时先手必赢,偶数就后手赢。
\(Code:\)
/*
@Author: nonameless
@Date: 2020-06-01 09:04:43
@Email: 2835391726@qq.com
@Blog: https://www.cnblogs.com/nonameless/
*/
#include <bits/stdc++.h>
#define x first
#define y second
#define pb push_back
#define sz(x) (int)x.size()
#define all(x) x.begin(), x.end()
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PLL;
typedef pair<int, int> PII;
const double eps = 1e-8;
const double PI = acos(-1.0);
const int INF = 0x3f3f3f3f;
const ll LNF = 0x3f3f3f3f3f3f;
inline int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
inline ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }
inline int lcm(int a, int b) { return a * b / gcd(a, b); }
int main(){
int t; cin >> t;
while(t --){
int n, x;
cin >> n >> x;
int cnt = 0; // 计算 x 的度
for(int i = 1; i < n; i ++){
int u, v; cin >> u >> v;
if(u == x || v == x) cnt ++;
}
if(cnt <= 1 || n % 2 == 0) puts("Ayush");
else puts("Ashish");
}
return 0;
}
\(D. Guess The Maximums\)
\(Description:\)
交互式题,看了半天才看懂。
给定 \(n, k\),意为有一个长度为 \(n\) 的数组 \(A\)(未知,但值域在 \([1,\ n]\) 中),让你解密出长度为 \(k\) 的密码序列 \(P\)。再给定 \(k\) 个集合(集合两两不相交),集合里的元素是 \(A\) 的下标,对于一个集合 \(S\) 可以解出一个密码,即:
你可以进行 \(12\) 次询问,每次询问你也将提供一个集合 \(T\)(和 \(S\) 一样也是代表下标),程序会返回 \(\max_{j \in T}A[j]\)。最后你需要解出密码序列 \(P\)。
\(Solution:\)
由于 \(S\) 解出密码是他补集的最大值 \(mx\),那么分两种情况:
- 最大值在其中一个 \(S\) 中
其他 \(k - 1\) 个集合解出密码就是 \(mx\) - 最大值不在 \(S\) 中
\(k\) 个集合解出的密码都是 \(mx\)
所以最少有 \(k - 1\) 个密码是 \(mx\),所以我们可以先用一次查询得出最大值 \(mx\),然后来判断是否存在一个集合中包含了 \(mx\) 得下标?由于集合得个数有 \(1000\) 个,而我们只剩 \(11\) 次查询了,而这两个数字,显然是在提醒我们用二分。
我们可以二分来确定 \(mx\) 是否存在在某个集合。给每个集合一个下标,确定了 \(l,\ r\),然后算出 \(mid\),将 \([l,\ mid]\) 得集合取并集,作为查询集合,如果返回的是 \(mx\),那么说明 \(mx\) 在 \([l,\ mid]\),否则就在 \([mid + 1,\ r]\) 中,最后根据确定的那个集合,再查询一次他的补集,即得到他确定的密码(因为我们已经确定了其他 \(k - 1\) 个集合对应的密码就是 \(mx\) 了)。
\(Code:\)
/*
@Author: nonameless
@Date: 2020-06-01 11:32:21
@Email: 2835391726@qq.com
@Blog: https://www.cnblogs.com/nonameless/
*/
#include <bits/stdc++.h>
#define x first
#define y second
#define pb push_back
#define sz(x) (int)x.size()
#define all(x) x.begin(), x.end()
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PLL;
typedef pair<int, int> PII;
const double eps = 1e-8;
const double PI = acos(-1.0);
const int INF = 0x3f3f3f3f;
const ll LNF = 0x3f3f3f3f3f3f;
inline int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
inline ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }
inline int lcm(int a, int b) { return a * b / gcd(a, b); }
const int N = 1e3 + 10;
int n, k;
vector<int> s[N];
int ans[N];
// 查询
int query(vector<int> ask){
printf("? %d", sz(ask));
for(int i = 0; i < sz(ask); i ++)
printf(" %d", ask[i]);
puts("");
fflush(stdout); // 用 printf 一定要写
int res; scanf("%d", &res);
return res;
}
int main(){
int t; cin >> t;
while(t --){
cin >> n >> k;
for(int i = 1; i <= k; i ++){
int c; cin >> c;
s[i].clear();
for(int j = 1; j <= c; j ++){
int x; scanf("%d", &x);
s[i].pb(x);
}
}
vector<int> ask; // 查询的集合
for(int i = 1; i <= n; i ++) ask.pb(i);
int mx = query(ask); // 得到最大值
int l = 1, r = k, mid;
while(l < r){
mid = l + r >> 1;
ask.clear();
// 这里取 [l, mid] 的并集
for(int i = l; i <= mid; i ++)
for(int j = 0; j < sz(s[i]); j ++)
ask.pb(s[i][j]);
int x = query(ask);
if(x == -1) return 0;
if(x == mx) r = mid;
else l = mid + 1;
}
ask.clear();
// 得到他的补集
map<int, int> mp;
for(int i = 0; i < sz(s[l]); i ++) mp[s[l][i]] = 1;
for(int i = 1; i <= n; i ++) if(!mp[i]) ask.pb(i);
int x = query(ask);
if(x == -1) return 0;
printf("!");
for(int i = 1; i <= k; i ++){
if(i == l) printf(" %d", x);
else printf(" %d", mx);
}
puts("");
fflush(stdout);
string ss; cin >> ss;
if(ss == "Incorrect") return 0;
}
return 0;
}
\(E. Tree Shuffling\)
\(Description:\)
给定一颗树,每个结点都有一个初始值 \(b_i\) 和目标值 \(c_i\) 和一个代价 \(a_i\),要求最后每个结点的值变为 \(c_i\),对于以 \(u\) 为根的子树,他要改变他的子树中的 \(t\) 个点的值(将他们的 \(b_i\) 以某种顺序排列)的代价为 \(t * a_u\),问最小代价是多少?其中特别的是:\(0 \leq b_i,c_i \leq 1\)。
\(Solution:\)
读题的时候没有注意 \(b_i,c_i\) 的限制,发现做不了,看了官方题解才发现。。。
就是一个树上贪心,记录一下每颗子树的 \(b_i\) 值 的 \(0\) 和 \(1\) 的个数,把父节点的 \(a_i\) 往下传,和当前结点取 \(min\) 得到改变的最小代价。最后判断一下是否还有没有多的 \(0\) 和 \(1\) 存在即可。感觉比 \(D\) 要简单。
\(Code:\)
/*
@Author: nonameless
@Date: 2020-06-01 10:38:05
@Email: 2835391726@qq.com
@Blog: https://www.cnblogs.com/nonameless/
*/
#include <bits/stdc++.h>
#define x first
#define y second
#define pb push_back
#define sz(x) (int)x.size()
#define all(x) x.begin(), x.end()
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PLL;
typedef pair<int, int> PII;
const double eps = 1e-8;
const double PI = acos(-1.0);
const int INF = 0x3f3f3f3f;
const ll LNF = 0x3f3f3f3f3f3f;
inline int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
inline ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }
inline int lcm(int a, int b) { return a * b / gcd(a, b); }
const int N = 2e5 + 10, M = N << 1;
int n;
int a[N], b[N], c[N];
ll ans = 0;
int h[N], to[M], nxt[M], idx = 0;
void add(int u, int v){
to[ ++ idx] = v; nxt[idx] = h[u]; h[u] = idx;
}
PII dfs(int cur, int fa, int w){ // 当前结点,父节点,父节点传下来的 w
w = min(w, a[cur]);
PII res = {0, 0};
if(b[cur] != c[cur]){
if(b[cur] == 1) res.x ++;
else res.y ++;
}
for(int i = h[cur]; i; i = nxt[i]){
int v = to[i];
if(v == fa) continue;
PII tmp = dfs(v, cur, w);
res.x += tmp.x; res.y += tmp.y; // 加上子树的
}
int cnt = min(res.x, res.y); // 直接处理
ans += 2ll * cnt * w;
res.x -= cnt; res.y -= cnt;
return res;
}
int main(){
cin >> n;
for(int i = 1; i <= n; i ++)
scanf("%d%d%d", &a[i], &b[i], &c[i]);
for(int i = 1, u, v; i < n; i ++){
scanf("%d%d", &u, &v);
add(u, v); add(v, u);
}
PII res = dfs(1, -1, INF);
if(res.x || res.y) puts("-1");
else cout << ans << endl;
return 0;
}