【题解】Atcoder ABC302 F,G,Ex

完全不会 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;
}
posted @ 2023-05-21 16:39  linyihdfj  阅读(129)  评论(0编辑  收藏  举报