SNOI省选模拟赛Round1 T1 Travel TreeDP

题目大意:

给你一颗n个节点的树,每条边有边权。

Q组询问,每次询问u,k,输出从u点出发在mod k意义下的最长路。

2<=n<=3000,1<=Q<=1e5,2<=k<=100

分析:

很容易想到预处理出dis[i][j]表示i到j的距离,对于每次询问O(n)查询,时间复杂度O(n^2+Qn),不可做。

考虑优化,用dp[u][k]表示从u出发在mod k意义下的最长路,对于每次询问O(1)查询,时间复杂度为O(n*n*k+Q),也不行。

上述两个算法超时的主要原因是没有对已有信息的再次利用,而是每次都重新开始处理信息。

那么定义dp[i][j][k]表示从i点出发,在mod j意义下长度为k的路径是否存在(方案数)。

可以先从根节点dfs,记录从根节点出发的dp值,转移即为dp[x][j][(k+val[i])%j]+=dp[to[i]][j][k]。

然后再进行一遍dfs,对于一个不是根节点的点,它的子树的dp值显然是对的,但从该节点往上走的情况还没统计,因为是dfs更新,所以该节点的父亲已经更新过了,那么该节点的dp值加上它父节点的dp值即可。但其父节点的dp值同样包含了该节点的子树的dp值,所以在更新前其父节点要先减去改节点的dp值,更新完后再加回来。

对于每组询问,i从k-1枚举到0,看长度为i的路径是否存在,输出即可。处理每次询问最大是k的复杂度。

该算法复杂度是O(n*k*k+Qk),可以通过。

p.s. 在打模拟赛的时候因为思路以及方程和同机房某位dalao撞了导致被教练误以为抄袭,就当这题我现场没AC吧。

代码:

#include<bits/stdc++.h>
#define maxn 3005
#define maxm 6005
using namespace std;
typedef long long LL;
int read()
{
	char c;int sum=0,f=1;c=getchar();
	while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){sum=sum*10+c-'0';c=getchar();}
	return sum*f;
}
int head[maxn],to[maxm],val[maxm],nex[maxm],cnt;
void add(int u,int v,int w)
{
	to[++cnt]=v;nex[cnt]=head[u];val[cnt]=w;head[u]=cnt;
}
int n,q;
int dp[maxn][105][105],son[105][105];
void dfs1(int x,int fa)
{
	for(int i=1;i<=100;i++)
	dp[x][i][0]=1;
	for(int i=head[x];i;i=nex[i])
	{
		if(to[i]==fa) continue;
		dfs1(to[i],x);
		for(int j=1;j<=100;j++)
		for(int k=0;k<j;k++)
		dp[x][j][(k+val[i])%j]+=dp[to[i]][j][k];
	}
}
void dfs2(int x,int fa,int v)
{
	if(fa!=-1)
	{
		for(int j=1;j<=100;j++)
		for(int k=0;k<j;k++)
		{
			son[j][k]=dp[x][j][k];
			dp[fa][j][(k+v)%j]-=son[j][k];
		}
		for(int j=1;j<=100;j++)
		for(int k=0;k<j;k++)
		dp[x][j][(k+v)%j]+=dp[fa][j][k];
		for(int j=1;j<=100;j++)
		for(int k=0;k<j;k++)
		dp[fa][j][(k+v)%j]+=son[j][k];
	}
	for(int i=head[x];i;i=nex[i])
	{
		if(to[i]==fa) continue;
		dfs2(to[i],x,val[i]);
	}
}
int main()
{
	freopen("travel.in","r",stdin);
	freopen("travel.out","w",stdout);
	n=read();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read(),w=read();
		add(u,v,w);add(v,u,w);
	}
	dfs1(1,-1);
	dfs2(1,-1,0);
	q=read();
	while(q--)
	{
		int u=read(),k=read();
		for(int i=k-1;i>=0;i--)
		if(dp[u][k][i])
		{
			printf("%d\n",i);
			break;
		}
	}
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值