小A与图 题解

题目

题目描述

        有一个n个点n条边的有向图,每条边为<i,f(i),w(i)>,意思是i指向f(i)的边权为w(i)的边,现在小A想知道,对于每个点的si和mi。
        si:由i出发经过k条边,这k条边的权值和。
        mi:由i出发经过k条边,这k条边的权值最小值。

输入

第一行两个数n和k
第二行n个数f(i)
第三行n个数w(i)

输出

每行两个数si和mi

样例输入

7 3
1 2 3 4 3 2 6
6 3 1 4 2 2 3

样例输出

10 1
8 1
7 1
10 2
8 2
7 1
9 3

数据范围限制

30%的数据:n,k<=1000。
100%的数据:N<=10^5,k<=10^10,0<=f(i)<n,w(i)<=10^8。

提示

数据结构

        我们用暴力去计算每个点肯定是不行的。不过,我们注意到,它求的其实是边的区间和和区间最小值。因此,我们可以用一个ST表去维护一个区间的区间和和区间最小值。我们设f[i][j].s表示以i为起点,走了2^{j}条边的边权和,设f[i][j].m表示以i为起点,走了2^{j}条边的边权最小值,f[i][j].to表示 以i为起点,走了2^{j}条边的终点。

算法

        我们预处理这个ST表。

        因为在f[i][j]中i这个节点可以由f[i][j-1]的终点来表示,所以得出状态转移方程f[i][j].to=f[f[i][j-1].to][j-1].to

        因为s这个成员其实就是区间和,所以我们可以由f[i][j-1]转移过来。加上的就是f[i][j]和f[i][j-1]相差得的这条边。所以得出状态转移方程

f[i][j].s=f[i][j-1].s+f[f[i][j-1].to][j-1].s;

        因为m这个成员其实就是s的区间和中的最大值,所以得出状态转移方程f[i][j].m=min(f[i][j-1].m,f[f[i][j-1].to][j-1].m);

for (int j=1; j<=log2(k); j++)
		for (int i=0; i<n; i++)
		{
			f[i][j].to=f[f[i][j-1].to][j-1].to;
			f[i][j].s=f[i][j-1].s+f[f[i][j-1].to][j-1].s;
			f[i][j].m=min(f[i][j-1].m,f[f[i][j-1].to][j-1].m);
		}

        但正当我们去打时,我们发现,我们只求出了走2^{j}的答案,怎么办呢?我们只需要把k分解成2进制(及2^{p_{1}}+2^{p_{2}}+2^{p_{3}}+......),然后再把这些区间加起来得到s的答案。m的答案同理。

while (c>0)
{
	if (c&1)//如果那一位是0就不用考虑 
	{
		ans1+=f[j][t].s;
		ans2=min(ans2,f[j][t].m);
		j=f[j][t].to;//下一个区间的起点 
	}
	t++;
	c>>=1;//右移一位。 
}

 注意事项

        i=0~n-1,看提示就知道了。

        不用long long会爆掉

代码

#include <cstdio>
#include <cmath>
#define ll long long
ll min(ll a,ll b);

struct node
{
	ll to;
	ll s;
	ll m;
};

const ll INF=0x7ffffffffffffff/3;
const ll N=1e5,M=log2(N);
ll a[N],b[N];
node f[N][M];

int main()
{
	int n;
	ll k;
	freopen("graph.in","r",stdin);
	freopen("graph.out","w",stdout);
	scanf("%d %lld",&n,&k);
	for (int i=0; i<n; i++) scanf("%d",&a[i]);
	for (int i=0; i<n; i++) scanf("%d",&b[i]);
	for (int i=0; i<n; i++)//初始化 
	{
		f[i][0].to=a[i];
		f[i][0].s=b[i];
		f[i][0].m=b[i];
	}
	for (int j=1; j<=log2(k); j++)//预处理 
		for (int i=0; i<n; i++)
		{
			f[i][j].to=f[f[i][j-1].to][j-1].to;
			f[i][j].s=f[i][j-1].s+f[f[i][j-1].to][j-1].s;
			f[i][j].m=min(f[i][j-1].m,f[f[i][j-1].to][j-1].m);
		}
	for (int i=0; i<n; i++)//算si和mi 
	{
		int t=0,j=i;
		ll c=k,ans1=0,ans2=INF;
		while (c>0)
		{
			if (c&1)//如果那一位是0就不用考虑 
			{
				ans1+=f[j][t].s;
				ans2=min(ans2,f[j][t].m);
				j=f[j][t].to;//下一个区间的起点 
			}
			t++;
			c>>=1;//右移一位。 
		}
		printf("%lld %lld\n",ans1,ans2);
	}
	return 0;
}

ll min(ll a,ll b) 
{
	if (a<b) return a;
	else return b;
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值