2020多校hdu第三场 1005:Little W and Contest (hdu6795)
题目:http://acm.hdu.edu.cn/showproblem.php?pid=6795
题目描述:
每个人的权值为1或2,要求选3个人组成一队,队里的人权值总和>=5,且他们互不认识。
一开始他们都不认识,在之后n-1天里,每2个人认识,且认识关系具有传递性,即2个互相
不认识的人,若他们认识的人相互认识,则他们也会认识。问n天内,每天可以组成多少队。
分析:
显而易见是用并查集,考虑在x,y这两个人认识后对答案的影响。设互相认识这个朋友圈,
是一个集合,x在u集合,y在v集合中。因为u,v集合合并后,从u集合中选出的人和v中的人组队
就变成不合法的了,我们只要用前一天的答案减去这些不合法的组合就是这天的答案。
在并查集中用按高度归并,就很容易得到他们合并后总的根。
代码:
#include<iostream> #include<algorithm> #include<cstring> #include<cstdio> #include<sstream> #include<vector> #include<stack> #include<deque> #include<cmath> #include<map> #include<queue> #include<bitset> #define sd(x) scanf("%d",&x) #define ms(x,y) memset(x,y,sizeof x) #define fu(i,a,b) for(int i=a;i<=b;i++) #define fd(i,a,b) for(int i=a;i>=b;i--) #define all(a) a.begin(),a.end() using namespace std; typedef long long ll; typedef unsigned long long ull; typedef long double ld; const int maxn=1e5+79; const int mod=1e9+7; const ll INF=1e18+7; int a[maxn],fa[maxn]; int cnt1[maxn],cnt2[maxn],Rank[maxn]; int find(int x) { return x==fa[x]?x:fa[x]=find(fa[x]); } int unit(int x,int y) { x=find(x),y=find(y); if(Rank[x]<=Rank[y]) { cnt1[y]+=cnt1[x]; cnt2[y]+=cnt2[x]; fa[x]=y; if(Rank[x]==Rank[y]) Rank[y]++; return y; } else { cnt1[x]+=cnt1[y]; cnt2[x]+=cnt2[y]; fa[y]=x; return x; } } int main() { int T; cin>>T; while(T--) { ms(cnt1,0); ms(cnt2,0); ms(Rank,0); int n; sd(n); ll num1=0,num2=0; fu(i,1,n) { fa[i]=i; sd(a[i]); if(a[i]==1) cnt1[i]=1,num1++; else cnt2[i]=1,num2++; } ll ans=num2*(num2-1)/2*num1%mod+num2*(num2-1)*(num2-2)/6%mod; ans%=mod; printf("%lld\n",ans); fu(i,1,n-1) { int x,y; sd(x);sd(y); int fx=find(x),fy=find(y); ll x1=cnt1[fx],x2=cnt2[fx]; ll y1=cnt1[fy],y2=cnt2[fy]; int rt=unit(x,y);//总的根 //首先减去在集合x,y中选2个2的情况 ans=(ans-(n-cnt1[rt]-cnt2[rt])*x2*y2%mod+mod)%mod; //然后是在集合x,y中选一个1的情况 ans=(ans-(num2-cnt2[rt])*x1*y2%mod+mod)%mod; ans=(ans-(num2-cnt2[rt])*x2*y1%mod+mod)%mod; printf("%lld\n",ans); } } return 0; }