题目
题目描述
有一个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表示以为起点,走了
条边的边权和,设f[i][j].m表示以
为起点,走了
条边的边权最小值,f[i][j].to表示 以
为起点,走了
条边的终点。
算法
我们预处理这个ST表。
因为在f[i][j]中这个节点可以由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);
}
但正当我们去打时,我们发现,我们只求出了走的答案,怎么办呢?我们只需要把k分解成2进制(及
+......),然后再把这些区间加起来得到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;
}