解析
很妙的一道题
看这两个南辕北辙的标签就知道这题不简单
看见dp思路还是得打开
一开始其实想到按边权排序了
但卡在了重构树上
遇到dp一定要敢想
勇于和图论等结合
考虑正解
按照边权升序排序
依次加边到图中
并查集维护连通性和集合内的边数
发现,一个联通块合法当且仅当它在某个时刻是当前图的一个团
(这个概念还是现查的…)
考虑建一个重构树
显然所以合法的区间在树上对应的叶子的dfs序是连续的
可以通过dfs求出合法的区间,并记录哪些区间可以进行转移
最后进行一遍朴素dp即可
由于合法的区间只会有On个,所以均摊复杂度是n2n^2n2
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1550;
const int mod=998244353;
#define ll long long
ll read(){ll x=0,f=1;char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();};while(isdigit(c)){x=x*10+c-'0';c=getchar();};return x*f;
}int n,m;
struct node{int to,nxt;
}p[N<<1];
int fi[N<<1],cnt;
inline void addline(int x,int y){p[++cnt]=(node){y,fi[x]};fi[x]=cnt;return;
}struct edge{int x,y,w;
}e[N*N];
bool cmp(edge a,edge b){return a.w<b.w;
}
int fa[N<<1],siz[N<<1],num[N<<1],tot,id,ls[N<<1],rs[N<<1],tim;
bool jd[N][N];
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
void dfs(int x){if(fi[x]==-1){siz[x]=1;ls[x]=rs[x]=++tim;jd[tim][tim]=1;return;}for(int i=fi[x];~i;i=p[i].nxt){int to=p[i].to;dfs(to);siz[x]+=siz[to];if(!ls[x]) ls[x]=ls[to];else rs[x]=rs[to];}int o=rs[x]-ls[x]+1;jd[ls[x]][rs[x]]=num[x]==o*(o-1)/2;//printf("x=%d ls=%d rs=%d num=%d\n",x,ls[x],rs[x],num[x]);return;
}
ll dp[N][N];
int main(){#ifndef ONLINE_JUDGE//freopen("a.in","r",stdin);//freopen("a.out","w",stdout);#endifmemset(fi,-1,sizeof(fi));cnt=-1;n=read();id=n;for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){int x=read();if(i<j) e[++tot]=(edge){i,j,x};}}for(int i=1;i<=n*2;i++) fa[i]=i;sort(e+1,e+1+tot,cmp);for(int i=1;i<=tot;i++){int x=e[i].x,y=e[i].y;x=find(x),y=find(y);if(x==y){num[x]++;continue;}++id;fa[x]=fa[y]=id;num[id]=num[x]+num[y]+1;addline(id,x);addline(id,y);}dfs(id);dp[0][0]=1;for(int i=1;i<=n;i++){for(int k=1;k<=i;k++){if(!jd[k][i]) continue;for(int j=1;j<=n;j++){(dp[i][j]+=dp[k-1][j-1])%=mod;}}}for(int i=1;i<=n;i++) printf("%lld ",dp[n][i]);return 0;
}
/**/