UOJ #279. 【UTR #2】题目交流通道
题目叙述
给定 \(n\) 个点的图和两两点之间的最短路径长度,并且每条边权值在 \([0,k]\) 内,求标边的长度的方案数。
题解
设两个点 \(u,v\) 之间的边权值为 \(w_{u,v}\) ,最短路径长度为 \(d_{u,v}\)。
如果 \(d_{u,v}\) 可以被其他一系列最短路径替代,比如 \(d_{u,v}=d_{u,1}+d_{1,2}+\cdots+d_{x,v}\) ,那么 \(d_{u,v}\) 实际上是不重要的。
而那些重要的点对 \(u,v\) ,一定是连接了一条边权为 \(w_{u,v}\) 的边。
但是这些事情在存在边权为 0 的边的时候显然存在问题。
于是考虑将最短路长度为 0 那些点构成的联通块,全都缩起来。两个联通块之间,如果是重要的,那么只要有一条是那个权值,其他边的权值不小于这个就可以了。因此直接 \(k^c-w^c\) 即可。
剩下的问题就变成边权为 0 的点构成的联通块内部的方案数了。这明显是一个 \(n\) 个点联通图计数。
设 \(g_n\) 表示 \(n\) 个点的图的方案数(\(2^{binom{n}{2}}\)),\(f_n\) 表示 \(n\) 个点的联通图方案数。
那么有:
\[f_n=g_n-\sum_{i=1}^{n-1}f_ig_{n-i}
\]
这可以通过枚举第一个点的联通块大小的方式解释。
最后只要把这些东西全乘起来即可。别忘了判断一些无解的情况,比如不满足三角不等式,不满足 \(d_{u,v}=d_{v,u}\) 之类的事情。
总结
- \(n\) 个点联通图计数这样的经典容斥。“全都联通”转化为“去掉不联通”。
代码
#include <cstdio>
#include <iostream>
#include <vector>
#define macro_expand(x) #x
#define print_macro(x) printf("%s\n",macro_expand(x))
#define FOR(i,l,r) for(int i=(l);i<=(r);++i)
#define ROF(i,r,l) for(int i=(r);i>=(l);--i)
using namespace std;
typedef long long LL;
const int Mod=998244353;
int add(int &x,int y){return ((x+=y)>=Mod)?(x-=Mod):x;}
int dec(int &x,int y){return ((x-=y)<0)?(x+=Mod):x;}
int ad(int x,int y){return ((x+y)>=Mod)?(x+y-Mod):(x+y);}
int dc(int x,int y){return ((x-y)<0)?(x-y+Mod):(x-y);}
int ml(int x,int y){return (LL)x*y%Mod;}
int ksm(int x,int y){
int ret=1;
for(;y;y>>=1,x=ml(x,x))if(y&1)ret=ml(ret,x);
return ret;
}
bool chkmin(int &x,const int &y){return (x>y)?(x=y,1):0;}
const int MN=405;
int N,K,D[MN][MN],fa[MN],d[MN][MN];
int get(int u){return (u==fa[u])?u:(fa[u]=get(fa[u]));}
void merge(int u,int v){
u=get(u),v=get(v);
if(u!=v)fa[u]=v;
}
vector<int> node[MN];
int f[MN];
int fac[MN],caf[MN];
void init(){
fac[0]=1;
for(int i=1;i<=N;++i)fac[i]=ml(fac[i-1],i);
caf[N]=ksm(fac[N],Mod-2);
for(int i=N-1;i>=0;--i)caf[i]=ml(caf[i+1],i+1);
}
int B(int u,int d){
if(d<0||u<d)return 0;
return ml(fac[u],ml(caf[d],caf[u-d]));
}
int main(){
freopen("pipeline.in","r",stdin);
freopen("pipeline.out","w",stdout);
scanf("%d%d",&N,&K);
init();
FOR(i,1,N)FOR(j,1,N)scanf("%d",&D[i][j]);
FOR(i,1,N)FOR(j,i+1,N)FOR(k,1,N)if(k!=i&&k!=j&&D[i][k]+D[k][j]<D[i][j]){
printf("0\n");
return 0;
}
FOR(i,1,N)FOR(j,1,N)if(D[i][j]!=D[j][i]){
printf("0\n");
return 0;
}
FOR(i,1,N)FOR(j,1,N)if(D[i][i]!=0){
printf("0\n");
return 0;
}
// 注意特判
FOR(i,1,N)fa[i]=i;
FOR(i,1,N)FOR(j,1,N)if(!D[i][j])merge(i,j);
FOR(i,1,N)node[get(i)].push_back(i);
FOR(i,1,N)FOR(j,1,N)d[i][j]=1e9+1;
FOR(i,1,N)if(fa[i]==i)FOR(j,1,N)if(fa[j]==j){
for(int x:node[i])for(int y:node[j])
chkmin(d[i][j],D[x][y]);
}
int ans=1;
FOR(i,1,N)if(fa[i]==i)FOR(j,i+1,N)if(fa[j]==j){
bool can=0;
FOR(k,1,N)if(fa[k]==k&&k!=i&&k!=j&&D[i][k]+D[k][j]==D[i][j]){can=1;break;}
#define t (K-D[i][j])
#define si ((int)node[i].size())
#define sj ((int)node[j].size())
if(can)ans=ml(ans,ksm((t+1)%Mod,si*sj));
else{
if(K<D[i][j]){
printf("0\n");
return 0;
}
// 注意特判!
ans=ml(ans,dc(ksm((t+1)%Mod,si*sj),ksm(t,si*sj)));
}
#undef t
#undef si
#undef sj
}
for(int i=1;i<=N;++i){
f[i]=ksm(K+1,i*(i-1)/2);
for(int j=1;j<=i-1;++j){
#define t (i-j)
dec(f[i],ml(f[j],ml(ksm(K+1,t*(t-1)/2),ml(B(i-1,j-1),ksm(K,(i-j)*j)))));
#undef t
// 注意算式
}
}
for(int i=1;i<=N;++i)if(fa[i]==i)ans=ml(ans,f[node[i].size()]);
printf("%d\n",ans);
fclose(stdin);
fclose(stdout);
return 0;
}