这是一道好题。考察了扩展域并查集,总的来说,对我的启发如下:
1、扩展域并查集是一种能在单个集合中记录多种关系的方法。常规的并查集在一个集合内只有一种集合,但是扩展域可以有多个,具体是通过+k*n(k表示要表达的关系数量),比如i与j表示一种关系,i与j+n表示另一种关系。
2、扩展域并查集需要满足一种特定的关系——异或。即两个或多个关系经过传递后,最终形成的新关系需要等于前面若干关系的异或结果。
3、并查集是可以使用离散化的,离散化不是随便用的。注意实现的一些细节。
4、其他更多的做法——种类、带权并查集可以提供更多思路。
#include<bits/stdc++.h>
using namespace std;
const int N=5e3+10;
int fa[10*N],book[10*N];
void init(int n)
{
for(int i=0;i<=2*n;i++)
fa[i]=i;
}
int find(int x)
{
if(x==fa[x])
return fa[x];
return fa[x]=find(fa[x]);
}
void Union(int a,int b)
{
int x=find(a),y=find(b);
if(x!=y)
fa[x]=y;
}
struct Node{
int a;
int b;
}node[5*N];
string S[5*N];
int main(void)
{
int n,m;cin>>n>>m;
int cnt=0;
for(int i=1;i<=m;i++){
cin>>node[i].a>>node[i].b>>S[i];
node[i].a--;
book[cnt++]=node[i].a;
book[cnt++]=node[i].b;
}
sort(book,book+cnt);
cnt=unique(book,book+cnt)-book;
init(cnt);
for(int i=1;i<=m;i++){
int idxa=lower_bound(book,book+cnt,node[i].a)-book;
int idxb=lower_bound(book,book+cnt,node[i].b)-book;
node[i].a=idxa;
node[i].b=idxb;
}
bool ok=true;
int a,b;string str;
for(int i=1;i<=m;i++){
a=node[i].a,b=node[i].b;
str=S[i];
if(str=="odd"){
if(find(a)==find(b)){
cout<<i-1<<endl;
ok=false;
break;
}
Union(a,b+cnt);
Union(b,a+cnt);
}else{
if(find(a+cnt)==find(b)){
cout<<i-1<<endl;
ok=false;
break;
}
Union(a,b);
Union(a+cnt,b+cnt);
}
}
if(ok)
cout<<m<<endl;
}
和前一道题类似,但是思维更简单。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int fa[N];
int n;
struct Node{
Node(){
}
Node(int u,int v,int d){
x=u;
y=v;
e=d;
}
int x,y,e;
};
struct Node a[N];
vector<int> book;
void init(int n)
{
for(int i=0;i<=n;i++)
fa[i]=i;
}
int Find(int x)
{
if(x==fa[x])
return fa[x];
return fa[x]=Find(fa[x]);
}
bool cmp(struct Node A,struct Node B)
{
return A.e>B.e;
}
int main(void)
{
int T;cin>>T;
while(T--){
int cnt=0;
bool ok=false;
cin>>n;
for(int i=0;i<n;i++){
int u,v,d;
cin>>u>>v>>d;
a[i]={u,v,d};
book.push_back(u);
book.push_back(v);
}
sort(book.begin(),book.end());
book.erase(unique(book.begin(),book.end()),book.end());
init(book.size());
for(int i=0;i<n;i++){
a[i].x=lower_bound(book.begin(),book.end(),a[i].x)-book.begin();
a[i].y=lower_bound(book.begin(),book.end(),a[i].y)-book.begin();
}
sort(a,a+n,cmp);
for(int i=0;i<n;i++){
int u=Find(a[i].x),v=Find(a[i].y);
if(a[i].e){
if(u!=v)
fa[u]=v;
}
else if(u==v){
printf("NO\n");
ok=true;
break;
}
}
if(!ok) cout<<"YES"<<endl;
book.clear();
}
}
P4552 [Poetize6] IncDec Sequence
对差分数组的考察,比较巧妙,可以记一下。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N];
long long sum[N]={0};
int main(void)
{
int n;
cin>>n;
a[0]=0;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) sum[i]=a[i]-a[i-1];
long long x=0,y=0;
for(int i=2;i<=n;i++){
if(sum[i]>0) x+=sum[i];
else y-=sum[i];
}
cout<<max(x,y)<<endl;
cout<<abs(x-y)+1;
}
对前缀和和二分的考察。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int w[N],v[N];
typedef struct Node{
Node(){
}
Node(int a,int b){
l=a;
r=b;
}
int l;
int r;
}node;
node range[N];
int n,m;
long long s;
long long ans=1e12;
int sumw[N],sumv[N];
int check(long long mid)
{
memset(sumw,0,sizeof(sumw));
memset(sumv,0,sizeof(sumv));
for(int i=1;i<=n;i++){
if(w[i]>=mid){
sumw[i]=sumw[i-1]+1;
sumv[i]=sumv[i-1]+v[i];
}else{
sumw[i]=sumw[i-1];
sumv[i]=sumv[i-1];
}
}
long long y=0,Sum=0;
for(int i=1;i<=m;i++){
int l=range[i].l,r=range[i].r;
Sum=(sumw[r]-sumw[l-1])*(sumv[r]-sumv[l-1]);
y+=Sum;
}
ans=min(ans,abs(y-s));
if(y>s) return -1;
else if(y==s) return 0;
else return 1;
}
int main(void)
{
cin>>n>>m>>s;
for(int i=1;i<=n;i++){
cin>>w[i]>>v[i];
}
for(int i=1;i<=m;i++){
int a,b;
cin>>a>>b;
range[i]={a,b};
}
bool ok=false;
int l=1,r=1e6;
while(l<r){
int mid=l+r>>1;
int flag=check(mid);
if(flag==1)
r=mid;
else if(flag==-1)
l=mid+1;
else{
ok=true;
cout<<0;
break;
}
}
if(!ok) cout<<ans;
}
总结。
差分:
S[i]=a[i]-a[i-1]
前缀和:
Sum[i]=Sum[i-1]+a[i]
前缀和的差分是原数组,差分的前缀和是原数组。差分通常用于对区间内整体的+或-,时间复杂度为O(1),前缀和通常用于对区间求和,时间为O(1)。
并查集:
通常描述传递关系,注意传递关系与内部无关,比如[1,3]为odd,[3,6]为odd,只能说明[1,6]为even,但不能说明[2,4]是什么。