神秘题目
题目描述
给定一张 (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);
}
}