[BZOJ]5000: OI树 倍增

Description

几天之后小跳蚤即将结束自己在lydsy星球上的旅行。这时,lydsy人却发现他们的超空间传送装置的能量早在小跳
蚤通过石板来到lydsy星球时就已经消耗光了。这时,小跳蚤了解到自己很有可能回不到跳蚤国了,于是掉下了伤
心的眼泪……lydsy人见状决定无论如何也要送小跳蚤回地球,于是lydsy人的大祭司lavendir决定拜访lydsy星球
的OI树,用咒语从OI树中取得能量。咒语中有K种字母,我们用前K个大写英文字母来表示它。OI树可以被认为是一
个有着N个节点的带权有向图,所有节点的出度都是K,并且所有的出边都对应于一个咒语中的字母。仪式开始的时
候有一个标记物放在OI树的1号节点上。之后,从咒语的第一个字母开始,每经过一个字母,标记物就沿着该字母
对应的出边进入这条边的终点,并且得到相当于边权大小的能量值。当咒语处理完毕时,就可以得到这个过程中得
到的所有能量了。现在由于lydsy人超群的计算能力,他们已经知道某咒语大概会获得多少能量,只是还想知道会
获得的能量值对一个数M取模的结果。跳蚤国王通过小跳蚤留下的石板也了解到了小跳蚤现在的处境,所以他又找
到了你,希望你帮助他计算出这个问题的答案。

题解:

tkj大神告诉我是倍增之后,就很简单了。 to[i][j][k] 表示从i走第j个字母,走 2k 步到达的点; v[i][j][k] 表示从i走第j个字母,走 2k 步后获得的权值。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=10010;
char str[120010];
int n,k,mod,ans=0,a[maxn][28],b[maxn][28],to[maxn][28][32],v[maxn][28][32];
int getv(int x,int y,int z)
{
    if(z==0)return 0;
    if(z==1)return v[x][y][0];
    for(int i=30;i>=0;i--)
    if(z>=(1<<i))return (v[x][y][i]+getv(to[x][y][i],y,z-(1<<i)))%mod;
}
int getto(int x,int y,int z)
{
    if(z==0)return x;
    if(z==1)return to[x][y][0];
    for(int i=30;i>=0;i--)
    if(z>=(1<<i))return getto(to[x][y][i],y,z-(1<<i));
}
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    for(int j=1;j<=k;j++)
    {
        scanf("%d%d",&a[i][j],&b[i][j]);
        to[i][j][0]=a[i][j];
        v[i][j][0]=b[i][j];
    }
    scanf("%s%d",str,&mod);
    for(int l=1;l<=30;l++)
    for(int i=1;i<=n;i++)
    for(int j=1;j<=k;j++)
    {
        v[i][j][l-1]%=mod;
        to[i][j][l]=to[to[i][j][l-1]][j][l-1];
        v[i][j][l]=(v[i][j][l-1]+v[to[i][j][l-1]][j][l-1])%mod;
    }
    int now=1,len=strlen(str);
    for(int i=0;i<len;)
    {
        if(str[i]==']'){i++;continue;}
        if(str[i]=='[')
        {
            int j,num=0;
            for(j=i+1;;j++)
            {
                if(str[j]<'0'||str[j]>'9')break;
                num=num*10+str[j]-'0';
            }
            ans=(ans+getv(now,str[j]-'A'+1,num))%mod;
            now=getto(now,str[j]-'A'+1,num);
            i=j+2;
        }
        else if(str[i]>='A'&&str[i]<='Z')
        {
            ans=(ans+getv(now,str[i]-'A'+1,1))%mod;
            now=getto(now,str[i]-'A'+1,1);
            i++;
        }
    }
    printf("%d",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值