标签:基础 efi int pen view lap std 自身 地址
题目地址:http://acm.hdu.edu.cn/showproblem.php?pid=6795
题意:有n个人,分为两类,一类为权值为1的人,一类为权值为2的人。起初,这n个人互不相识,然后又n-1此介绍,每次介绍u与v相识(在这之前u,v并不认识,若a认识b,b认识c,则a也认为认识c),在第i次介绍之前输出当前的组合方案。组合是从挑选3个人,3个人的权值和不小于5,并且3个人互不相识
输入:第一行一个整数t,表示测试样例的个数
对于每个测试样例,第一行一个整数n,第二行n个整数(1或2),表示权值,接下来n-1行表示n-1次介绍
输出:对于每个测试样例,在第i次介绍之前,输出当前的组合的方案,如果输比较大,则输出对10^9+7的模
题解:对于3个人的组合权值不小于5只有两种方式:2+2+2或者2+2+1.
看到里面的a认识b,b认识c则a认识c,便会意识到这肯定是并查集存储关系。
然后,观察其中的数学逻辑,对于组合方案的数目(不取模的话)整体而言是非递增排列,我们可以先计算出初始的组合数目sum,对于每一次的介绍只要在上一次的sum的基础上减去因为当前介绍而不能组队的方案数目即可。另外,题目里面说u,v并不相识,说明在此之前u,v属于两个不同的集合,我们只需要标记每个集合含有的1的数目和2的数目,每次sum减去p2[ru]*p2[rv]*(now2+now1)+p1[ru]*p2[rv]*now2+p2[ru]*p1[rv]*now2.其中now1,now2表示除当前两个集合之外的其余的1与2的数目的总和就可以了。
AC代码:
#include<iostream> #include<cstdio> using namespace std; #define ll long long int const int N=100000+5; const int maxn=1000000000+7; ll p1[N],p2[N];//分别存储集合的祖宗为i的集合的1和2的数量 ll s1,s2;//记录1和2的总数 ll sum;//初始时(还没有第一次介绍前的组队数) int fa[N];//并查集的记录数组 int my[N];//表示自身节点是2或者是1 int n; void init(){ s1=0,s2=0; for(int i=1;i<=n;i++){ fa[i]=i;//并查集数组初始化 if(my[i]==1) p1[i]=1,p2[i]=0,s1++; else p1[i]=0,p2[i]=1,s2++; } sum=s2*(s2-1)*(s2-2)/2/3+s2*(s2-1)/2*s1; sum%=maxn; } int find(int x){ if(x==fa[x]) return x; else return fa[x]=find(fa[x]); } void merge(int u,int v){ int ru=find(u),rv=find(v); fa[rv]=ru; ll now1=s1-p1[ru]-p1[rv],now2=s2-p2[ru]-p2[rv]; sum=sum-p2[ru]*p2[rv]*(now2+now1)-p1[ru]*p2[rv]*now2-p2[ru]*p1[rv]*now2; while(sum<0) sum+=maxn;//之前这里写的是sum+=maxn然后就一直出错,因为sum加了一次maxn之后还有可能是负的 sum%=maxn; p1[ru]+=p1[rv]; p2[ru]+=p2[rv]; } int main(){ int t;cin>>t; while(t--){ cin>>n; for(int i=1;i<=n;i++) scanf("%d",&my[i]); init(); cout<<sum<<endl; int u,v; for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); merge(u,v); cout<<sum<<endl; } } return 0; }
写于:2020/7/29 19:34
2020hdu多校第三场1005(6795)Little W and Contest
标签:基础 efi int pen view lap std 自身 地址
原文地址:https://www.cnblogs.com/sunjianzhao/p/13399171.html