[CodeChef Nov Challenge]Max-digit Tree

探讨在给定有根树与特定序列增长规则下,寻找所有包含根节点的连通子图,其DFS序数值能在序列中找到对应项的计数问题。通过动态规划与递归构建函数,实现高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

给出以1为根的有根树,每个点上有个数字di (d1!=0)
定义一个包含1的连通块对应的数是其按编号从小到大做dfs序得到的序列中,di按顺序连接起来形成的数字
定义序列a,满足a1=1,ai=ai−1+maxdigit(ai−1)a_1=1,a_i=a_{i-1}+maxdigit(a_{i-1})a1=1,ai=ai1+maxdigit(ai1)
其中maxdigit(n)是n在十进制下最大的那个数位中的数
求有多少个包含1的连通块对应的数字在a中出现
n<=500

Solution

考虑如何判断一个数是否合法
定义F[i][p][x]表示当前数的后i位除个位全是0,个位为x,前面的最大值为p,使第i位产生1的进位之后个位会变成多少
F[i]可以从F[i-1]递归得到,加10个1就好了
那么我们也可以得到判定一个数是否合法的方法:一位一位放,记录最后一位,利用F不断跳
当然可以预处理trs[i][k][p][x]表示第i位放k,前面的位的最大值为p,个位为x,其余全为0,之后个位会变成什么
那么Dp的时候我们只需要记录i,p,x即可实现O(1)转移
考虑dfs序的限制,显然一个点是由dfs序上的一个区间转移过来的,用前缀和优化一下
复杂度O((n*10)^2)

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

const int N=505,Mo=1e9+7;

void inc(int &x,int y) {x=x+y>=Mo?x+y-Mo:x+y;}

int ty,n,x,y,a[N],nx[N][10][10],trs[N][10][10][10],to[N][N];
int f[N][N][10][10],sum[N][N][10][10];
int w[N],fa[N],sz[N],dfn[N],d[N],tot;
bool vis[10];

bool in(int n) {
	a[0]=0;
	for(;n;n/=10) a[++a[0]]=n%10;
	int x=1,mx=0;
	fd(i,a[0],1) {
		x=trs[i][mx][a[i]][x];
		mx=max(mx,a[i]);
	}
	return x!=-1;
}

void dfs(int x,int y) {
	dfn[++tot]=x;sz[x]=1;fa[x]=y;w[x]=tot;
	fo(i,1,to[x][0]) if (to[x][i]!=y) dfs(to[x][i],x),sz[x]+=sz[to[x][i]];
}

int main() {	
	fo(p,0,9) 
		fo(x,0,9) {
			if (!p&&!x) continue;
			int y=x;for(;y<10;y+=max(y,p));
			nx[1][p][x]=y%10;
		}
	fo(i,2,500)
		fo(p,0,9)
			fo(x,0,9) {
				if (!p&&!x) continue;
				int y=x;
				fo(j,0,9) y=nx[i-1][max(p,j)][y];
				nx[i][p][x]=y;
			}
	fo(i,1,500)
		fo(p,0,9)
			fo(x,0,9) {
				if (!p&&!x) continue;
				if (i>1) {
					int y=x;
					trs[i][p][0][x]=y;
					fo(j,0,8) {
						y=nx[i-1][max(p,j)][y];
						trs[i][p][j+1][x]=y;
					}
				} else {
					int y=x;
					fo(j,0,9) vis[j]=0;
					for(;y<10;y+=max(y,p)) vis[y]=1;
					fo(j,0,9) 
						if (vis[j]) trs[i][p][j][x]=j;
						else trs[i][p][j][x]=-1;
				}
			}
	for(scanf("%d",&ty);ty;ty--) {
		scanf("%d",&n);
		fo(i,1,n) to[i][0]=0;
		fo(i,1,n-1) {
			scanf("%d%d",&x,&y);
			to[x][++to[x][0]]=y;
			to[y][++to[y][0]]=x;
		}
		fo(i,1,n) sort(to[i]+1,to[i]+to[i][0]+1);
		fo(i,1,n) scanf("%d",&d[i]);
		tot=0;dfs(1,0);
		int ans=0;
		fo(i,1,n) fo(j,1,n-i+1) fo(p,0,9) fo(x,0,9) f[i][j][p][x]=sum[i][j][p][x]=0;
		fo(i,1,n) if (trs[i][0][d[1]][1]!=-1) f[1][i][d[1]][trs[i][0][d[1]][1]]=sum[1][i][d[1]][trs[i][0][d[1]][1]]=1;
		fo(i,2,n) {
			fo(j,1,n-i+1)
				fo(p,0,9)
					fo(x,0,9) {
						int y=dfn[i];
						if (trs[j][p][d[y]][x]!=-1) inc(f[i][j][max(d[y],p)][trs[j][p][d[y]][x]],(sum[i-1][j+1][p][x]-sum[w[fa[y]]-1][j+1][p][x]+Mo)%Mo);
					}
			fo(j,1,n-i+1) fo(p,0,9) fo(x,0,9) sum[i][j][p][x]=(sum[i-1][j][p][x]+f[i][j][p][x])%Mo;
		}
		fo(i,1,n) fo(p,0,9) fo(x,0,9) inc(ans,f[i][1][p][x]);
		printf("%d\n",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值