• 【BZOJ5056】OI游戏 最短路+有向图生成树计数


    【BZOJ5056】OI游戏

    Description

    小Van的CP最喜欢玩与OI有关的游戏啦~小Van为了讨好她,于是冥思苦想,终于创造了一个新游戏。
    下面是小Van的OI游戏规则:
    给定一个无向连通图,有N个节点,编号为0~N-1。图里的每一条边都有一个正整数权值,边权在1~9之间。
    要求从图里删掉某些边(有可能0条),使得剩下的图满足以下两个条件:
    1) 剩下的图是一棵树,有N-1条边。
    2) 对于所有v (0 < v < N),0到v的最短路(也就是树中唯一路径长度)和原图中的最短路长度相同。
    最终要报出有多少种不同的删法可以满足上述条件。(两种删法不同当且仅当存在两个点,一种删法删完之后这两个点之间存在边而另外一种删法不存在。)
    由于答案有可能非常大,良心的小Van只需要答案膜1,000,000,007的结果。
    后记: 然而这游戏太高难度了,小Van的CP做不出来因此很不开心!
    她认为小Van在故意刁难她,于是她与小Van分手了。。。
    不过对于精通OI的你来说,这不过是小菜一碟啦!

    Input

    第一行一个整数N,代表原图结点。
    接下来N行,每行N个字符,描绘了一个邻接矩阵。邻接矩阵中,
    如果某一个元素为0,代表这两个点之间不存在边,
    并且保证第i行第i列的元素为0,第i行第j列的元素(i≠j)等于第j行第i列的元素。
    2≤N≤50

    Output

    一行一个整数,代表删法总方案数膜1,000,000,007的结果。

    Sample Input

    Input1
    2
    01
    10
    Input2
    4
    0123
    1012
    2101
    3210

    Sample Output

    Output1
    1
    Output2
    6

    题解:一开始看错题,以为是任意两点间的最短路都相同。。。

    先求出1号点到所有点的最短路径图(如果1到所有点的最短路径都可能经过某条边,则这条边在最短路径图上)(注意这个图是有向的)(可以用SPFA或Dij,不过本人懒,用的Floyd)。然后我们只需要求出这个图的生成树个数了。由于要求的是外向树,所以用度数矩阵-邻接矩阵,再用辗转相除得到行列式的值即可。具体做法可以参见天赋那道题。

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    using namespace std;
    typedef long long ll;
    const ll P=1000000007;
    int n;
    char str[60];
    int dis[60][60],map[60][60];
    ll v[60][60],A,B;
    ll ans;
    int main()
    {
    	scanf("%d",&n);
    	int i,j,k;
    	for(i=1;i<=n;i++)
    	{
    		scanf("%s",str+1);
    		for(j=1;j<=n;j++)	dis[i][j]=str[j]-'0',map[i][j]=dis[i][j]=(!dis[i][j]&&i!=j)?(0x3f3f3f3f):dis[i][j];
    	}
    	for(k=1;k<=n;k++)	for(i=1;i<=n;i++)	for(j=1;j<=n;j++)	dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
    	for(i=1;i<=n;i++)	for(j=1;j<=n;j++)	if(i!=j)
    	{
    		if(dis[1][i]+map[i][j]==dis[1][j])	v[j][j]++,v[i][j]--;
    	}
    	for(i=1;i<=n;i++)	for(j=1;j<=n;j++)	v[i][j]=(v[i][j]+P)%P;
    	ans=1;
    	for(i=2;i<=n;i++)
    	{
    		for(j=i;j<=n;j++)	if(v[j][i])	break;
    		if(j!=i)	for(ans=P-ans,k=i;k<=n;k++)	swap(v[i][k],v[j][k]);
    		for(j=i+1;j<=n;j++)
    		{
    			A=v[i][i],B=v[j][i];
    			while(B)
    			{
    				ll tmp=A/B,temp=A;	A=B,B=temp%B;
    				for(ans=P-ans,k=i;k<=n;k++)	v[i][k]=(v[i][k]-v[j][k]*tmp%P+P)%P,swap(v[i][k],v[j][k]);
    			}
    		}
    		ans=ans*v[i][i]%P;
    	}
    	printf("%lld",ans);
    	return 0;
    }
  • 相关阅读:
    [C++] 习题 2.18 倒序查找字串
    [C++] 二叉树计算文件单词数
    [C++] 例题 2.7.1 用栈实现简易计算器
    [C++] 非递归实现前中后序遍历二叉树
    [C++] 习题 2.15 实现简单环形队列
    数据结构、算法及应用
    [C++] 习题 2.14 用队列实现桶排序
    svn add 所有文件的命令
    解决Error opening terminal: xterm.的错误
    linux下删除项目中所有.svn的命令
  • 原文地址:https://www.cnblogs.com/CQzhangyu/p/7603667.html
Copyright © 2020-2023  润新知