给定一个数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;
}
题目描述
给出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;
}
给定长度为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] << " ";
}
}
题目描述
有这样一块土地,它可以被划分成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;
}
题目大意:一个公司有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;
}