6377. 【NOIP2019模拟2019.10.05】幽曲[埋骨于弘川]
题目
题目大意
有个无限长的数列\({a_n}\),\(a_1=1\),\(a_n=a_{n-1}+maxdight_k(a_{n-1})\)
\(maxdight_k(x)\)表示\(x\)在\(k\)进制下的所有位上的数的最大值。
然后有一棵带点权(点权在\([0,k)\))的树,选取某个点集,满足点集中所有点的父亲都在点集中。
按照先序遍历遍历整个点集,点权连成一个整数,如果整数在数列中,那么就会有\(1\)的贡献。
问所有合法点集的总贡献。
正解
我们先考虑一下如何判断一个数是否在数列中。
显然,\(maxdight_k(x)\in [0,k-1)\)
这就意味着,去掉各位后,所有的自然数都会被遍历到。
我们可以构造出一个在数列中的和这个数都除去个位一样的最小的数。
(假设个位编号为\(1\))
接下来就有了这样的\(DP\):设\(f_{i,p,a}\)表示,第\(2\)到\(i\)位都是\(0\),\(i+2\)以及更高位已经被确定了,当前除个位外的最大值为\(p\),个位为\(a\)的时候,使\(i+1\)为加一后个位会变成多少。
当\(i=1\)时,可以暴力计算。当\(i>1\)时,可以通过\(f_{i-1}\)位的状态跳\(k\)次得到。
\(g_{i,p,x,a}\)表示\(i\)位从\(0\)变成\(x\),更高位的最大值为\(p\),个位原来为\(a\),将要变成多少。
转移类似。
于是判断的方法就显而易见了:一开始钦定\(p=0\),\(a=1\),然后从高位往第\(2\)位填数,这时候除了个位之外都一样了。于是再看看是否可以从目前构造的数变成要判断的数。
在树上模拟这个判断过程:设\(dp_{i,j,p,x}\)表示转移到\(i\)点,判定到第\(j\)位,除个位外的最大值为\(p\),个位为\(x\)的方案数。
然而这样似乎依然会TLE。然后我们发现,按照\(dfs\)序转移之后,转移到某个点的区间是连续的一段,所以用前缀和优化转移就可以了。
代码(未AC)
调了好久没有调出来……也懒得调了……
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 510
#define mo 998244353
#define ll long long
int n,K;
int d[N];
int to[N][N],nto[N];
int fa[N],dfn[N],nowdfn,q[N];
void init(int x){
q[dfn[x]=++nowdfn]=x;
for (int i=0;i<nto[x];++i){
int y=to[x][i];
if (y!=fa[x])
fa[y]=x,init(y);
}
}
int f[N][10][10],g[N][10][10][10];
ll dp[N][10][10][10],sum[N][10][10][10];
bool ok[10][10][10];
inline void upd(ll &a,ll b){a=(a+b)%mo;}
inline void getsum(int i){
for (int j=2;j<=n;++j)
for (int p=0;p<K;++p)
for (int x=0;x<K;++x)
sum[i][j][p][x]=(sum[i-1][j][p][x]+dp[i][j][p][x])%mo;
}
int main(){
// freopen("in.txt","r",stdin);
freopen("buried.in","r",stdin);
freopen("buried.out","w",stdout);
scanf("%d%d",&n,&K);
for (int i=1;i<=n;++i)
scanf("%d",&d[i]);
for (int i=1;i<n;++i){
int u,v;
scanf("%d%d",&u,&v);
to[u][nto[u]++]=v;
to[v][nto[v]++]=u;
}
for (int i=1;i<=n;++i)
sort(to[i],to[i]+nto[i]);
init(1);
for (int p=0;p<K;++p)
for (int a=0;a<K;++a){
if (!p && !a)
continue;
int x=a;
for (;x<K;x+=max(p,x));
f[1][p][a]=x-K;
}
for (int i=2;i<=n;++i)
for (int p=0;p<K;++p)
for (int a=0;a<K;++a){
int p_=p,a_=a;
for (int x=0;x<K;++x){
g[i][p][x][a]=a_;
a_=f[i-1][p_][a_];
p_=max(p_,x+1);
}
f[i][p][a]=a_;
}
for (int p=0;p<K;++p)
for (int a=0;a<K;++a){
ok[p][a][a]=1;
if (!p && !a)
continue;
for (int x=a+max(p,a);x<K;x+=max(p,x))
ok[p][a][x]=1;
}
ll ans=ok[0][1][d[1]];
for (int j=2;j<=n;++j){
int p_=max(0,d[1]),y=g[j][0][d[1]][1];
dp[1][j][p_][y]=1;
}
getsum(1);
for (int i=2;i<=n;++i){
for (int p=0;p<K;++p)
for (int x=0;x<K;++x)
if (ok[p][x][d[q[i]]])
upd(ans,sum[i-1][2][p][x]-sum[dfn[fa[q[i]]]-1][2][p][x]+mo);
for (int j=2;j<=n;++j)
for (int p=0;p<K;++p)
for (int x=0;x<K;++x){
int p_=max(p,d[q[i]]),y=g[j][p][d[q[i]]][x];
upd(dp[i][j][p_][y],sum[i-1][j+1][p][x]-sum[dfn[fa[q[i]]]-1][j+1][p][x]+mo);
}
// for (int j=2;j<=n;++j)
// for (int p=0;p<K;++p)
// for (int x=0;x<K;++x)
// if (dp[i][j][p][x])
// printf("dp[%d][%d][%d][%d]=%lld\n",i,j,p,x,dp[i][j][p][x]);
getsum(i);
}
printf("%lld\n",ans);
return 0;
}
总结
\(DP\)的时候还有强行构造的骚方法……
看来我还是太菜了……
My vegetable has exploded!