uoj279题目交流通道(dp)
题目大意:
神犇星球有 \(n\) 座小城。对于任意两座小城 \(v,u\)\((v≠u)\),吉米多出题斯基想在 \(v,u\) 之间建立一个传送时间为 \(w(v,u)\)的无向传送通道,其中 \(w(v,u)\) 为不超过 \(k\) 的非负整数。建成后,神犇星球的居民可从一座小城出发经过一个或若干个传送通道到达另一座小城交流题目,花费的时间为所有经过的传送通道的传送时间之和。
吉米多出题斯基还没有决定每一个传送通道的传送时间取值,只是对于任意两座小城 \(v,u\),决定了从 \(v\)出发到达 \(u\) 的最短时间要恰好等于 \(d(v,u)\)。但吉米多出题斯基日理万机,于是他找到了你 —— 风璃殇做不出题耶维奇,请你帮助吉米多出题斯基数一数有多少种不同的满足条件的传送通道建设方案吧!
由于方案数可能很大,你只用输出方案数对 \(998244353\) 取模后的结果。
QwQ看到这个题 其实没什么思路
既然没什么思路,我们不妨先特判下不合法的情况
1.存在\(dis[i][k]+dis[k][j]<dis[i][j]\)
2.\(dis[i][j]!=dis[j][i]\)
3.\(dis[i][i]!=0\)
QwQ
看了题解,才知道是一个dp
首先,我们考虑不存在0边的情况,那么对于一条边存在\(dis[i][k]+dis[k][j]==dis[i][j]\),那么\(dis[i][j]\)这个边就可以取\([dis[i][j],k]\)这个范围内的值,那么它的贡献就是\(k-dis+1\)对吧
QwQ然后我们考虑
如果有0边怎么办QwQ
我们可以把所有的0边缩成一个点,先算完每个块每部的贡献,然后再算块外,最后合起来。
QwQ这是一个很值得纪念的一个小套路,我们令\(g[i]\)表示i个点相连的方案数,QwQ因为i个点一共有\(\frac{i(i-1)}{2}\),每个点有\(k+1\)种取值QwQ,所以\(g[i]\)显然等于\((k+1)^{\frac{i(i-1)}{2}}\), QwQ,然后我们令\(f[i]\)表示i个点互相相连,且互相的权值为0的合法方案数
我在计算\(f[i]\)的时候,用的是总方案-不合法的方案$$f[i]=g[i]-\sum_{j=1}^{i-1} C_{i-1}^{j-1} \times f[j] \times g[i-j] \times k^{j(i-j)}$$
QwQ这里的思路是 枚举有几个点相连成0团,然后钦定1个点为合法点,然后乘一乘组合数(一定要乘!!!), 乘上\(f[j]\)(因为0团内的点相连的方案必须是合法的。)再乘上\(g[i-j]\) 另外的点内部的连接是无所谓的。最后乘上\(k^{j(i-j)}\)因为两个团之间的连边不能是0,其余都可以
QwQ那么处理完这个数组呢 0团的内部的方案数也就能迎刃而解了
那么我们应该怎么把它合并起来呢?
对于两个团(点)\(i,j\)
如果存在\(a[i][k]+a[k][j]==a[i][j]\),那么\(ans=ans*qsm(k-a[i][j]+1,size[i]*size[j])\)
如果不存在...是不是没有贡献呢?
并不是这样的
如果不存在的话。我只需要枚举两个团之间至少有一条边是\(a[i][j]\)就行,这里运用了一个小tips
\(ans*(qsm(k-a[i][j]+1,size[i]*size[j])-qsm(k-a[i][j],size[i]*size[j])\)就是所有的情况减去,所有的边都是大于\(a[i][j]\)情况
总之这个题的细节还是很多很多的
直接上代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#define ll long long
using namespace std;
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
const ll mod = 998244353;
const int maxn = 510;
int fa[maxn];
ll f[maxn],g[maxn];
int n;
ll k;
ll ans=1;
ll a[maxn][maxn];
ll c[maxn][maxn];
int size[maxn];
int find(int x)
{
if (fa[x]!=x) fa[x]=find(fa[x]);
return fa[x];
}
ll qsm(ll i,ll j)
{
ll ans=1;
while (j)
{
if (j&1) ans=ans*i%mod;
i=i*i%mod;
j>>=1;
}
return ans;
}
void init(int n)
{
for (int i=0;i<=n;i++)
c[i][i]=1,c[i][0]=1;
for (int i=1;i<=n;i++)
for (int j=1;j<i;j++)
c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
}
int main()
{
scanf("%d%lld",&n,&k);
init(n);
for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) a[i][j]=read();
bool flag = true;
for (register int i=1;i<=n;++i)
for (register int j=1;j<=n;++j)
{
if (a[i][i]!=0) flag=false;
if (a[i][j]!=a[j][i]) flag=false;
if (a[i][j]>k) flag=false;
}
for (register int k=1;k<=n;++k)
for (register int i=1;i<=n;++i)
for (register int j=1;j<=n;++j)
if (a[i][j]>a[i][k]+a[k][j]) flag=false;
if (!flag) {
cout<<0;
return 0;
}
f[0]=g[0]=1;
for (register int i=1;i<=n;i++){
g[i]=qsm(k+1,i*(i-1)/2);
f[i]=g[i];
for (register int j=1;j<=i-1;j++)
{
f[i]=(f[i]-c[i-1][j-1]*f[j]%mod*g[i-j]%mod*qsm(k,j*(i-j))%mod+mod)%mod;
}
}
for (int i=1;i<=n;i++) fa[i]=i;
for (int i=1;i<=n;i++)
{
for (int j=1;j<=n;j++)
{
if (i==j) continue;
if (!a[i][j]) fa[find(i)]=fa[find(j)];
}
}
for (int i=1;i<=n;i++) size[find(i)]++;
for (int i=1;i<=n;i++) if (fa[i]==i) for (int j=i+1;j<=n;j++) if (fa[j]==j)
{
bool ok=0;
for (int p=1;p<=n;p++)
{
if (fa[p]!=p) continue;
if (p==i || p==j) continue;
if (a[i][p]+a[p][j]==a[i][j]) {
ok=1;break;
}
}
if (ok) ans=ans*qsm(k-a[i][j]+1,size[i]*size[j])%mod;
else ans=ans*(qsm(k-a[i][j]+1,size[i]*size[j])-qsm(k-a[i][j],size[i]*size[j])+mod)%mod;
}
for (int i=1;i<=n;i++) if (fa[i]==i) ans=ans*f[size[i]]%mod;
cout<<ans;
return 0;
}