给定一副有向图,每个点的出度都为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;
}