zoukankan      html  css  js  c++  java
  • 生成树题目

    1486:【例题1】黑暗城堡

    求:最小生成树数目

    先用dijkstra求出1号房间到每个房间的单源最短路径存储到dis数组中。把树形城堡看作以1为根的有根树。由题,若x是y的根节点,x、y之间的通道长度为z,
    则应该有:dis[y]=dis[x]+z。事实上,我们把满足题目要求的树结构,即对任意一对父子结点x、y都有上式成立的树结构,称为图的一棵最短路径生成树。
    与Prim算法类似,统计有多少结点x满足dis[p]=dis[x]+e[x][p],让p与其中任意一个x相连都符合题目要求。即满足乘法定理。

    最短路径生成树:对于任意一对父子结点x、y均满足dis[y]=dis[x]+e[x][y]的树结构称为图的一棵最短路径生成树;

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1010;
    const int INF=0x3fffffff;
    typedef long long LL;
    /*
    先用dijkstra求出1号房间到每个房间的单源最短路径存储到dis数组中。把树形城堡看作以1为根的有根树。由题,若x是y的根节点,x、y之间的通道长度为z,
    则应该有:dis[y]=dis[x]+z。事实上,我们把满足题目要求的树结构,即对任意一对父子结点x、y都有上式成立的树结构,称为图的一棵最短路径生成树。
    与Prim算法类似,统计有多少结点x满足dis[p]=dis[x]+e[x][p],让p与其中任意一个x相连都符合题目要求
    
    最短路径生成树:对于任意一对父子结点x、y均满足dis[y]=dis[x]+e[x][y]的树结构称为图的一棵最短路径生成树;
    */
    LL  dis[maxn];
    LL num[maxn];
    int vis[maxn];
    struct node{
    	int to,val;
    	node(int a,int b){
    		to=a;val=b;
    	}
    };
    vector<node> adj[maxn];
    int n,m;
    int main(){
    	int x,y,z;
    	scanf("%d %d",&n,&m);
    	for(int i=0;i<m;i++){
    		scanf("%d %d %d",&x,&y,&z);
    		adj[x].push_back(node(y,z));
    		adj[y].push_back(node(x,z));
    	}
    	for(int i=1;i<=n;i++) dis[i]=INF;
    	dis[1]=0;
    	for(int i=1;i<=n;i++){
    		int u=-1,temp=INF;
    		for(int j=1;j<=n;j++){
    			if(!vis[j]&&dis[j]<temp){
    				temp=dis[j];
    				u=j;
    			}
    		}
    		if(u==-1) break;
    		vis[u]=1;
    		for(int j=0;j<adj[u].size();j++){
    			int diss=adj[u][j].val;
    			int to=adj[u][j].to;
    			if(!vis[to]){
    				if(dis[to]>dis[u]+diss){
    					dis[to]=dis[u]+diss;
    				}
    			}
    		}
    	}
    	
    	LL ans=1,mod=(1<<31)-1;
    	for(int i=1;i<=n;i++){
    		for(int j=0;j<adj[i].size();j++){
    			int t=adj[i][j].to;
    			if(dis[t]==dis[i]+adj[i][j].val) num[t]++;
    		}
    	} 
    	for(int i=1;i<=n;i++){
    		//cout<<num[i]<<endl;
    		if(num[i]){
    			ans=(ans*num[i])%mod;
    		}
    	}
    	printf("%lld
    ",ans);
    return 0;
    }

    1487:【例 2】北极通讯网络

    求:最小生成树的第k大的边的长度

    k个村庄装上卫星设备后可以看成1个点,那么只需要求这(n-k+1)个点和(n-k)条边所构成的最小生成树
    利用Kruskal算法,只需要进行(n-k)次操作,最后一次操作时加入生成树的边的权值就是所求的d

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1010;
    const int INF=0x3fffffff;
    typedef long long LL;
    //题目的意思是求最小生成树的第k大的边的长度
    /*
    k个村庄装上卫星设备后可以看成1个点,那么只需要求这(n-k+1)个点和(n-k)条边所构成的最小生成树
    利用Kruskal算法,只需要进行(n-k)次操作,最后一次操作时加入生成树的边的权值就是所求的d
    */
    int n,k,m,fa[510],x[510],y[510];
    struct node{
    	int a,b;
    	double dis;
    }ed[250000];
    double js(int ax,int ay,int bx,int by){
    	return sqrt(pow(ax-bx,2)+pow(ay-by,2));
    }
    bool cmp(node a,node b){
    	return a.dis<b.dis;
    }
    int findf(int x){
    	if(x==fa[x]) return x;
    	else return fa[x]=findf(fa[x]);
    }
    int num1;
    double res;
    void kruskal(){
    	for(int i=1;i<=n;i++) fa[i]=i;
    	for(int i=1;i<=m;i++){
    		int fa1=findf(ed[i].a);
    		int fa2=findf(ed[i].b);
    		if(fa1!=fa2){
    			fa[fa1]=fa2;
    			num1++;
    			if(num1==n-k){ //进行n-k次
    				res=ed[i].dis;
    				return ;
    			}
    		}
    	}
    }
    int main(){
    	scanf("%d %d",&n,&k);
    	for(int i=1;i<=n;i++){
    			scanf("%d %d",&x[i],&y[i]); //输入坐标
    		}
    		if(k==0) k=1; //别忘了这一个 
    	if(k>=n) {
    		printf("0.00
    ");
    		return 0;
    	}
    	else{
    		
    		m=1;
    		for(int i=1;i<=n;i++){
    			for(int j=i+1;j<=n;j++){
    				ed[m].a=i;
    				ed[m].b=j;
    				ed[m].dis=js(x[i],y[i],x[j],y[j]);  //计算每一个点间的距离
    				m++;
    			}
    		}
    		m--;  //细节呀!!!!!!!!! 
    		sort(ed+1,ed+m+1,cmp);
    		kruskal();
    		printf("%.2lf
    ",res);
    	}
    return 0;
    }

    1488:新的开始

    需要加一个超级源点!!!!!加的超级源点与各个点的边权为建立收费站的价值

    应该建一个虚拟源点,每个点i连向虚拟源点的边权为发电站费用v[ i ]

    为什么呢..?因为虽然是双向边,但存边的时候只能单向存。

    这就导致,从哪个点出发,会决定了,有的边加不加的进去。

    于是...不能简简单单的取出发电站费用中最少的.

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1010;
    const int INF=0x3fffffff;
    typedef long long LL;
    //还是不太理解为什么要加一个超级源点的问题
    int n;
    int mon[510];
    int mp[510][510];
    LL dis[510];
    int vis[510]={0};
    int main(){
    	cin>>n;
    	for(int i=1;i<=n;i++){
    		cin>>mon[i];
    		mp[i][1+n]=mon[i];  //这个1+n就是超级源点
    		mp[1+n][i]=mon[i];
    	} 
    	for(int i=1;i<=n;i++){
    		for(int j=1;j<=n;j++){
    			cin>>mp[i][j];
    		}
    	}
    	for(int i=1;i<=n+1;i++){   //超级源点也要加上 
    		dis[i]=mp[1][i];
    	}
    	LL ans=0;
    	vis[1]=1;
    	for(int i=1;i<=n;i++){
    		int u=-1,temp=INF;
    		for(int j=1;j<=n+1;j++){
    			if(!vis[j]&&dis[j]<temp){
    				temp=dis[j];
    				u=j;
    			}
    		}
    		if(u==-1) break;
    		vis[u]=1;
    		ans+=dis[u];
    		for(int j=1;j<=n+1;j++){
    			if(!vis[j]&&j!=u&&dis[j]<mp[u][j]){
    				dis[j]=mp[u][j];
    			}
    		}
    	}
    	cout<<ans<<endl;
    return 0;
    }

    1489:构造完全图

    https://blog.csdn.net/lylzsx20172018/article/details/104149398?depth_1-utm_source=distribute.pc_relev=ant.none-task&utm_source=distribute.pc_relevant.none-task

    完全图:
    若一个图的每一对不同顶点恰有一条边相连,则称为完全图。
    完全图是每对顶点之间都恰连有一条边的简单图。n个端点的完全图有n个端点及n(n-1) / 2条边。由
    最小生成树复原最小完全图
    最小生成树T,T中的每一条边都是一条割边
    去掉任意一条树T中的边,这个树一定会变成两个联通块,令其为A、B。
    要将T扩展为完全图G,那么显然 A中的每个点都需要与B中的所有点相连。
    A中新发出的连边的权值一定是大于(若等于则存在多种最小生成树解,不合题意)出发点在树上的相接边的权值的。
    再一看数据量,单独枚举肯定不行。由联通块可以联想到并查集,发现可行。
    对于一条最小生成树上的边E<A,B>,可以看做E连接了A,B两个联通块。
    那么要将A、B两块连接为一个完全图需要加的边数就是 cnt[A] * cnt[B]-1,其中cnt表示联通块中的结点个数。
    边的权值一定是大于E的权值的,要使其最小,那么这些边的权值都是 E的权值+1
    那么合并A、B两个联通块的花费就是 (边E.权+1)*(cnt[A]*cnt[B]-1)。ans在每次合并联通块时加上花费即可求解。
    另外一点,因为要求的是最小的完全图,所以有一个贪心策略:先把树上的边按照权值从小到大排序,然后枚举即可

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1e5+10;
    const int INF=0x3fffffff;
    typedef long long LL;
    /*
    https://blog.csdn.net/lylzsx20172018/article/details/104149398?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
    完全图:
    若一个图的每一对不同顶点恰有一条边相连,则称为完全图。
    完全图是每对顶点之间都恰连有一条边的简单图。n个端点的完全图有n个端点及n(n ? 1) / 2条边。由
    最小生成树复原最小完全图:
    最小生成树T,T中的每一条边都是一条割边
    去掉任意一条树T中的边,这个树一定会变成两个联通块,令其为A、B。
    要将T扩展为完全图G,那么显然 A中的每个点都需要与B中的所有点相连。
    A中新发出的连边的权值一定是大于(若等于则存在多种最小生成树解,不合题意)出发点在树上的相接边的权值的。
    再一看数据量,单独枚举肯定不行。由联通块可以联想到并查集,发现可行。
    对于一条最小生成树上的边E<A,B>,可以看做E连接了A,B两个联通块。
    那么要将A、B两块连接为一个完全图需要加的边数就是 cnt[A] * cnt[B]-1,其中cnt表示联通块中的结点个数。
    边的权值一定是大于E的权值的,要使其最小,那么这些边的权值都是 E的权值+1
    那么合并A、B两个联通块的花费就是 (边E.权+1)*(cnt[A]*cnt[B]-1)。ans在每次合并联通块时加上花费即可求解。
    另外一点,因为要求的是最小的完全图,所以有一个贪心策略:先把树上的边按照权值从小到大排序,然后枚举即可。
    
    */ 
    struct node{
    	int u,v;
    	LL w;
    }ed[maxn];
    int fa[maxn];
    int findfa(int x){
    	if(x==fa[x]) return x;
    	else return fa[x]=findfa(fa[x]);
    }
    int n,size[maxn];
    bool cmp(node a,node b){
    	return a.w<b.w;
    }
    int main(){
    	scanf("%d",&n);
    	LL summ=0;
    	for(int i=1;i<n;i++){
    		scanf("%d %d %lld",&ed[i].u,&ed[i].v,&ed[i].w);
    		summ+=ed[i].w;  //也不要忘了加上本来的大小
    	}
    	for(int i=1;i<=n;i++){
    		fa[i]=i;
    		size[i]=1;  //需要记录大小,每个连通块
    	}
    	sort(ed+1,ed+n,cmp);
    	for(int i=1;i<n;i++){
    		int t1=findfa(ed[i].u);
    		int t2=findfa(ed[i].v);
    		summ+=(LL)(size[t1]*size[t2]-1)*(ed[i].w+1); //!!!这一步 
    		fa[t1]=t2;
    		size[t2]+=size[t1];  ///在这里合并
    	}
    	printf("%lld
    ",summ); 
    return 0;
    }

    1490:秘密的牛奶运输

    这就是一个次小生成树得模板问题

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=2010;
    const int INF=0x3fffffff;
    typedef long long LL;
    //求次小生成树
    //kruskal算法 
    int n,m,pre[maxn],fa[maxn],maxx[maxn][maxn];
    struct edge{
    	int u,v,w;
    	bool vis;
    }ed[20010]; 
    vector<int> g[maxn];
    bool cmp(edge a,edge b){
    	return a.w<b.w;
    }
    void inti(){
    	for(int i=1;i<=n;i++){
    		g[i].clear();
    		g[i].push_back(i);
    		fa[i]=i;
    	}
    }
    int findf(int x){
    	if(x==fa[x]) return x;
    	else return fa[x]=findf(fa[x]);
    }
    int kruskal(){
    	sort(ed+1,ed+1+m,cmp);
    	inti();
    	int ans=0,cnt=0;
    	for(int i=1;i<=m;i++){
    		if(cnt==n-1) break;   //已经ok了
    		int fa1=findf(ed[i].u);
    		int fa2=findf(ed[i].v);
    		if(fa1!=fa2){
    			cnt++;
    			ed[i].vis=1;
    			ans+=ed[i].w;
    			int len_fx=g[fa1].size();
    			int len_fy=g[fa2].size();
    			for(int j=0;j<len_fx;j++){
    				for(int k=0;k<len_fy;k++){
    					maxx[g[fa1][j]][g[fa2][k]]=maxx[g[fa2][k]][g[fa1][j]]=ed[i].w;
    				}
    			}
    			fa[fa1]=fa2;
    			for(int j=0;j<len_fx;j++){
    				g[fa2].push_back(g[fa1][j]);
    			}
    		} 
    	}
    	return ans;
    }
    
    int second_kruskal(int mst){
    	int ans=INF;
    	for(int i=1;i<=m;i++){
    		if(!ed[i].vis){  //这条边没有访问过 
     			ans=min(ans,mst+ed[i].w-maxx[ed[i].u][ed[i].v]);
    		}
    	}
    	return ans;
    }
    int main(){
    		scanf("%d %d",&n,&m);
    		for(int i=1;i<=m;i++){
    			scanf("%d %d %d",&ed[i].u,&ed[i].v,&ed[i].w);
    			ed[i].vis=0;
    		}
    		int mst=kruskal();
    		int sec=second_kruskal(mst);
    		printf("%d
    ",sec);
    	
    	return 0;
    }
     

    1491:Tree

    挑出need条最短的边,再从黑边里面挑组成最小生成树不就行了?这样不行,因为这样它就干扰了原来的最小生成树,不能保证最优解.
    因为kruskal算法的根本是边的权值要单调递增.所以我们可以从这里下手,把每一条白边加上一个值,改变每一条边的排列顺序,最后计算答案时就只需要减去
    原来加上的值就可以了.由于我们不知道要加上多少,所以我们可以用二分答案来做每一次的判断来得到答案.
    意:如果两条边权值相等,白边的优先级要高一些.
    二分答案,考虑我们往白边上加值或者减值,那么就会对应的少选白边或者少选黑边

    那么如果当前 白边+mid如果kuskal选择白边比need多就继续往上面加值,如果选择白边比need少就往下减值

    因为kuskal保证图一定连通,并且代价最小。所以保证了正确性

    /*
    先挑出need条最短的边,再从黑边里面挑组成最小生成树不就行了?这样不行,因为这样它就干扰了原来的最小生成树,不能保证最优解.
    因为kruskal算法的根本是边的权值要单调递增.所以我们可以从这里下手,把每一条白边加上一个值,改变每一条边的排列顺序,最后计算答案时就只需要减去
    原来加上的值就可以了.由于我们不知道要加上多少,所以我们可以用二分答案来做每一次的判断来得到答案.
    意:如果两条边权值相等,白边的优先级要高一些.
    二分答案,考虑我们往白边上加值或者减值,那么就会对应的少选白边或者少选黑边
    
    那么如果当前  白边+mid如果kuskal选择白边比need多就继续往上面加值,如果选择白边比need少就往下减值
    
    因为kuskal保证图一定连通,并且代价最小。所以保证了正确性
    */
    //呜呜呜呜,不知道哪里错了
    #include<bits/stdc++.h>
    #define maxn 50005
    using namespace std;
    int n,m,p,father[maxn],ans,sum,cnt,white;
    struct node{
        int u,v,dis,c;
    }edge[maxn*4];
    inline bool cmp(node x,node y)
    {
        if(x.dis==y.dis) return x.c<y.c;//权值相等白边优先级高
        return x.dis<y.dis;
    }
    inline int find(int x)
    {
        if(father[x]!=x) father[x]=find(father[x]);
        return father[x];
    }
    inline bool check(int x)//二分答案+kruskal
    {
        for(int i=0;i<=n;i++) father[i]=i;
        for(int i=1;i<=m;i++)
        {
            if(!edge[i].c) edge[i].dis+=x;//加上x的值
        }
        sort(edge+1,edge+m+1,cmp);
        cnt=0,white=0,sum=0;
        for(int i=1;i<=m;i++)//略作修改的kruskal模板
        {
            if(cnt==n-1) break;
            int x=find(edge[i].u),y=find(edge[i].v);
            if(x==y) continue;
            father[x]=y;
            cnt++;
            sum+=edge[i].dis;
            if(!edge[i].c) white++;
        }
        for(int i=1;i<=m;i++)
        {
            if(!edge[i].c) edge[i].dis-=x;//还原
        }
        if(white>=p) return 1;
        return 0;
    }
    int main()
    {
        scanf("%d%d%d",&n,&m,&p);
        for(int i=1;i<=m;i++) scanf("%d%d%d%d",&edge[i].u,&edge[i].v,&edge[i].dis,&edge[i].c);
        int l=-101,r=101,mid;
        while(l<=r)
        {
            mid=l+r>>1; 
            if(check(mid)) 
            {
                l=mid+1;
                ans=sum-p*mid;
            }
            else r=mid-1;
        } 
        printf("%d",ans);
        return 0;
    } 

    1492:最小生成树计数

    于不同的最小生成树,每种权值的边使用的数量是一定的,每种权值的边的作用是确定的
    我们可以先做一遍Kruskal,求出每种权值的边的使用数量num
    再对于每种权值的边,2^num搜索出合法使用方案,把每种权值的边的方案用乘法原理乘起来就是答案了
    另外,最小生成树计数的数学原理(有点复杂5555)https://wenku.baidu.com/view/872eb02de2bd960590c677c6.html

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=2010;
    const int INF=0x3fffffff;
    typedef long long LL;
    /*
    对于不同的最小生成树,每种权值的边使用的数量是一定的,每种权值的边的作用是确定的
    我们可以先做一遍Kruskal,求出每种权值的边的使用数量num
    再对于每种权值的边,2^num搜索出合法使用方案,把每种权值的边的方案用乘法原理乘起来就是答案了
    另外,最小生成树计数的数学原理(有点复杂5555)https://wenku.baidu.com/view/872eb02de2bd960590c677c6.html 
    */ 
    int n,m;
    struct node{
    	int u,v,dis,pos;
    }ed[maxn];
    int fa[maxn];
    int num[maxn],tot;
    int st[maxn],cnt;
    int summ,mod=31011;
    LL ans=1;
    bool cmp(node a,node b){
    	return a.dis<b.dis;
    }
    int findf(int x){
    	if(x==fa[x]) return x;
    	else return fa[x]=findf(fa[x]);
    }
    void dfs(int kind,int now,int chosen){  //dfs(i,st[i],0);
    	if(now==st[kind+1]) {   //到了下一个长度的坐标了 
    		if(chosen==num[kind]) summ++,cout<<summ<<endl;//如果这种长度的边用了这么多次 
    		return;
    	}
    	int fa1=findf(ed[now].u);
    	int fa2=findf(ed[now].v);
    	if(fa1!=fa2){
    		fa[fa1]=fa2;  //合并 
    		dfs(kind,now+1,chosen+1);  //选择这条边,进行下一条 
    		fa[fa1]=fa1; //恢复 
    		fa[fa2]=fa2;
    	}
    	dfs(kind,now+1,chosen);  //同一个连通块,就不选,接着下一个 
    }
    int main(){
    	scanf("%d %d",&n,&m);
    	for(int i=1;i<=m;i++){
    		scanf("%d %d %d",&ed[i].u,&ed[i].v,&ed[i].dis);
    	}
    	for(int i=1;i<=n;i++) fa[i]=i;
    	sort(ed+1,ed+1+m,cmp);
    	//其实这些动作相当于再把长度离散化 
    	for(int i=1;i<=m;i++){
    		if(ed[i].dis!=ed[i-1].dis) st[++cnt]=i; //不同长度边的 下标 
    		ed[i].pos=cnt; //所在的位置 
    	}
    	st[cnt+1]=m+1;
    	for(int i=1;i<=m;i++){
    		int fa1=findf(ed[i].u);
    		int fa2=findf(ed[i].v);
    		if(fa1!=fa2) {
    			fa[fa1]=fa2;
    			num[ed[i].pos]++;   //这个位置的数(这个长度的边)用了多少次 
    			tot++;  //生成树含有的边数 
    		}
    	}
    	if(tot!=n-1) {
    		printf("0
    ");
    		return 0;
    	}
    	for(int i=1;i<=n;i++) fa[i]=i;
    	for(int i=1;i<=cnt;i++){
    		if(num[i]){
    			summ=0;
    			dfs(i,st[i],0);
    		//	cout<<summ<<endl;
    			for(int j=st[i];j<st[i+1];j++){ //计算完了之后,这些长度一样的边就合并 
    				fa[findf(ed[j].u)]=findf(ed[j].v);
    			} 
    			ans=(ans*summ)%mod;
    		}
    	}
    	printf("%lld",ans);
    return 0;
    }
    
    //再次哭泣
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int maxn=2000,Mod=31011;
    int n,m,tot,sum,ans=1,cnt,st[maxn],fa[maxn],num[maxn];
    struct edge{int x,y,dis,pos;}e[maxn];
    void read(int &k){
        k=0; int f=1; char c=getchar();
        while(c<'0'||c>'9')c=='-'&&(f=-1),c=getchar();
        while('0'<=c&&c<='9')k=k*10+c-'0',c=getchar();
        k*=f;
    }
    int find(int x){return fa[x]==x?x:find(fa[x]);}
    void dfs(int kind,int now,int chosen){
        if (now==st[kind+1]){
            if (chosen==num[kind]) {
            	sum++;
    		}
            return;
        }
        int p=find(e[now].x),q=find(e[now].y);
        if (p!=q) fa[p]=q,dfs(kind,now+1,chosen+1),fa[p]=p,fa[q]=q;    
        dfs(kind,now+1,chosen);
    }
    bool cmp(edge a,edge b){return a.dis<b.dis;}
    int main(){
        read(n); read(m);
        for (int i=1;i<=m;i++)read(e[i].x),read(e[i].y),read(e[i].dis);
        sort(e+1,e+m+1,cmp);
        for (int i=1;i<=m;i++) {
            if (e[i].dis!=e[i-1].dis) st[++cnt]=i;
            e[i].pos=cnt;
        }
        st[cnt+1]=m+1;
        for (int i=1;i<=n;i++) fa[i]=i;
        for (int i=1,x,y;i<=m;i++) 
            if ((x=find(e[i].x))!=(y=find(e[i].y))) fa[x]=y,num[e[i].pos]++,tot++;
        if (tot!=n-1) return puts("0"),0;
        for (int i=1;i<=n;i++) fa[i]=i;
        for (int i=1;i<=cnt;i++) if(num[i]){
            sum=0; dfs(i,st[i],0);
            
            for (int j=st[i];j<st[i+1];j++) fa[find(e[j].x)]=find(e[j].y);
            ans=1LL*ans*sum%Mod;
        }
        printf("%d",ans);
        return 0;
    } 
    

      

  • 相关阅读:
    OAuth2.0的四种授权模式
    Jedis整合单机、Sentinel和Cluster模式
    Redis Cluste部署
    Web-动态页面
    Web开发-Servlet&HTTP&Request
    Ajax&Json
    Web开发之Tomcat&Servlet
    PagedListCore的使用
    自己写一个依赖注入容器Container
    在core2.0中实现按程序集注入依赖
  • 原文地址:https://www.cnblogs.com/shirlybaby/p/12558967.html
Copyright © 2011-2022 走看看