「JSOI2019」神经网络

「JSOI2019」神经网络

考虑一个合法的哈密顿路可以表示为什么样子:

按照不同树的编号,分割为一段段,相邻两段属于不同树

同时,如果最后一段和第一段同编号,将最后一段移动到第一段前面

由此,一个哈密顿路可以由唯一表示:

1号点在第一个段中,此后每一段和上一个属于不同树,且最后一段不属于1树

由此,问题分解为两部分:

Part1 求解树路径分段

考虑树形\(dp\)求解,每个点记录\(dp_{i,j,0/1}\)表示当前\(i\)子树内已经产生\(j\)条路径,\(i\)自己是否可以向父亲连边

容易用类似树形背包的方式合并,每次决策儿子是否连接到自己上面

注意:一个长度\(>1\)的段,需要考虑正反方向的排放

复杂度为\(O(\sum k_i^2)\)

\[\ \]

Part2 合并每棵树的段

相邻两段不同色,考虑容斥求解

枚举这棵树中的\(i\)个段自己生成了\(j\)个不合法的相邻,\(i\)个段合并生成\(i-j\)个段,且乘上容斥系数\((-1)^j\)

\(i\)个并掉\(j\)个,方案数计算如下:

先把\(i\)个排好,乘上\(i!\),然后选择\(j\)个间隔合并掉\(\binom{i-1}{j}\),然后对于剩下的\(i-j\)个元素无序,需要除掉\((i-j)!\)

背包合并容斥之后的结果,对于当前的\(i\)个元素,任意排列即可

然而上面是理想情况,还需要考虑\(1\)号元素不能被排列,要强制最后一个段不是1树的段

这一部分,在树1的容斥以及最终背包合并时特殊处理即可,即少排列一个元素,且最后合并时先选一个放在最后面

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef pair <int,int> Pii;
#define reg register
#define mp make_pair
#define pb push_back
#define Mod1(x) ((x>=P)&&(x-=P))
#define Mod2(x) ((x<0)&&(x+=P))
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }

char IO;
template <class T=int> T rd(){
	T s=0; int f=0;
	while(!isdigit(IO=getchar())) f|=IO==‘-‘;
	do s=(s<<1)+(s<<3)+(IO^‘0‘);
	while(isdigit(IO=getchar()));
	return f?-s:s;
}

const int N=5e3+10,P=998244353;

int n,m;
int I[N],J[N],C[N][N];
ll qpow(ll x,ll k=P-2) {
	ll res=1;
	for(;k;k>>=1,x=x*x%P) if(k&1) res=res*x%P;
	return res;
}

struct Edge{
	int to,nxt;
} e[N<<1];
int head[N],ecnt;
void AddEdge(int u,int v){
	e[++ecnt]=(Edge){v,head[u]};
	head[u]=ecnt;
}
int dp[N][N][2]; // 0,1 是否向上连
int G[N][3],H[N][3],sz[N];

void dfs(int u,int f){
	sz[u]=0;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==f) continue;
		dfs(v,u);
	}
	G[0][0]=1,G[0][1]=G[0][2]=0;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==f) continue;
		rep(i,0,sz[u]+sz[v]) rep(j,0,2) H[i][j]=G[i][j],G[i][j]=0;
		rep(i,0,sz[u]) rep(a,0,2) if(H[i][a]) rep(j,0,sz[v]) rep(b,0,min(1,2-a)) G[i+j][a+b]=(G[i+j][a+b]+1ll*H[i][a]*dp[v][j][b])%P;
		sz[u]+=sz[v];
	}
	rep(i,0,sz[u]+1) dp[u][i][0]=dp[u][i][1]=0;
	rep(i,0,sz[u]) {
		dp[u][i+1][0]=(0ll+dp[u][i+1][0]+G[i][0]+2*G[i][1]+2*G[i][2])%P; // 长度>1的段可以翻转
		dp[u][i][1]=(0ll+dp[u][i][1]+G[i][0]+G[i][1])%P; // 如果连了两个儿子,就无法向上连了
	}
	sz[u]++;
}

int F[N],T[N];
void Get(){
	n=rd();
	rep(i,1,n) head[i]=0;
	ecnt=0;
	rep(i,2,n) {
		int u=rd(),v=rd();
		AddEdge(u,v),AddEdge(v,u);
	}
	dfs(1,0);
	rep(i,1,n) {
		F[i]=dp[1][i][0],T[i]=0;
		ll t=1ll*F[i]*J[i]%P;
		rep(j,1,i) {
			T[j]=(T[j]+((i-j)&1?P-1:1)*t%P*C[i-1][i-j]%P*I[j])%P;
		}
	}
}

int S[N],c;

int main(){
	rep(i,J[0]=1,N-1) J[i]=1ll*J[i-1]*i%P;
	I[N-1]=qpow(J[N-1]);
	drep(i,N-1,1) I[i-1]=1ll*I[i]*i%P;
	rep(i,0,N-1) rep(j,C[i][0]=1,i) C[i][j]=C[i-1][j]+C[i-1][j-1],Mod1(C[i][j]);
	m=rd();
	if(m==1) return n=rd(),printf("%d\n",n<=2),0;
	S[0]=1;
	rep(t,1,m-1) {
		Get();
		drep(i,n+c,0) {
			S[i]=0;
			rep(j,1,min(i,n)) S[i]=(S[i]+1ll*S[i-j]*T[j])%P;
		}
		c+=n;
	}
	Get();
	rep(i,1,n) {
		F[i]=dp[1][i][0],T[i]=0;
		ll t=1ll*F[i]*J[i-1]%P; 
		// 特殊处理,不允许排列第一段
		rep(j,1,i) T[j]=(T[j]+((i-j)&1?P-1:1)*t%P*C[i-1][i-j]%P*I[j-1])%P;
	}
	int ans=0;
	// 不允许改变第一段的位置
	// 且强制最后一段不能属于第一棵树
	rep(i,1,c) if(S[i]) rep(j,1,n) if(T[j]) {
		// 强制前面的最后一个在最后
		int t=1ll*J[i]*J[j-1]%P*C[i-1+j-1][j-1]%P;
		ans=(ans+1ll*t*S[i]%P*T[j])%P;
	}
	printf("%d\n",ans);
}

「JSOI2019」神经网络

上一篇:在js中加载css


下一篇:CSS Hack