Codeforces Round #651 (Div. 2)
[Link](Dashboard - Codeforces Round #651 (Div. 2) - Codeforces)
A. Maximum GCD
思路
- 贪心
对于一个偶数 x x x,能和他构成最大的点应该为 x / 2 x/2 x/2且最大的值为 x / 2 x/2 x/2,如果选择一个 ( x / 2 , x ] (x/2, x] (x/2,x]那么 g c d gcd gcd是他们的约数一定 < x / 2 <x/2 <x/2,如果选择一个 [ 1 , x / 2 ) [1,x/2) [1,x/2)的数由于这个数本身已经小于 x / 2 x/2 x/2了因此他们的约数小于 x / 2 x/2 x/2。
对于一个奇数 x x x,首先 x + 1 / 2 x+1/2 x+1/2这个数和他互质,可以反证法来证明, x − 1 / 2 x-1/2 x−1/2这个数不一定,如果把 x − 1 x-1 x−1,则 x − 1 x-1 x−1和 x − 1 / 2 x-1/2 x−1/2的最大公约数为 x − 1 / 2 x-1/2 x−1/2,因此奇数的最优解就是减一变成偶数后的最优解。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
int main() {
ios::sync_with_stdio(false), cin.tie(0);
int T;
cin >> T;
while (T -- ) {
cin >> n;
if (n & 1) cout << (n - 1) / 2 << '\n';
else cout << n / 2 << '\n';
}
return 0;
}
B. GCD Compression
思路
- 构造
如果最终数组中均是偶数则一定成立,因此可以任意两个奇数或者任意两个偶数相加以可,如果说奇数偶数分别多一个就丢掉,否则在奇数和偶数中多的那个丢掉两个即可。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
int main() {
ios::sync_with_stdio(false), cin.tie(0);
int T;
cin >> T;
while (T -- ) {
vector<PII> ve1, ve2;
cin >> n;
for (int i = 1; i <= n * 2; i ++) {
int x; cin >> x;
if (x & 1) ve1.push_back({x, i});
else ve2.push_back({x, i});
}
if (ve1.size() % 2 && ve2.size() % 2) ve1.pop_back(), ve2.pop_back();
else if (ve1.size() > ve2.size()) ve1.pop_back(), ve1.pop_back();
else ve2.pop_back(), ve2.pop_back();
for (int i = 0; i < ve1.size(); i += 2) cout << ve1[i].y << ' ' << ve1[i + 1].y << '\n';
for (int i = 0; i < ve2.size(); i += 2) cout << ve2[i].y << ' ' << ve2[i + 1].y << '\n';
}
return 0;
}
C. Number Game
思路
- 贪心,博弈
分析一下可得必败态有当前为 1 1 1,当前为大于 2 2 2的偶数,中间可以冗余的操作就是每次除以一个奇约数,因此统计一下奇质数因子的个数 c n t cnt cnt,在搞出筛掉这些奇质因子后的数(这个数无法进行操作二了,一定是一个必胜或必败态的数),如果 c n t > 1 cnt>1 cnt>1则先手一定可以获胜,因为剩的数如果是必胜态我就给你留一个因子让你操作,如果是必败态我就一次把因子都筛掉,如果 c n t = = 1 cnt==1 cnt==1的话就看剩下的这个数是什么态输出即可。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
int main() {
ios::sync_with_stdio(false), cin.tie(0);
int T;
cin >> T;
while (T -- ) {
int x; cin >> x;
bool ok = true;
int cnt = 0;
int tt = x;
if (x & 1 && x > 1) {
cout << "Ashishgup\n";
continue ;
}
for (int i = 2; i <= x / i; i ++)
if (x % i == 0) {
if (i & 1) while (x % i == 0) x /= i, cnt ++, tt /= i;
else while (x % i == 0) x /= i;
}
if (x > 1) {
if (x & 1) {
cnt += x % 2;
tt /= x;
}
}
// debug(tt)
if (cnt) {
if (tt == 1 || (tt != 2 && tt % 2 == 0) || cnt > 1) cout << "Ashishgup\n";
else cout << "FastestFinger\n";
}
else {
if (tt != 2) cout << "FastestFinger\n";
else cout << "Ashishgup\n";
}
}
return 0;
}
D. Odd-Even Subsequence
思路
- 二分,贪心
直接枚举的话无从下手,但是答案具有单调性,因此直接二分答案,假设当前为二分的值为 x x x。如何判断 x x x是否成立呢,奇偶里有一个成立即可,以奇数为例,从前往后枚举,如果当前这个 < = x <=x <=x则一定选,否则下一个选的位置我们要不可以选,要不因为是当前这个数后面得跳过去,怎么样都不亏,因此直接选即可,偶数同理。注意偶数的时候要从第二个位置开始,并且无论奇偶如果当前情况不是实际的最后一个数,最后都要留一个位置给最后一个数。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 2e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
bool check(int x) {
int tag1 = (k + 1) / 2, tag2 = k / 2;
int cnt = 0;
int rr = n;
if (k % 2 == 0) rr --;
for (int i = 1; i <= n; i += 2) {
int j = i;
while (j <= n && a[j] > x) j ++;
if (j <= rr && a[j] <= x) {
i = j;
cnt ++;
if (cnt >= tag1) break;
}
else break;
}
if (cnt >= tag1) return true;
cnt = 0;
rr = n;
if (k & 1) rr --;
for (int i = 2; i <= n; i += 2) {
int j = i;
while (j <= n && a[j] > x) j ++;
if (j <= rr && a[j] <= x) {
i = j;
cnt ++;
if (cnt >= tag2) break;
}
else return false;
}
if (cnt >= tag2) return true;
return false;
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n >> k;
int l = INF, r = -1;
for (int i = 1; i <= n; i ++) cin >> a[i], l = min(l, a[i]), r = max(r, a[i]);
while (l < r) {
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
// cout << check(2) << '\n';
cout << l << '\n';
return 0;
}
E. Binary Subsequence Rotation
思路
- 贪心
首先 a , b a,b a,b相同的位置一定不用改变,如果改变的话只会不变或者更差,并且 a , b 中 0 , 1 a,b中0,1 a,b中0,1一定要相同否则无解。因此我们将不同的位置提取出来,由于不同的操作相当于循环右移一下,因此只会有两种情况 01 或 10 01或10 01或10这样的变一下 a , b a,b a,b这两个位置就想同了,同理 101010 或 010101 101010或010101 101010或010101也均可在一次操作中消除,贪心来看我们每次都想找到最多的 101010 或 010101 101010或010101 101010或010101来操作掉,区别在于当前选择 1010 1010 1010还是 0101 0101 0101消除,贪心来看如果最开始的元素是 0 0 0那么我们选 0101 0101 0101更优反之 1010 1010 1010更优,但是这样模拟操作的话复杂度过高,转化为每次操作后什么样的数会被留下来,其实就是连续的 1 1 1会留下来一部分,也就是我们要找 11110001 11110001 11110001或 0000111 0000111 0000111这样的,其它的比他短的都可以在将他消除前面的过程中附带消除掉,因此我们将 0 0 0赋值为 − 1 -1 −1, 1 1 1赋值为 1 1 1,就是找前缀和最大是多少和最小是多少然后将他们的绝对值相加即可。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n;
int res = 0;
int cnta = 0, cntb = 0;
string a, b; cin >> a >> b;
vector<int> ve;
for (int i = 0; i < n; i ++) {
if (a[i] != b[i]) {
if (a[i] == '1') ve.push_back(1);
else ve.push_back(-1);
}
cnta += a[i] == '0', cntb += b[i] == '0';
}
if (cnta != cntb) {
cout << -1 << '\n';
return 0;
}
int len1 = 0, len2 = 0;
int s = 0;
for (int i = 0; i < ve.size(); i ++) {
s += ve[i];
if (s > 0) len1 = max(len1, s);
if (s < 0) len2 = max(len2, -s);
}
cout << len1 + len2 << '\n';
return 0;
}
F2. The Hidden Pair (Hard Version)
思路
- 二分,思维
假设标记的两个点为 a , b a,b a,b,,首先有一个很重要的性质就是在 a , b a,b a,b最短路径上的点到它们的距离一定是 d i s t a , b dist_{a,b} dista,b且比不在最短路径上的点到它们的距离短。
因此我们可以先将所有的点都询问一次,这个时候返回的一定是最短路径上的一个点 t t t和路径长度 l e n len len,我们以 t t t为根来看这棵树,则 a , b a,b a,b的深度一定一个 ≥ l e n / 2 \ge len/2 ≥len/2一个小于 ≤ l e n / 2 \le len / 2 ≤len/2,考虑搜出以 t t t为根的子树的每个深度的结点有哪些,之后我们二分深度来看 a a a或 b b b到底是哪一层的哪一个,这里由于上面深度的那个性质,因此只需要二分 l e n + 1 / 2 到 l e n len + 1/2到len len+1/2到len即可,然后对于某个深度的所有点统一查询一次,由于最开始那个最短路径的性质存在,因此如果距离比 l e n len len大则一定在 a , b a,b a,b的下面否则可能在 a , b a,b a,b某个上边或者是 a , b a,b a,b,这样二分出来一个点 a a a,从点 a a a开始搜出所有深度为 l e n len len的点,然后查一次,由于第一个性质的存在只有 b b b的距离是 l e n len len其他的均大于 l e n len len。
最开始查询需要一次, n ≤ 1000 n\le 1000 n≤1000,因此 l e n ≤ 1000 len\le 1000 len≤1000,我们只查 [ l e n + 1 / 2 , l e n ] [len + 1/2, len] [len+1/2,len]因此最多查九次,最后找点 b b b查一次,因此最多 11 11 11次。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 20010, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N], dep[N];
vector<int> ve, D[N];
int x, d;
void query() {
cout << "? " << ve.size();
for (auto t : ve) cout << ' ' << t;
cout << endl, cout << flush;
cin >> x >> d;
}
void dfs(int u, int fa) {
dep[u] = dep[fa] + 1, D[dep[u] - 1].push_back(u);
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (j == fa) continue ;
dfs(j, u);
}
}
void dfs(int u, int fa, int v) {
if (!v) {
ve.push_back(u);
return ;
}
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (j == fa) continue ;
dfs(j, u, v - 1);
}
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
int T;
cin >> T;
while (T -- ) {
cin >> n;
ve.clear();
for (int i = 1; i <= n; i ++) h[i] = -1, D[i].clear(), ve.push_back(i);
for (int i = 1; i < n; i ++) {
int u, v; cin >> u >> v;
add(u, v), add(v, u);
}
query();
dfs(x, 0);
int l = d + 1 >> 1, r = d, len = d, root = -1;
while (l < r) {
int mid = l + r + 1>> 1;
ve = D[mid];
if (!ve.size()) {
r = mid - 1;
continue ;
}
query();
if (d <= len) l = mid, root = x;
else r = mid - 1;
}
if (root == -1) ve = D[l], query(), root = x;
ve.clear();
dfs(root, 0, len);
query();
cout << "! " << root << ' ' << x << endl;
string str;
cin >> str;
}
return 0;
}