CUGBACM21级暑假专题训练#2 并查集

B - Equals

        给定一个数N,一个长度为N,元素为乱序且不重复的1~N的值,给出M个序对(xi,yi),你可以任意次数交换给定序对对应的数组元素,请求出通过交换最多有多少数组值于下标值相等。

题解:这些序对构成多个联通块,当存在联通块中的下标于其下标对应的数组值对应时,即可。由此通过并查集求解。

#include <iostream>
#include<cmath>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;

const int N=1e5+5;

int fa[N];

int find(int u)
{
	if(fa[u]!=u)
{
		fa[u]=find(fa[u]);
}
	return fa[u];
}

void join(int u,int v)
{
	int fu;
	fu=find(u);
	int fv;
	fv=find(v);
	if(fu!=fv)
	{
		fa[fu]=fv;
	}
}
int s[N];

int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	
	{
		cin>>s[i];
	}
	
	for(int i=0;i<=n;i++)
	{
		fa[i]=i;
	}
	int x,y;
	while(m--)
	{
		cin>>x>>y;
		join(x,y);
	}
	int ans=0;
	for(int i=1;i<=n;i++)
	{
		if(find(s[i])==find(i))
		{
			ans++;
		}
	}
	cout<<ans<<endl;
}

F - String Reconstruction

 

题目描述

        给出n个字符串,各字符串出现的次数k,每次出现的位置。求一个满足上述字符串出现情况的字典序最小的字符串。

题解:由于某个出现的字符串可能已经包含在另一个字符串中,为了降低时间复杂度,我们需要跳过这种情况。使用并查集,当前位置为pos,它的父下标是fa[pos],也就是,pos到fa[pos]的字符之前已经被更新,当fa[pos]>pos+len(字符)-1时,直接跳过即可。否则更新当前一个字符,则fa[pos]的父下标就变成了P=fa[pos]+1,然后让,pos=fa[P],即又重复上述步骤。

#include <iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const long long N=2000002;

int fa[N];
int find(int u)
{
	if(fa[u]!=u)
	fa[u]=find(fa[u]);
	return fa[u];
}


int main() 
{
	ios::sync_with_stdio(0);
	
	long long n;
	cin>>n;
//	char s[N];
	string s;
	//memset(s,'#',sizeof s);
	long long maxr=0;
	for(int i=0;i<N;i++)
	{
		fa[i]=i;
		s+='a';
	}
	//long long fu,fv;

	long long m,len,pos;
	
	

	while(n--)
	{
		string s1;
		cin>>s1;
		len=s1.length();
		cin>>m;
		while(m--)
		{
		cin>>pos;	
		pos--;
		maxr=max(maxr,pos+len);
		for(int i=pos;i<pos+len;)
		{
		int	fu=find(i);
			if(fu>pos+len-1)
			{
				break;
			}
			s[fu]=s1[fu-pos];
			fa[fu]=fu+1;
			i=fa[fu];
		}
		}
	}
	

	
	for(long long i=0;i<maxr;i++)
	{
	
		
			cout<<s[i];
		
	}
	return 0;
}

G - Minimum Array

        给定长度为N的的整数数组a,b,b的元素顺序可以随意调节,要求长度为N的数组c,

c[i]=(a[i]+b[i])%N,要使得c的字典序最小。

题解:要使得c[i]尽可能小,则a[i]+b[i]->n;即b[i]->n-a[i]且b[i]>=n-a[i];如果b[i]都小于n-a[i],则b[i]要最小,因为当b[i]<n-a[i],c[i]=(a[i]+b[i])%N=a[i]+b[i].因此,使用lower_bound()函数快捷。

#include<iostream>
#include<algorithm>
#include<vector>
#include<set>
using namespace std;
vector<int > v;
int s[100];
multiset<int> b;

const int N = 2e5 + 5;
int a[N];

int c[N];
int main()
{
	
	int n;
	cin >> n;
	for (int i = 0; i < n; i++)
	{
		cin >> a[i];

	}
	int tep;
	for (int i = 0; i < n; i++)
	{
		cin >> tep;
		b.insert(tep);

	}

	multiset<int>::iterator it;
	for (int i = 0; i < n; i++)
	{
		it = b.lower_bound(n - a[i]);

		if (it == b.end())
		{
			c[i] = (a[i] + *b.begin()) % n;
			b.erase(b.begin());
		}
		else
		{
			c[i] = (a[i] + *it) % n;
			b.erase(it);
		}
	}
	for (int i = 0; i < n; i++)
	{
		cout << c[i] << " ";
	}
	
}

I - 降水

 

题目描述

有这样一块土地,它可以被划分成N×M个正方形小块,每块面积是一平方英寸,第i行第j列的小块可以表示成P(i,j)。这块土地高低不平,每一小块地P(i,j)都有自己的高度H(i,j)(单位是英寸)。
一场倾盆大雨后,这块地由于地势高低不同,许多低洼地方都积存了不少降水。假如你已经知道这块土地的详细信息,你能求出它最多能积存多少立方英寸的降水么?

输入格式

输入文件第一行有两个数,N,M(1<=N, M <=100),表示土地的规模是N×M平方英寸。
以下有N行,每行有M个整数,表示每块地的高低(每个整数在[1,10000]内,以英寸为单位)。

输出格式

输出文件只有一行,一个数,表示土地中最多能积存多少立方英寸的水。

样例输入

3 6
3 3 4 4 4 2
3 1 3 2 1 4
7 3 1 6 4 1

样例输出

5

题解:

我们考虑一层一层地向图中加水
这样的话当我们考虑到v这个高度,所有小于v的高度我们都已经考虑过了
所以我们只考虑当前有多少个位置可以加入一层水
对于边界上的节点,我们需要排除掉,所以我们用0表示已经删除掉的集合,所以把需要删除的节点和0连接起来,否则的话我们需要维护高度相同的联通块的连通性,就把当前块和周围已经访问过的块连接起来就行了

所以用并查集维护siz

#include<iostream>
#include<algorithm>
#include<vector>
#include<set>
using namespace std;

const int N = 10000 + 10;

int fa[N],siz[N];//父节点和与该节点相连的节点数
bool vis[105][105];//是否访问过,未访问则尚不能把它加入联通块,因为该点比较“高”,水还未加入该高度
vector< pair<int, int> > vt[N];
int n, m;
bool checkin(int x, int y)
{
	if (x<1 || y<1 || x>n || y>m)return 0;
	return 1;
}
int mx[5] = { 0,0,-1,1 };
int my[5] = { -1,1,0,0 };

int getpos(int x, int y)
{
	if (checkin(x, y))
	{
		return (x - 1) * m + y;
	}
	else
	{
		return 0;
	}
};

void init()
{
	fa[0] = siz[0] = 0;
	for (int i = 1; i < N; i++)
	{
		fa[i] = i;
		siz[i] = 1;
	}
}
int find(int u)
{
	if (u != fa[u])
		fa[u] = find(fa[u]);
	return fa[u];
}
void join(int x, int y)
{
	int fx, fy;
	fx = find(x);
	fy = find(y);
	if (fx == fy)
		return;
		fa[fx] = fy;
		siz[fy] += siz[fx];
	
}

int main()
{
	cin >> n >> m;
	int vl;
	pair<int, int >p;
	init();
	for (int i = 1; i <= n; i++)

	{
		for (int j = 1; j <= m; j++)
		{
			cin >> vl;
			p = { i,j };
			vt[vl].push_back(p);
		}
	}
	int ct = 0, ans = 0;
	int up = n * m;
	int i,j;
	int tx,ty,px,py;
	for ( int i = 1; i < N; i++)
	{
		
		if (siz[find(0)] == up)break;//联通点总数等于所有点总数,则跳出
		for (int j = 0; j < vt[i].size(); j++)
		{
		
			tx = vt[i][j].first;
		
			ty = vt[i][j].second;

			vis[tx][ty] = 1;
			ct++;
			for (int k = 0; k < 4; k++)
			{
				px = tx + mx[k];
				py = ty + my[k];
				if (!checkin(px, py) || vis[px][py])
				{
					join(getpos(tx, ty), getpos(px, py));
				}
			}
		}

		ans += ct - siz[find(0)];//ct-siz[find(0)]是加水数-满溢处

	}
	cout << ans << endl;
}

 

J - Restructuring Company

题目大意:一个公司有n个人n个部门,要合并部门。有两种方式合并,一种是序号x和y两个部门直接合并,x和y是部门中的某个成员;二是序号x到y的一系列部门合并。
三是对x和y两个成员查询是否在同一个部门。

题解:并查集问题。难点在于第二种合并方式,区间合并。
解决方式是,维护一个next数组,保存当前部门的下一个部门。对每次的区间合并都维护一下next数组,这样每次的区间合并可以跳过大部分已经被合并的部门,是一种动态的更新过程。

注意这题用scanf,cin大概率超时。

#include <iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const long long N = 200000 + 55;





const int Nt = 200000 + 55;

int fa[N],tnext[N];
int find(int x)
{
	if (fa[x] == x) {
		return x;
	}
	return fa[x] = find(fa[x]);
}

void join(int x, int y)
{
	int xx = find(x), yy = find(y);
	if (xx != yy) {
		fa[xx] = yy;
	}
}


int main()
{

	long long n, q;
	cin >> n >> q;
	for (int i = 0; i <= n + 2; i++)

	{
		fa[i] = i;
		tnext[i] = i + 1;
	}
	int ty, x, y;
	//int fu, fv;
	for (int i = 0; i < q; i++)
	{
		//cin >> ty >> x >> y;
		scanf("%d%d%d", &ty, &x, &y);
		if (ty == 1)
		{
			join(x, y);
		}
		else if (ty == 2)
		{
			
			int t;
		
			for (int j = x+1; j <= y; j=t)
			{
				join(j - 1, j);
				t = tnext[j];
				tnext[j] = tnext[y];
			}

		}
		else if (ty == 3)
		{
			if (find(x) == find(y))

			{
				cout << "YES" << endl;
			}
			else
			{
				cout << "NO" << endl;
			}
		}

	}


	return 0;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

linalw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值