Codeforces Round #781 (CF1665) 简要题解
CF1665A GCD vs LCM
题意
给定 \(n\),求正整数 \(a, b, c, d\) 使得 \(gcd(a, b) = lcm(c, d)\) 且 \(a + b + c + d = n\)
输出任意一种即可
题解
显然,\(a = n - 3, b = c = d = 1\) 为一种合法方案,输出即可
代码
#include<bits/stdc++.h>
using namespace std;
int n, t;
int main()
{
cin >> t;
while(t--){
cin >> n;
cout << n - 3 << " " << 1 << " " << 1 << " " << 1 << endl;
}
return 0;
}
CF1665B Array Cloning Technique
题意
你有一个序列集合,初始时只有一个序列 \(a\),你可以进行下面两种操作
-
将序列集合中的一个序列复制一份并加入集合中
-
将两个元素交换(可以是不同序列中的元素)
问最少多少次操作可以使得集合中有一个只由一种元素构成的序列
题解
显然,最终得到的序列一定是由 \(a\) 序列中出现次数最多的元素构成的
不妨设 \(a\) 序列长度为 \(n\),其中出现次数最多的元素为 \(x\),出现了 \(k\) 次
显然贪心策略为
-
集合当前除了序列 \(a\) 还有别的序列有元素 \(x\),我们交换这个序列中的 \(x\) 到 \(a\) 上
-
集合当前只有序列 \(a\) 有元素 \(x\),我们复制序列 \(a\)
显然,这可以使操作次数最少
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, t, a[N], mx;
map <int, int> mp;
int main()
{
cin >> t;
while(t--){
cin >> n, mx = 0;
for(int i = 1; i <= n; i++){
cin >> a[i], mp[a[i]]++;
if(mp[a[i]] > mx) mx = mp[a[i]];
}
int ans = 0, cur = n - mx;
while(cur){
ans++;
if(mx >= cur) ans += cur, cur = 0;
else cur -= mx, ans += mx, mx <<= 1;
}
cout << ans << endl;
mp.clear();
}
return 0;
}
CF1665C Tree Infection
题意
给定一棵树,初始时每个节点都是健康的,现在不知道从哪里来了一种病毒,每一秒会发生如下两种事件
-
传染:若一个结点被感染,则他可以传染给它的一个兄弟结点
-
注射:可以指定一个结点,使其感染
假设你可以控制每一秒被传染和注射的点是谁,问最少几秒可以使整棵树被感染
题解
显然,对于每个结点,他的若干个子结点中我们必须注射一个(根结点也必须注射)
对于两个结点 \(u\) 和 \(v\),如果 \(u\) 的子结点个数大于 \(v\) 的子结点个数,那么我们应该先注射 \(u\) 的子节点
当每个点都有子结点被注射时,我们再注射根结点
如果此时还有结点未被感染,假设 \(u\) 中未被感染的子结点数大于 \(v\) 中未被感染的子结点数,我们应该先注射 \(u\) 的子节点
用堆模拟即可
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
const int M = 400010;
int t, n, head[N], nxt[M], to[M], st[N], top, cnt, ex;
priority_queue <int> q;
inline void addedge(int u, int v)
{
to[++cnt] = v;
nxt[cnt] = head[u], head[u] = cnt;
}
bool cmp1(int x, int y) { return x > y; }
int main()
{
cin >> t;
while(t--){
cin >> n, st[top = 1] = 1;
for(int i = 2, u; i <= n; i++)
cin >> u, addedge(u, i);
for(int i = 1; i <= n; i++){
int d = 0;
for(int j = head[i]; j; j = nxt[j]) d++;
if(d) st[++top] = d;
}
sort(st + 1, st + top + 1, cmp1);
for(int i = 1; i <= top; i++)
if(st[i] > top - i + 1) q.push(st[i] - top + i - 1);
if(!q.empty()) for(ex = 1; ex < q.top(); ex++){
int u = q.top(); q.pop();
q.push(u - 1);
if(ex >= q.top()) break;
}
cout << top + ex << endl;
for(int i = 1; i <= n; i++) head[i] = st[i] = to[i] = nxt[i] = 0;
n = top = cnt = ex = 0;
while(!q.empty()) q.pop();
}
return 0;
}
CF1665D GCD Guess
题意
交互题
有一个数 \(x\),你可以给出正整数 \(a, b\) 询问 \(gcd(x + a, x + b)\)
你需要在 \(30\) 次询问内猜出 \(x\)
其中 \(x \le 10^9\)
题解
观察发现 \(x \le 10^9\),次数在 \(30\) 次以内
显然,这里需要用到一个常识: \(\lceil \log_2 10^9 \rceil = 30\)
也就是说 \(x\) 最多有 \(30\) 个二进制位
每次询问确定一个二进制位即可
假设当前是 \(2^k\) 位,前 \(k - 1\) 位是 \(y\),那么我们询问 \(2^{k - 1} - y + 2^{k+1}, 2^{k-1} - y\),如果答案是 \(2^{k+1}\),那么 \(2^k\) 位就是 \(0\),否则就是 \(1\)
由于 \(a, b\) 需要是正整数,那么我们猜 \(x + 1\),最后再减一即可
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL t, cur, now;
LL que(LL a, LL b)
{
cout << "? " << a << " " << b << endl;
LL res = 0;
return cin >> res, res;
}
void answer(LL x) { cout << "! " << x << endl; }
int main()
{
cin >>t;
while(t--){
for(cur = 1, now = 2; now <= (1ll << 30); now <<= 1)
if(que(cur + now, cur) != now) cur += (now >> 1);
answer((1ll << 30) - cur);
}
return 0;
}
CF1665E MinimizOR
题意
给定序列 \(a\),多次询问区间最小或
\(a_i < 2^{30}\)
题解
由于 \(a_i < 2^{30}\) 那么最小或只会在最小的 \(31\) 个数字中产生
证明:
考虑数学归纳法,证明当 \(a_i < 2^k\) 时,最小或在最小的 \(k + 1\) 个数中产生
-
\(a_i < 2^1\),结论显然成立
-
\(a_i < 2^k\) 时结论成立,当 \(a_i < 2^{k+1}\) 时,设 \(2^k\) 为 \(0\) 的数有 \(t\) 个,分类讨论如下
-
\(t > 2\),则答案中 \(2^k\) 位为 \(0\)
-
\(t > k + 1\),则答案会在最小的 \(k + 1\) 个数中产生,自然也会在最小的 \(k + 2\) 个数中产生
-
\(t \le k + 1\),则答案会在最小的 \(t\) 个数中产生,自然也会在最小的 \(k + 2\) 个数中产生
-
-
\(t \le 1\) 则答案中 \(2^k\) 位为 \(1\)
-
\(t = 0\) 则答案会在最小的 \(k + 1\) 个数中产生,自然也会在最小的 \(k + 2\) 个数中产生
-
\(t = 1\) 则答案会在前 \(k-1\) 位中最小的 \(k + 1\) 个数中产生,也肯定会在最小的 \(k + 2\) 个数中产生
-
-
也就是对于每个询问找到区间最小的 \(31\) 个数后暴力判断即可,使用线段树,复杂度 \(O(n \log^2 a)\)
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
const int inf = 1 << 30;
int n, T, q, a[N];
struct node{
int mn[32];
node operator +(const node a)const{
int t1 = 0, t2 = 0;
node res;
for(int i = 0; i < 31; i++)
res.mn[i] = mn[t1] < a.mn[t2] ? mn[t1++] : a.mn[t2++];
return res;
}
void create(int x){
mn[0] = x;
for(int i = 1; i < 31; i++) mn[i] = inf;
}
int getans(){
int ans = inf;
for(int i = 1; i < 31; i++)
for(int j = 0; j < i; j++) ans = min(ans, mn[i] | mn[j]);
return ans;
}
}t[N << 2];
void build(int l, int r, int x)
{
if(l == r) return t[x].create(a[l]), void();
int mid = (l + r) >> 1;
build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
t[x] = t[x << 1] + t[x << 1 | 1];
}
node que(int l, int r, int x, int L, int R)
{
if(L <= l && r <= R) return t[x];
node res; res.create(inf);
int mid = (l + r) >> 1;
if(L <= mid) res = res + que(l, mid, x << 1, L, R);
if(R > mid) res = res + que(mid + 1, r, x << 1 | 1, L, R);
return res;
}
int main()
{
cin >> T;
while(T--){
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
build(1, n, 1);
cin >> q;
while(q--){
int l, r;
cin >> l >> r;
node cur = que(1, n, 1, l, r);
cout << cur.getans() << endl;
}
}
return 0;
}