3 条题解
-
5
好神仙的题目。赛时胡了一个状态和转移都和官解不同的做法,得到了 的优秀复杂度。卡了一场常卡进了 分。这个做法和官解关系不大,并且很难进行最后的优化部分,所以在此不再赘述。
首先考虑 的情况。考虑记录一些状态能够描述子树内的选择方案, 表示整个子树没有被覆盖过, 表示子树内部有点被覆盖过并且子树外的点还能被覆盖, 表示子树内部有点被覆盖过并且子树外的点不能被覆盖了。考虑转移,需要把转移描述为只和 有关的形式才能较为简单的扩展到 的情况。发现对于 的转移,很难描述为 的形式,因为需要出现两个子树为 或者根节点被选择才能转移到 。所以考虑记录辅助状态 表示出现过至少 次 的方案。那么转移有以下 种:
上面没有出现过的转移为不合法或者不存在对应状态。这么转移之后再考虑和根节点是否选择合并的转移,那么有:
$$(0,0)\rightarrow 0 \ \ (1,0)\rightarrow 1 \ \ (2,0)\rightarrow 2 \ \ (3,0)\rightarrow 3 \ \ $$$$(0,1)\rightarrow 3 \ \ (1,1)\rightarrow3 \ \ (3,1)\rightarrow 3 \ \ $$转移的同时计入 两个数组的贡献。最后将 状态放到 两种状态即可。因为 状态对应的状态可以封口也可以不封口。复杂度 。
考虑对于 的情况,每一位暴力枚举上面的 种转移,第一部分的转移复杂度是 的。对于复合根节点情况的部分,暴力枚举根节点状态显然不优,可以类似 FMT 的对每一位依次进行变换,也就是逐位枚举根节点状态并处理这一位变换后的位置。复杂度为 。对于 状态的下放可以用类似的做法也做到 。复杂度 ,视常数可以获得 分。
考虑优化,目前的瓶颈在于 的部分。一个很神秘的做法是考虑到如果没有辅助状态 ,那么转移只有 。所以考虑枚举儿子的一些位置的状态钦定为 ,由于对于 的转移是和 复合之后仍然为 ,所以为 的位可以让它的值为对应位为 的和。类似 OR 卷积的 FWT,经过一次正变换之后为 的位置真实值可以为 或 。然后对变换之后的部分进行 的转移,但是多了 的状态,由于经过了变换,只需要加入 的转移。这部分转移的复杂度是 的。对于转移之后 的位置,他们是从 转移过来的,所以真实值可能是 ,所以要进行一次类似 OR 卷积的 IFWT 让他变成真实值为 的值。FWT 和 IFWT 的复杂度是 ,所以总的复杂度就是 ,可以通过。
#include<bits/stdc++.h> using namespace std; struct edge{int v,nxt;}e[205]; int n,m,u,v,cnt,h[105],w[105][256],p[105][8],dp[105][1<<16],num,tmp[1<<16]; void add(int u,int v){e[++cnt]={v,h[u]};h[u]=cnt;} const int mod=998244353; void Add(int &x,int y){x=(x+y>=mod?x+y-mod:x+y);} struct node{int x,y,z;}go[2000005]; void init(int k,int x,int y,int z) { if(k==m){go[++num]={x,y,z};return;} init(k+1,x,y,z); init(k+1,x,y|(1<<(k<<1)),z|(1<<(k<<1))); init(k+1,x|(1<<(k<<1)),y,z|(1<<(k<<1))); init(k+1,x|(2<<(k<<1)),y,z|(2<<(k<<1))); init(k+1,x,y|(2<<(k<<1)),z|(2<<(k<<1))); init(k+1,x|(3<<(k<<1)),y|(3<<(k<<1)),z|(3<<(k<<1))); } void fwt(int *a) { for(int i=0;i<m;i++) { for(int s=0;s<(1<<(m<<1));s++) { int c=(s>>(i<<1))&3; if(c==0)Add(a[s+(3<<(i<<1))],a[s]); else if(c==1)Add(a[s+(2<<(i<<1))],a[s]); } } } void ifwt(int *a) { for(int i=0;i<m;i++) { for(int s=0;s<(1<<(m<<1));s++) { int c=(s>>(i<<1))&3; if(c==3)Add(a[s],mod-a[s-(3<<(i<<1))]),Add(a[s],mod-a[s-(2<<(i<<1))]); } } } void dfs(int u,int fa) { dp[u][0]=1; for(int i=h[u];i;i=e[i].nxt) { int v=e[i].v; if(v==fa)continue; dfs(v,u); for(int s=0;s<(1<<(m<<1));s++)tmp[s]=dp[u][s],dp[u][s]=0; fwt(tmp);fwt(dp[v]); for(int s=1;s<=num;s++)Add(dp[u][go[s].z],1ll*tmp[go[s].x]*dp[v][go[s].y]%mod); ifwt(dp[u]); } for(int i=0;i<m;i++) { for(int s=0;s<(1<<(m<<1));s++)tmp[s]=dp[u][s],dp[u][s]=0; for(int s=0;s<(1<<(m<<1));s++) { int c=(s>>(i<<1))&3; if(c==0) { Add(dp[u][s],1ll*tmp[s]*(mod+1-p[u][i])%mod); Add(dp[u][s|(3<<(i<<1))],1ll*tmp[s]*p[u][i]%mod); } else if(c==1) { Add(dp[u][s],1ll*tmp[s]*(mod+1-p[u][i])%mod); Add(dp[u][s|(2<<(i<<1))],1ll*tmp[s]*p[u][i]%mod); } else if(c==2) { Add(dp[u][s],1ll*tmp[s]*(mod+1-p[u][i])%mod); } else { Add(dp[u][s],tmp[s]); } } } for(int s=0;s<(1<<(m<<1));s++) { int ns=0; for(int i=0;i<m;i++)if((s>>(i<<1))&1)ns|=(1<<i); dp[u][s]=1ll*dp[u][s]*w[u][ns]%mod; } for(int i=0;i<m;i++) { for(int s=0;s<(1<<(m<<1));s++)tmp[s]=dp[u][s],dp[u][s]=0; for(int s=0;s<(1<<(m<<1));s++) { if(((s>>(i<<1))&3)==3) { Add(dp[u][s-(1<<(i<<1))],tmp[s]); Add(dp[u][s-(2<<(i<<1))],tmp[s]); } else Add(dp[u][s],tmp[s]); } } } int main() { //freopen("e.in","r",stdin); cin.tie(0)->sync_with_stdio(0); cin>>n>>m; for(int i=1;i<n;i++) { cin>>u>>v; add(u,v);add(v,u); } for(int i=0;i<m;i++)for(int j=1;j<=n;j++)cin>>p[j][i]; for(int i=1;i<=n;i++) { for(int s=0;s<(1<<m);s++)cin>>w[i][s]; } init(0,0,0,0);dfs(1,0); int ans=0; for(int s=0;s<(1<<(m<<1));s++) { int flag=1; for(int i=0;i<m;i++)flag&=(((s>>(i<<1))&3)!=1); if(flag)Add(ans,dp[1][s]); } cout<<ans; return 0; }
信息
- ID
- 11
- 时间
- 2000ms
- 内存
- 512MiB
- 难度
- 9
- 标签
- 递交数
- 56
- 已通过
- 10
- 上传者