完全不会 G 和 Ex,这些套路还是要积累一下的。
F.Merge Set
题目描述:
给定 \(n\) 个集合,每次可以合并两个有交的集合,问最少多少次合并可以让 \(1\) 和 \(m\) 位于同一个集合中。
题目分析:
一开始将题读成了将 \([1,m]\) 位于同一个集合中,然后就感觉这个题相当不可做。
因为集合的元素之间没有任何关系,而如果直接考虑集合之间怎么弄弄的话肯定是不行的,而题目要求每次合并两个有交的集合,那么不妨转化为图论。
也就是将所有有交的集合连在一起,但是这样的复杂度就炸天了,而且也没法统计答案。
所以就考虑对于每一个数建一个点,然后对于每一个集合向它包含的数连边,这样的话就完美解决了上述问题。
这样其实就直接跑一个从数 \(1\) 到数 \(m\) 的最短路然后稍微处理一下就是答案。
因为最小情况下我们每次合并一个区间,肯定都是要用这个区间里新加入的数扩展新的区间,所以直接求没问题。
代码:
点击查看代码
#include<bits/stdc++.h>
#define PII pair<int,int>
using namespace std;
const int N = 2e6+5;
struct edge{
int nxt,to,val;
edge(){}
edge(int _nxt,int _to,int _val){
nxt = _nxt,to = _to,val = _val;
}
}e[2 * N];
int head[N],dis[N],cnt;
bool vis[N];
void add_edge(int from,int to,int val){
e[++cnt] = edge(head[from],to,val);
head[from] = cnt;
}
int main(){
int n,m;scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++){
int a;scanf("%d",&a);
for(int j=1; j<=a; j++){
int k;scanf("%d",&k);
add_edge(k,m+i,1);
add_edge(m+i,k,1);
}
}
memset(dis,0x3f,sizeof(dis));
priority_queue<PII> q;q.push({0,1});
dis[1] = 0;
while(!q.empty()){
int now = q.top().second;q.pop();
if(vis[now]) continue;
vis[now] = true;
for(int i=head[now]; i; i=e[i].nxt){
int to = e[i].to;
if(dis[to] > dis[now] + e[i].val){
dis[to] = dis[now] + e[i].val;
q.push({-dis[to],to});
}
}
}
int ans = dis[m] / 2 - 1;
if(ans > 10000000) printf("%d\n",-1);
else printf("%d\n",ans);
return 0;
}
G.Sort from 1 to 4
题目描述:
给定长度为 \(n\) 的仅包含 \([1,4]\) 的序列,每次可以交换两个数,问最少多少次交换后序列单调不降。
题目分析:
考虑将序列排序肯定就是形如 111...222...333...444...
所以显然可以对于每一个位置的数找到其对应换成的数,若数 \(i\) 要变为数 \(j\) 则记为 \((i,j)\)。
如果存在 \((i,j)\) 也存在 \((j,i)\) 那么直接这两个换就好了,考虑扩展其实就是:如果要成功换到肯定是这些置换构成一个环,所以直接对于二元环、三元环、四元环都统计一下就好了,注意要按照这种顺序统计,因为这样能保证答案尽可能小。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e6+5;
const int INF = 1e9+5;
int ans = 0,lm,a[N],b[N],cnt[10][10];
bool vis[N];
void dfs(int now){
if(now == lm+1){
int tmp = INF;
for(int i=1; i<lm; i++) tmp = min(tmp,cnt[a[i]][a[i+1]]);
tmp = min(tmp,cnt[a[lm]][a[1]]);
ans += tmp * (lm - 1);
for(int i=1; i<lm; i++) cnt[a[i]][a[i+1]] -= tmp;
cnt[a[lm]][a[1]] -= tmp;
return;
}
for(int i=1; i<=4; i++){
if(vis[i]) continue;
a[now] = i;vis[i] = true;
dfs(now+1);
vis[i] = false;
}
}
int main(){
int n;scanf("%d",&n);
for(int i=1; i<=n; i++) scanf("%d",&a[i]),b[i] = a[i];
sort(b+1,b+n+1);
for(int i=1; i<=n; i++) cnt[a[i]][b[i]]++;
lm = 2;dfs(1);
lm = 3;dfs(1);
lm = 4;dfs(1);
printf("%d\n",ans);
return 0;
}
Ex.Ball Collector
题目分析:
给定一棵 \(n\) 个点的树,每个点有两个小球,权值分别为 \(a_i,b_i\),经过这个点就可以选择两个小球中的一个。对于 \(v \ge 2\),都要求出 \(1\) 到 \(v\) 路径上选出来的小球中不同整数个数的最大值。
题目分析:
两个数只能选一个,这就很图论。
也就是若路径中存在点 \(i\) 则建边:\((a_i,b_i)\)。
这样就能发现对于每一个连通块,若为树则贡献为点数-1,否则贡献为点数。
因为我们要高效维护这个信息,并且我们只关注连通性问题,所以可以考虑直接使用并查集维护。
那么我们可以直接 dfs 这棵树,然后每一次回溯都是相当于撤销最后一次操作,用可撤销并查集就可以了。
可撤销并查集不可路径压缩
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
struct node{
int x,y,fa,fx,fy,val,sz;
}st[N];
struct edge{
int nxt,to;
edge(){}
edge(int _nxt,int _to){
nxt = _nxt,to = _to;
}
}e[2*N];
int cnt,top,tmp,ans[N],head[N],fa[N],flag[N],sz[N],a[N],b[N],tot[N];
void add_edge(int from,int to){
e[++cnt] = edge(head[from],to);
head[from] = cnt;
}
int find(int x){
if(fa[x] == x) return x;
return find(fa[x]);
}
void merge(int x,int y){
x = find(x),y = find(y);
if(sz[x] > sz[y]) swap(x,y);
if(x == y){
st[++top] = {x,x,fa[x],flag[x],flag[x],flag[x],0};
tmp += flag[x];
flag[x] = false;
return;
}
int val = 0;
if(flag[x] && flag[y]) val = 1;
else val = flag[x] ^ flag[y];
st[++top] = {x,y,fa[x],flag[x],flag[y],val,sz[x]};
tmp += val; sz[y] += sz[x];
fa[x] = y;
if(!flag[x]) flag[y] = false;
}
void del(){
int x = st[top].x,y = st[top].y;
tmp -= st[top].val;
sz[y] -= st[top].sz;
fa[x] = st[top].fa;
flag[x] = st[top].fx;
flag[y] = st[top].fy;
--top;
}
void dfs(int now,int fath){
merge(a[now],b[now]);
ans[now] = tmp;
for(int i=head[now]; i; i=e[i].nxt){
int to = e[i].to;
if(to == fath) continue;
dfs(to,now);
}
del();
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int n;scanf("%d",&n);
for(int i=1; i<=n; i++) scanf("%d%d",&a[i],&b[i]);
for(int i=1; i<n; i++){
int from,to;scanf("%d%d",&from,&to);
add_edge(from,to);add_edge(to,from);
}
for(int i=1; i<=n; i++) fa[i] = i,sz[i] = 1,flag[i] = true;
dfs(1,0);
for(int i=2; i<=n; i++) printf("%d ",ans[i]);
return 0;
}