Codeforces 702E 倍增

给定一副有向图,每个点的出度都为1,每条边有边权。

给定一个整数k

求每个点出发k条边的路径边权之和与路径权值的最小值是多少?

首先这个倍增是容易想出来的,如果每次只走一条边,那么时间太浪费。并且在倍增过程中注意是将全部的以i倍增的都处理完才处理i+1的倍增,因为如果是先处理某个点的某个倍增f[u][i],而连接点的v的f[v][i-1]是没被计算的,就会倍增错误。所以倍增的顺序应该是先循环倍增值,再循环这个倍增值需要处理的点。(一开始用dfs倍增的时候没考虑到这点,并且如果是dfs倍增,应该是需要34次dfs的,每次dfs只处理一种倍增,1<<34是第一个大于1e10的数,k<1e10的)

另外值得注意的一点是,计算答案的倍增策略:

我一开始是:(1200ms)

ll s(int now)
{
	ll temp=k;ll ret=0;
	while(temp)
	{
		ret+=len[now][log2(temp)];
		now=f[now][log2(temp)];
		temp-=(ll)1<<log2(temp);
	}
	return ret;
}

ll m(int now)
{
	ll temp=k;ll ret=1000000000;
	while(temp)
	{
		ret=min(ret,min1[now][log2(temp)]);
		now=f[now][log2(temp)];
		temp-=(ll)1<<log2(temp);
	}
	return ret;
}

后来是:(650ms)

ll s(int now)
{
	ll temp=k;ll ret=0;
	for(int i=34;i>=0;i--)
	{
		if(pw[i]<=temp)
		{
			ret+=len[now][i];
			temp-=pw[i];//这一次向下走了1<<i位,注意不是len[now][i],len记录的是边权和
			now=f[now][i];
		}
	}
	return ret;
}

ll m(int now)
{
	ll temp=k;ll ret=1000000000;
	for(int i=34;i>=0;i--)
	{
		if(pw[i]<=temp)
		{
			ret=min(ret,min1[now][i]);
			temp-=pw[i];
			now=f[now][i];
		}
	}
	return ret;
}

前一种的时间消耗居然是后面的两倍。

调试之后发现,log计算太费时间,就算保存使用都比第二种多了大约200ms。

另外pw[i]保存1<<i,测试了一下发现时间几乎没变化,所以使用1<<i也是不错的,但是需要注意的是,1<<i的1默认是int,在这里会爆int,所以要用(ll)1<<i,不过综合来看还是pw保存为妙,位运算爆int是很难注意到的

AC代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;

ll read()
{
	ll ret=0,base=1;
	char ch=getchar();
	while(!isdigit(ch))
	{
		if(ch=='-') base=-1;
		ch=getchar();
	}
	while(isdigit(ch))
	{
		ret=(ret<<3)+(ret<<1)+ch-48;
		ch=getchar();
	}
	return ret*base;
}

int log2(ll a)
{
	return (int)(log(a)/log(2));
}

int n,to[100005],f[100005][36];
ll k,w[100005],len[100005][36],min1[100005][36],pw[36];

ll s(int now)
{
	ll temp=k,ret=0;
	for(int i=34;i>=0;i--)
	{
		if(pw[i]<=temp)
		{
			ret+=len[now][i];
			temp-=pw[i];
			now=f[now][i];
		}
	}
	return ret;
}

ll m(int now)
{
	ll temp=k,ret=1000000000;
	for(int i=34;i>=0;i--)
	{
		if(pw[i]<=temp)
		{
			ret=min(ret,min1[now][i]);
			temp-=pw[i];
			now=f[now][i];
		}
	}
	return ret;
}

int main()
{
	n=read();k=read();pw[0]=1;
	for(int i=1;i<=n;i++) f[i][0]=to[i]=read()+1;
	for(int i=1;i<=n;i++) len[i][0]=min1[i][0]=w[i]=read();
	for(int t=1;t<=34;t++)
	{
		pw[t]=pw[t-1]<<1;
		for(int u=1;u<=n;u++)
		{
			f[u][t]=f[f[u][t-1]][t-1];
			len[u][t]=len[u][t-1]+len[f[u][t-1]][t-1];
			min1[u][t]=min(min1[u][t-1],min1[f[u][t-1]][t-1]);
		}
	}
	for(int i=1;i<=n;i++) printf("%lld %lld\n",s(i),m(i));
 	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值