zoukankan      html  css  js  c++  java
  • 神秘题目6

    神秘题目

    题目描述

    ​ 给定一张 (n) 个点,(m) 条边的有向图,无重边,无自环。

    ​ 给定 (q) 次询问,每次给出 (s,t,d) ,求有多少条起点为 (s) 终点为 (t) 且中途不经过 (s)(t) 的路径,答案对 (1e9+7) 取模。

    ​ 注意路径不要求是简单路径,也就是说可以多次经过同⼀个点、同⼀条边。

    输入格式

    第一行两个整数 (n,m)

    接下来 (m) 行,每行两个整数 (u,v) 表示一条 (u)(v) 的有向边。

    接下来一行一个整数 (q) 表示询问次数。

    接下来 (q) 行,每行 (3) 个整数 (s,t,d) 表示一次询问。

    输出格式

    (q) 行,每行一个整数表示答案。

    样例输入

    7 15 123456
    1 4
    3 5
    2 6
    6 7
    2 4
    7 1
    3 6
    4 2
    4 5
    6 5
    6 3
    3 4
    1 2
    3 2
    6 2
    5
    1 2 3
    4 7 8
    3 6 2
    1 7 4
    3 5 40
    

    样例输出

    0
    4
    1
    1
    12512
    

    数据范围与约定

    对于 (30\%) 的数据 (nle 10)

    对于 (50\%) 的数据 (nle20)

    对于 (100\%) 的数据 (nle100),(0le mle n imes(n-1)),(1le dle50),(qle5 imes10^5)

    时间限制:(3s)

    空间限制:(256MB)

    题解

    30分sb暴力

    路径可以转化成从 (s) 走 1 步到其他节点,中间xjb走 (d-2) 步,最后走 1 步到 (t)

    中间这 (d-2) 步可以用矩阵快速幂加速递推,即 (F imes A^{d-2})

    其中 (F) 是一个 (1) 维向量,第 (i) 个元素表示走到 (i) 的方案数,(A) 是去掉 (s,t) 的邻接矩阵。

    #include<bits/stdc++.h>
    #define LL long long
    using namespace std;
    const int P=1000000007;
    bool ks;
    int n,m,q;
    int ANS[110][110][60];
    bool js;
    struct MATRIX
    {
    	LL data[130][130];
    	void one(){for(int i=n;i>=0;i--)data[i][i]=1;}
    	void clear(){memset(data,0,sizeof data);}
    	MATRIX operator *(const MATRIX &n2){
    		MATRIX TEMP;
    		for(int i=1;i<=n;i++)
    		for(int j=1;j<=n;j++){
    			TEMP.data[i][j]=0;
    			for(int k=1;k<=n;k++)
    				(TEMP.data[i][j]+=data[i][k]*n2.data[k][j])%=P;
    		}
    		return TEMP;
    	}
    	MATRIX ksm(int K){
    		MATRIX Be=*this,As;
    		As.clear();
    		if(K<0)return As;
    		As.one();
    		for(;K;K>>=1,Be=Be*Be)
    		if(K&1)As=As*Be;
    		return As;
    	}
    }A,B,C;
    inline int read()
    {
    	int x=0,w=0;char ch=0;
    	while(!isdigit(ch)){w|=ch=='-';ch=getchar();}
    	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    	return w?-x:x;
    }
    int calc(int S,int T,int D)
    {
    	if(ANS[S][T][D]!=-1)return ANS[S][T][D];
    	if(D==0)return 0;
    	if(D==1)return A.data[S][T];
    	B.clear();
    	for(int i=1;i<=n;i++)
    	for(int j=1;j<=n;j++)
    	if(i^S)if(i^T)if(j^S)if(j^T)
    		B.data[i][j]=A.data[i][j];
    	for(int i=1;i<=n;i++)
    	if(i^S)if(i^T)
    		C.data[1][i]=A.data[S][i];
    	C=C*(B.ksm(D-2));
    	int temp=0;
    	for(int i=1;i<=n;i++)
    	if(i^S)if(i^T)
    		temp=(temp+C.data[1][i]*A.data[i][T])%P;
    	return ANS[S][T][D]=temp;
    }
    int main()
    {
    	memset(ANS,-1,sizeof ANS);
    	n=read();m=read();
    	for(int i=1;i<=m;i++){
    		int x=read(),y=read();
    		A.data[x][y]=1;
    	}
    	q=read();
    	while(q --> 0){
    		int S=read(),T=read(),D=read();
    		printf("%d
    ",calc(S,T,D));
    	}
    }
    

    50分暴力

    矩阵加速个锤子啊,d就50

    而且向量乘矩阵明明是 (n^2) 的,非得写成 (n^3) 的矩阵乘矩阵。

    注意到,(d) 只有 50。可以考虑预处理出所有答案,然后 (O(1)) 回答。

    考虑递推,设 (f[x][y][i]) 表示从 (x)(i) 步走到 (y) 中途不经过 (x,y) 的方案数。

    转移很简单,枚举下一个节点即可。

    #include<bits/stdc++.h>
    #define LL long long
    using namespace std;
    bool ks;
    const int P=1000000007;
    int n,m,q,road[110][110];
    LL ANS[110][110][60],f[110][60];
    bool js;
    inline int read()
    {
    	int x=0,w=0;char ch=0;
    	while(!isdigit(ch)){w|=ch=='-';ch=getchar();}
    	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    	return w?-x:x;
    }
    void calc(int S,int T,int D)
    {
    	ANS[S][T][0]=ANS[S][T][2]=0;
    	ANS[S][T][1]=road[S][T];
    	memset(f,0,sizeof f);
    	for(int i=1;i<=n;i++)
    		f[i][1]=road[S][i];
    	for(int t=1;t<=50;t++)
    	for(int i=1;i<=n;i++)
    	if(i!=T&&i!=S)
    	for(int j=1;j<=n;j++)
    	if(j!=T&&j!=S&&road[i][j])
    		(f[j][t+1]+=f[i][t])%=P;
    	for(int t=2;t<=50;t++)
    	for(int i=1;i<=n;i++)
    	if(i!=T&&i!=S&&road[i][T])
    		(ANS[S][T][t]+=f[i][t-1])%=P;
    }
    void prepare()
    {
    	for(int i=1;i<=n;i++)
    	for(int j=1;j<=n;j++)
    		calc(i,j,50);
    }
    int main()
    {
    	freopen("road.in","r",stdin);
    	freopen("road.out","w",stdout);
    	n=read();m=read();
    	for(int i=1;i<=m;i++){
    		int x=read(),y=read();
    		road[x][y]=1;
    	}
    	prepare();
    	q=read();
    	while(q --> 0){
    		int S=read(),T=read(),D=read();
    		if(S==T&&!D)printf("1
    ");
    		else printf("%lld
    ",ANS[S][T][D]);
    	}
    }
    

    满分做法

    考虑总方案数-不合法方案数。

    (f[x][y][i]) 表示从 (x)(i) 步走到 (y) 中途不经过 (x,y) 的方案数。

    (g[x][y][i]) 表示从 (x)(i) 步走到 (y) 的方案数,中途没限制。

    (h[x][y][i]) 表示从 (x)(i) 步走到 (x) 中途不经过 (y) 的方案数。

    首先 (O(n^4)) 求出 (g) ,很简单,对于一个状态 (g[x][y][i]) 枚举 (y) 的下一个节点更新即可。

    然后大力分类讨论

    考虑用 (g) 减去不合法的方案求出 (f,h)

    (f)

    • 如果中间经过的第一个不能经过的节点是 (y): x --> y --> y

      • 注意,一条路径中途可能多次会经过 (y) ,我们只在第一次经过时统计,这样能避免重复。
      • 观察这条路径, y 将其分成了 x->y 和 y->y 两部分,前面一部分就是 (f) 了,后面是 (g),枚举前面一部分的长度更新 (f) 即可。
    • 如果中间经过的第一个不能经过的节点是 (x): x --> x --> y

    • 很显然,前一部分是 (h) ,后一部分是 (g) ,枚举长度更新即可。

    (g)

    • 路径只有一种,即 x-->y-->x
    • 前面是 (f),后面是 (g) 。更新方法和上面一模一样。
    #include<bits/stdc++.h>
    #define LL long long
    using namespace std;
    const mod=1000000007;
    bool ks;
    int n,m,q,road[110][110];
    LL f[110][110][60],g[110][110][60],h[110][110][60];
    bool js;
    inline int read()
    {
    	int x=0,w=0;char ch=0;
    	while(!isdigit(ch)){w|=ch=='-';ch=getchar();}
    	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    	return w?-x:x;
    }
    void prepare()
    {
    	for(int i=1;i<=n;i++)
    	g[i][i][0]=1;
    	for(int len=0;len<=50;len++)
    	for(int i=1;i<=n;i++)
    	for(int j=1;j<=n;j++)
    	for(int k=1;k<=n;k++)
    		(g[i][k][len+1]+=g[i][j][len]*road[j][k])%=mod;
    	for(int len=1;len<=50;len++){
    		for(int x=1;x<=n;x++)
    		for(int y=1;y<=n;y++){
    			f[x][y][len]=g[x][y][len];
    			for(int i=1;i<len;i++)
    				(f[x][y][len]-=f[x][y][i]*g[y][y][len-i])%=mod;
    			if(x==y)continue;
    			for(int i=1;i<len;i++)
    				(f[x][y][len]-=h[x][y][i]*g[x][y][len-i])%=mod;
    		}
    		for(int x=1;x<=n;x++)
    		for(int y=1;y<=n;y++){
    			h[x][y][len]=g[x][x][len];
    			for(int i=1;i<len;i++)
    				(h[x][y][len]-=f[x][y][i]*g[y][x][len-i])%=mod;
    			for(int i=1;i<len;i++)
    				(h[x][y][len]-=h[x][y][i]*g[x][x][len-i])%=mod;
    		}
    	}
    }
    int main()
    {
    	n=read();m=read();
    	for(int i=1;i<=m;i++){
    		int x=read(),y=read();
    		road[x][y]=1;
    	}
    	prepare();
    	q=read();
    	while(q --> 0){
    		int S=read(),T=read(),D=read();
    		printf("%lld
    ",(f[S][T][D]%mod+mod)%mod);
    	}
    }
    
  • 相关阅读:
    [SQL Server]如何激活一个账号
    sqlcmd命令详解
    SQL Server Express 2008 安装程序
    SharePoint中的本地化(Localization)
    2009十大企业应用产品
    2010年10大战略技术
    十个理由促使小企业敢于触碰“云计算”
    如何改进网站性能
    sqlcmd详细示例
    VMware网络配置详解
  • 原文地址:https://www.cnblogs.com/zYzYzYzYz/p/14582802.html
Copyright © 2011-2022 走看看