题意:n个人互不认识,经过一次介绍,两人认识,且a对b认识,b对c认识,则a对c认识,每个人都具有技能1或2,从n个人中选择三个人参加比赛,且至少有两个具有技能2,且三个人互补认识。
思路:利用并查集,将这n个人分成三种连通快中,每次从不同块中去取不满足的情况,存在四种可能,每次用前一次的减去不满足的情况个数,得到最终ans。
主要是看了这个理解的:
https://blog.youkuaiyun.com/njuptACMcxk/article/details/107641773
在写的过程中注意防止负数的出现,还有进行下一次例子的时候注意某些变量的初始化。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9+7;
const int N = 1e5+10;
int tcase,n,i,n1,n2,k,ans,j=1,u,v,pu,pv,x;
//int sto[N];
int p1[N],p2[N],p[N];
int Find(int xx)//并查集find函数
{
if(p[xx]!=xx)
p[xx]=Find(p[xx]);
return p[xx];
}
int main(){
scanf("%d",&tcase);
while(tcase--){
scanf("%d",&n);
n1 = n2 = 0;
for(i=1;i<=n;i++){//输入
scanf("%d",&x); p[i]=i;//记录各连通快
if(x==1){
n1++;p1[i]=1;p2[i]=0;
}
else{
n2++;p2[i]=1;p1[i]=0;/*p1[i]表示以i为根节点的连通块中,
权值为1的点的数量,p2[i]同理*/
}
}
//首先计算都不认识的时候
ans = ((ll)n2*(n2-1)*(n2-2)/6%mod + (ll)n1*n2%mod*(n2-1)/2%mod)%mod;//都不认识时的组合个数,利用组合公式
cout<<ans<<endl;
for(i=1;i<n;i++){//输入
k=0;
scanf("%d%d",&u,&v);
int pu=Find(u), pv=Find(v);//寻找u,v的根节点
//输入u,v之后不满足的情况包含四种
//①1 2 2
k=(k+(ll)p1[pu]*p2[pv]*(n2-p2[pu]-p2[pv]))%mod;
//②2 1 2
k=(k+(ll)p2[pu]*p1[pv]*(n2-p2[pu]-p2[pv]))%mod;
//2 2 2
k=(k+(ll)p2[pu]*p2[pv]*(n2-p2[pu]-p2[pv]))%mod;
//2 2 1
k=(k+(ll)p2[pu]*p2[pv]*(n1-p1[pu]-p1[pv]))%mod;
ans = (ans - k + mod)%mod;
//sto[j++] = ans;
p[pv]=pu;
p1[pu]+=p1[pv]; p2[pu]+=p2[pv];
p1[pv]=0; p2[pv]=0;
cout<<ans<<endl;
}
//for(i=0;i<j;i++){
//printf("%d\n",sto[i]);
// sto[i]=0;
// }
}
return 0;
}