F. Equate Multisets
题意
给出两个 multiset
A
A
A 和
B
B
B,判断能否将集合
B
B
B 经过若干次操作变为集合
A
A
A?如果可以,最少操作多少次?
- 操作1:选择一个元素 x x x,将其变为 x ∗ 2 x*2 x∗2;
- 操作2:选择一个元素 x x x,将其变为 ⌊ x 2 ⌋ \lfloor \frac{x}{2} \rfloor ⌊2x⌋。
思路
假设集合 A 中的一个偶数元素为 x,其对应在集合 B 中的元素为 x/2,那么元素 x 除以2 就相当于元素 x/2 乘以 2,仍然是相互匹配的。
那么操作就可以转化为:
- 操作1:选择 A 中的一个偶数元素 x x x,将其变为 x / 2 x/2 x/2;
- 操作2:选择 B 中的一个元素 x x x,将其变为 ⌊ x 2 ⌋ \lfloor \frac{x}{2} \rfloor ⌊2x⌋。
问,能否将两个集合变为相同?
因为每次操作都会使元素变小,所以每次取出两个集合中的最大元素,判断这两个元素是否相同。
- 如果相同,将这两个元素从集合中删掉;
- 如果不同,把较大的元素除以2后重新放入,操作次数++。(如果是 A 集合元素较大要除2,要保证其为偶数;否则其一定不会有对应元素,直接 -1)
- 最终判断集合是否为空。
注意
其实只要判断能否将两个集合变为相同,只需要判断是否 A 集合中所有元素的奇因子是否匹配就可以了。
A 集合中元素的偶因子可以由乘2得到,关键在于奇因子如何得到:只能由 B 集合元素除 2 得到。所以把 AB 集合的所有元素都变为奇因子,将 B 集合奇因子不断除 2 寻找匹配。
Code
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];
signed main(){
Ios;
cin >> T;
while(T--)
{
cin >> n;
priority_queue<int> que1, que2;
for(int i=1;i<=n;i++)
{
int x; cin >> x;
while(x % 2 == 0) x /= 2;
que1.push(x);
}
for(int i=1;i<=n;i++)
{
int x; cin >> x;
que2.push(x);
}
int flag = 0;
while(que1.size())
{
int x = que1.top(); que1.pop();
int y = que2.top(); que2.pop();
if(x == y) continue;
y /= 2;
if(y == 0){
flag = 1;
break;
}
que1.push(x), que2.push(y);
}
if(flag) cout << "NO\n";
else cout << "YES\n";
}
return 0;
}
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define endl '\n'
const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];
signed main(){
Ios;
priority_queue<int> que1, que2;
cin >> n;
for(int i=1;i<=n;i++)
{
int x; cin >> x;
que1.push(x);
}
for(int i=1;i<=n;i++)
{
int x; cin >> x;
que2.push(x);
}
int cnt = 0, flag = 0;
while(que1.size())
{
int x = que1.top(); que1.pop();
int y = que2.top(); que2.pop();
if(x == y) continue;
if(x > y) x /= 2, cnt ++;
else if(x < y)
{
if(y % 2 == 0) y /= 2, cnt ++;
else{
flag = 1;
break;
}
}
que1.push(x);
que2.push(y);
}
if(flag){
cout << -1 << endl;
}
else cout << cnt << endl;
return 0;
}
G. Passable Paths
题意
对于 n 节点的一棵树,一共 q 次询问。
每次询问,给定 m 个数的集合,判断集合中的所有元素是否在同一条链中?
1 ≤ n ≤ 2 ⋅ 1 0 5 , 1 ≤ m ≤ n , 1 ≤ p i ≤ n 1 \le n \le 2 \cdot 10^5,\ 1 \le m \le n, \ 1 \le p_i \le n 1≤n≤2⋅105, 1≤m≤n, 1≤pi≤n
easy version:
1
≤
q
≤
5
1 \le q \le 5
1≤q≤5
hard version:
1
≤
q
≤
1
0
5
1 \le q \le 10^5
1≤q≤105
思路
easy version:
直接 dfs 搜整棵树判断即可,关键在于从哪个点开始搜。
从所有元素所在链的端点开始,即任意选定根节点确定每个节点深度后,深度最大的元素。
搜的时候记录该路径中在集合中的节点个数,如果在当前路径中集合节点个数为集合大小,说明集合中的所有节点都在当前路径中,那么所有元素在同一条链。
hard version:
先假设确实存在这条链,再判断是否所有点在这条链中。
如果所有元素确实在同一条链上的话,那么其一个端点 l
是深度最大的元素(已确定),另一个端点 r
是端点 l 所在路径之外的深度最大的元素。
- 遍历所有节点,对于节点 x,如果 lca(x, l) 不为 x 的话,说明其和端点 l 不在同一路径上,取深度最大的那个作为端点 r。
- 如果说 r 未找到,那么说明所有元素都在同一路径上,直接 YES;
两个端点确定了,lca(l, r) 到这两个端点的两端路径相连接便是整条链,判断其他点是否在这条链上。
- 对于一个节点 x,如果其和一个端点的 lca 为 x,和另一个端点的 lca 为 lca(l, r) 的话,就说明这个节点在该链上。
Code
easy:
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];
vector<int> e[N];
int flag, ans;
int dep[N];
int cnt, pre[N];
int k;
void dfs1(int x, int fa)
{
for(int tx : e[x])
{
if(tx == fa) continue;
dep[tx] = dep[x] + 1;
dfs1(tx, x);
}
}
void dfs(int x, int fa, int cnt) //第三个变量始终表示一条链上满足点的个数
{
if(cnt == k){
ans = 1;
return;
}
for(int tx : e[x])
{
if(tx == fa) continue;
int t = 0;
if(mp[tx]) t = 1;
dfs(tx, x, cnt+t);
}
}
signed main(){
Ios;
cin >> n;
for(int i=1;i<n;i++)
{
int x, y; cin >> x >> y;
e[x].push_back(y);
e[y].push_back(x);
}
dfs1(1, 0);
cin >> m;
while(m--)
{
cin >> k;
mp.clear();
int mina = -1, root = 1;
for(int i=1;i<=k;i++)
{
int x; cin >> x;
if(dep[x] > mina){
mina = dep[x];
root = x;
}
mp[x] = 1;
}
ans = 0;
dfs(root, 0, 1);
if(ans) cout << "YES\n";
else cout << "NO\n";
}
return 0;
}
hard:
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];
int f[N][30], k, dep[N];
vector<int> e[N];
void bfs()
{
dep[0] = 0, dep[1] = 1;
queue<int> que;
que.push(1);
while(que.size())
{
int x = que.front(); que.pop();
for(int tx : e[x])
{
if(tx == f[x][0]) continue;
dep[tx] = dep[x] + 1;
que.push(tx);
f[tx][0] = x;
for(int i=1;i<=k;i++)
f[tx][i] = f[f[tx][i-1]][i-1];
}
}
}
int lca(int x, int y)
{
if(dep[x] < dep[y]) swap(x, y);
for(int i=k;i>=0;i--)
{
if(dep[f[x][i]] >= dep[y]) x = f[x][i];
}
if(x == y) return x;
for(int i=k;i>=0;i--)
{
if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
}
return f[x][0];
}
signed main(){
Ios;
cin >> n;
k = log(n)/log(2);
for(int i=1;i<n;i++)
{
int x, y; cin >> x >> y;
e[x].push_back(y);
e[y].push_back(x);
}
bfs();
cin >> m;
while(m--)
{
int q; cin >> q;
int l = 0, r = 0;
int maxa = 0;
for(int i=1;i<=q;i++){
cin >> a[i];
if(dep[a[i]] > maxa){
maxa = dep[a[i]];
l = a[i];
}
}
maxa = 0;
for(int i=1;i<=q;i++)
{
int x = a[i];
if(l == x) continue;
if(lca(l, x) != x){
if(dep[x] > maxa){
maxa = dep[x];
r = x;
}
}
}
if(!r){
cout << "YES\n";
continue;
}
int mid = lca(l, r);
int flag = 0;
for(int i=1;i<=q;i++)
{
int x = a[i];
if(x == l || x == r) continue;
int m1 = lca(x, l), m2 = lca(x, r);
if(m1 == x && m2 == mid || m2 == x && m1 == mid) continue;
flag = 1;
}
if(flag) cout << "NO\n";
else cout << "YES\n";
}
return 0;
}
E. Split Into Two Sets
题意
给定 n 个二元组,每个二元组有两个元素{x, y}。
问,是否能将所有二元组分成 A,B 两部分,每个二元组必须属于其中一个。
每个部分要满足:其中所有元素不得有重复。
2
≤
n
≤
2
⋅
1
0
5
,
n
为
偶
数
2 \le n \le 2 \cdot 10^5, \ n为偶数
2≤n≤2⋅105, n为偶数
1
≤
a
i
,
b
i
≤
n
1 \le a_i, b_i \le n
1≤ai,bi≤n
思路
方法1:
场上想的做法是这样的,对于一个二元组 {x, y} 来说,如果 x 出现在另一个二元组,y 也出现在另一个二元组,那么两个二元组一定要放在同一部分里,才能保证二元组{x, y} 能够放到另一部分,才能满足要求。
所以将这样的两两二元组用并查集合并起来,最终判断是否一个集合中存在相同的元素。
方法2:
染色判定二分图
将所有的二元组分成两部分实现不冲突,其实就是相当于,将每个二元组看成一个节点,将不能放在一起的二元组两两连边,判断是否能构成二分图。
对于一个元素出现在两个二元组中,那么这两个二元组就不能放在同一部分,那么就将这两个节点连边。
然后染色判定二分图即可。
Code
方法1:
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
unordered_map<int,int> mp1, mp2;
const int N = 200010, mod = 1e9+7;
int T, n, m;
PII a[N];
int pre[N];
vector<int> v[N];
int find(int x){
if(pre[x] != x) pre[x] = find(pre[x]);
return pre[x];
}
signed main(){
Ios;
cin >> T;
while(T--)
{
cin >> n;
mp1.clear();
mp2.clear();
int flag = 0;
for(int i=1;i<=n;i++)
{
cin >> a[i].fi >> a[i].se;
if(a[i].fi == a[i].se) flag = 1;
if(mp1[a[i].fi]){
if(mp2[a[i].fi]) flag = 1;
else mp2[a[i].fi] = i;
}else mp1[a[i].fi] = i;
if(mp1[a[i].se]){
if(mp2[a[i].se]) flag = 1;
else mp2[a[i].se] = i;
}else mp1[a[i].se] = i;
}
for(int i=1;i<=n;i++) pre[i] = i;
for(int i=1;i<=n;i++)
{
if(mp2[a[i].fi] && mp2[a[i].se])
{
int x, y;
if(i==mp1[a[i].fi]) x = mp2[a[i].fi];
else x = mp1[a[i].fi];
if(i==mp1[a[i].se]) y = mp2[a[i].se];
else y = mp1[a[i].se];
pre[find(x)] = find(y);
}
}
for(int i=1;i<=n;i++) v[i].clear();
for(int i=1;i<=n;i++) v[find(i)].push_back(i);
for(int i=1;i<=n;i++)
{
vector<int> vt;
for(int j : v[i]) vt.push_back(a[j].fi), vt.push_back(a[j].se);
sort(vt.begin(), vt.end());
for(int j=1;j<vt.size();j++) if(vt[j] == vt[j-1]) flag = 1;
}
if(flag) cout << "NO\n";
else cout << "YES\n";
}
return 0;
}
方法2:
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];
vector<int> v[N], e[N];
int col[N], flag;
void dfs(int x)
{
for(int tx : e[x])
{
if(col[tx] == col[x]){
flag = 1;
return;
}
if(col[tx] != -1) continue;
col[tx] = col[x] ^ 1;
dfs(tx);
}
}
signed main(){
Ios;
cin >> T;
while(T--)
{
cin >> n;
for(int i=1;i<=n;i++) v[i].clear(), e[i].clear();
for(int i=1;i<=n;i++)
{
int x, y; cin >> x >> y;
v[x].push_back(i);
v[y].push_back(i);
}
for(int i=1;i<=n;i++)
{
if(v[i].size() != 2) continue;
e[v[i][0]].push_back(v[i][1]);
e[v[i][1]].push_back(v[i][0]);
}
flag = 0;
for(int i=1;i<=n;i++) col[i] = -1;
for(int i=1;i<=n;i++)
{
if(col[i] == -1) col[i] = 0, dfs(i);
}
if(flag) cout << "NO\n";
else cout << "YES\n";
}
return 0;
}
C 题因为 unordered_map 被 hack 了。。
第一次补完一场 cf 的所有题!