JZWC【Day2】题解&总结

果然说Day1会水成狗,Day2被虐成狗,这次改题改的我都没有心情好好玩耍了……
附上题解

T1 送你一棵圣诞树

Description

给出m+1棵树,从0到m编号。T0只有一个编号为0的点。如果用Ti表示第i棵树(i>0),则在第Tai 棵树的第ci 个点和第Tbi 棵树的第di 个点之间连上了一条长度为li 的边来表示它。并且,保持Tai 中的所有节点编号不变,然后如果Tai 中有s 个节点,Tbi 中的所有节点的编号加上s。对于每棵树,求这里写图片描述 d(i,j)表示节点i到j的最短路径。

Input

第一行输入一个正整数T 表示数据组数。每组数据的第一行是一个整数m,接下来m 行每行五个整数ai, bi, ci, di, li,保证0 <= ai, bi < i, 0<= li<= 10^9,ci, di 存在。

Output

对于每组询问输出m 行。第i 行输出Ti 的权值。答案可能很大,请对10^9 + 7 取模后输出。

Sample Input

1
2
0 0 0 0 2
1 1 0 0 4

Sample Output

2
28

Data Constraint

对于30% 的数据,m <= 8
对于60% 的数据,m <= 16
对于100% 的数据,1 <= m<= 60,T<= 100

Solution

神题一道,改了两天。
首先,我们要知道,这样建出的树可能非常大,是不能用普通的方法表示的。
用ans[i]来表示Ti的答案,cout[i]表示Ti的节点个数,all(x,i)表示Tx中所有的点到i这个点的距离和,因为这棵树由两棵树组成,我们可以推出如下公式:
ans[i]=ans[a[i]]+ans[b[i]]+cout[a[i]] * cout[b[i]] * l[i]+all(a[i],c[i]) * cout[b[i]]+all(b[i],d[i])*cout[a[i]];
然后我们思考如何求all(x,i)。发现上面我们已经把一个问题分成了许多子问题,那么我们能不能再把all分开来求呢?
很明显是可以的。设to(x,i,j)表示Tx中,点i到点j的距离,则
若l为组成x的左子树,r为右子树,
(1)i存在于l中
all(x,i)=all(r,d[x])+all(l,i)+(l[x]+to(l,i,c[x]))*cout[r];
(2)i存在于r中
all(x,i)=all(l,c[x])+all(r,i)+(l[x]+to(r,i,d[x]))*cout[l];
有了上面的经验,我们也可以吧to(x,i,j)分开来求,详细见代码。
然后,我们可以写记忆化搜索,这样每一次to的复杂度是O(n)的,每一次all都要调用一次to,所以复杂度是O(n^2)。
总复杂度为O(n^3)

Code

#include<cstdio>
#include<cstring>
#include<map>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define ll long long
#define mo 1000000007
#define N 65
using namespace std;
map<pair<ll,ll>,ll> h[N];
map<ll,ll> h1[N];
struct note{
    ll a,b,c,d,l,w;
    void add() {h[w][make_pair(c,d)]=l;h[w][make_pair(d,c)]=l;}
}f[N];
ll cout[N],ans[N],c[N];
ll ty,n;
ll to(ll z,ll x,ll y) {
    if (x==y) return 0;
    if (h[z][make_pair(x,y)]) return h[z][make_pair(x,y)];
    ll l=f[z].a,r=f[z].b,sum;
    if (x<cout[l]&&y<cout[l]) sum=to(l,x,y);
    else if (x<cout[l]&&y>=cout[l]) 
    sum=(to(l,x,f[z].c)+f[z].l+to(r,y-cout[l],f[z].d))%mo;
    else if (x>=cout[l]&&y<cout[l]) 
    sum=(to(r,x-cout[l],f[z].d)+f[z].l+to(l,y,f[z].c))%mo;
    else sum=to(r,x-cout[l],y-cout[l]);
    h[z][make_pair(x,y)]=sum;
    return sum;
}
ll all(ll x,ll y) {
    if (!x) return 0;
    if (h1[x][y]) return h1[x][y];
    ll l=f[x].a,r=f[x].b,z;
    if (y<cout[l]) z=(all(r,f[x].d)+all(l,y)+
    (f[x].l+to(l,f[x].c,y))*c[r]%mo)%mo;
    else z=(all(l,f[x].c)+all(r,y-cout[l])+
    (f[x].l+to(r,f[x].d,y-cout[l]))*c[l]%mo)%mo;
    h1[x][y]=z;
    return z;
}
int main() {
    for(scanf("%d",&ty);ty;ty--) {
        scanf("%d",&n);
        memset(cout,0,sizeof(cout));cout[0]=1;c[0]=1;
        memset(ans,0,sizeof(ans));fo(i,1,n) h[i].clear(),h1[i].clear();
        fo(i,1,n) {
            scanf("%lld%lld%lld%lld%lld",
            &f[i].a,&f[i].b,&f[i].c,&f[i].d,&f[i].l);
            f[i].w=i;f[i].l=f[i].l%mo;
            f[i].d=f[i].d+cout[f[i].a];
            f[i].add();f[i].d=f[i].d-cout[f[i].a];
            c[i]=(c[f[i].a]+c[f[i].b])%mo;
            cout[i]=cout[f[i].a]+cout[f[i].b];
            ans[i]=(ans[f[i].a]+ans[f[i].b]+
            c[f[i].a]%mo*c[f[i].b]%mo*f[i].l%mo
            +all(f[i].a,f[i].c)*c[f[i].b]%mo+
            all(f[i].b,f[i].d)*c[f[i].a]%mo)%mo;
            printf("%lld\n",ans[i]);
        } 
    }
}

T2 我想大声告诉你

Description

有n个人,每一轮随机选择没有出局的人一个人出局,然后剩下的人受到一次攻击,每个人被攻击就有 p的概率淘汰,求当k=0..n-1时,每个人受到k次攻击然后出局的概率是多少,答案在模258280327 意义下。注意,出局不等于淘汰。

Input

第一行输入一个正整数T 表示数据组数。
对于每一组数据输入仅一行三个数n, x, y,表示在这组数据中有n 个人参赛,p = x/y。保证y 和258280327 互质。

Output

对于每组数据,输出一行n 个整数,表示对于k = 0到n - 1 的概率在模258280327 意义下的值。

Sample Input

2
3 40 100
9 32 1049

Sample Output

172186885 92980918 16529941
229582513 163885050 39458156 102374877 116777758 216371874 55544199 95860736 8136787

Data Constraint

对于60% 的数据,n <=100
对于100% 的数据,n <= 2* 10^3,1 <= T <= 5,0<= x < y <= 10^9

Solution

没什么好说的了,首先你要理解清楚题意,然后再看懂样例,然后你就大概会做了。
因为它是随机选人出局,我们把它变成按顺序选人出局,对于每个k,所有人出局的总概率是不变的。而题目描述的每个人出局的概率是相等的,所以按我们只需要做一次线性的dp,算出答案,然后取个平均数就行了。

Code

#include<cstdio>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define ll long long
#define N 2005
#define mo 258280327
using namespace std;
int ty,n;
ll x,y,f[N][N],ans,p[N];
ll mi(ll x,ll y) {
    if (y==0) return 1;
    ll z=x;y--;
    while (y) {
        if (y%2==1) z=z*x%mo;
        x=x*x%mo;y/=2;
    }   
    return z;
}
int main() {
    for(scanf("%d",&ty);ty;ty--) {
        scanf("%d%lld%lld",&n,&x,&y);
        memset(f,0,sizeof(f));f[1][0]=1;
        ll ni=mi(y,mo-2);p[0]=1;
        fo(i,1,n) p[i]=p[i-1]*(y-x)%mo*ni%mo;
        fo(i,1,n-1) 
            fo(j,0,n-1) 
                if (f[i,j]) {
                    f[i+1][j]=(f[i+1][j]+f[i][j]*(1-p[j]+mo)%mo)%mo;
                    f[i+1][j+1]=(f[i+1][j+1]+f[i][j]*p[j+1]%mo)%mo;
                } 
        fo(i,0,n-1) {
            ll ans=0;
            fo(j,1,n) ans=(ans+f[j][i])%mo;
            printf("%lld ",ans*mi(n,mo-2)%mo);
        }   
        printf("\n");
    }   
}

T3 对你的爱深不见底

Description

定义一些字符串,s1=’a’,s2=’b’,si=si-1+si-2,给出n,m,求sn的前m个字符形成的字符串中,最长的相等前后缀的长度。如,ababa为3。

Input

第一行输入一个正整数T 表示数据组数。
对于每组数据,第一行是两个整数n;m。保证1<= m <=|sn|

Output

对于每组数据,输出一个整数表示答案。答案可能很大,你只需要输出模258280327 后的答案。

Sample Input

2
4 3
5 5

Sample Output

1
2

Data Constraint

对于30% 的数据,n <= 20
对于60% 的数据,n <= 60
对于100% 的数据,n <= 10^3,1 <= T <= 100

Solution

首先,你得打个表,然后找规律。然后你就会发现,设k为最小的sk使得|sk|>m+1,那么答案就是m-|sk-2|,上高精度就行了。证明自行脑补,意会即可。

Code

#include<cstdio>
#include<cstring>
#include<iostream>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define N 1005
#define maxn 100000000
#define mo 258280327 
#define ll long long 
using namespace std;
struct note{
    int l,a[505];
}f[N],m,one;
note add(note x,note y) {
    note z;memset(z.a,0,sizeof(z.a));z.l=max(x.l,y.l);
    fo(i,1,z.l) {
        z.a[i]+=x.a[i]+y.a[i];z.a[i+1]+=z.a[i]/maxn;z.a[i]%=maxn;   
    }   
    if (z.a[z.l+1]) z.l++;
    return z;
}
note dec(note x,note y) {
    note z;memset(z.a,0,sizeof(z.a));z.l=x.l;
    fo(i,1,z.l) {
        z.a[i]=x.a[i]-y.a[i];
        if (z.a[i]<0) z.a[i]+=maxn,x.a[i+1]--;
    }
    if (!z.a[z.l]) z.l--;
    return z;
}
bool big(note x,note y) {
    if (x.l>y.l) return 1;
    else if (x.l<y.l) return 0;
    fd(i,x.l,1) 
        if (x.a[i]>y.a[i]) return 1;
        else if (x.a[i]<y.a[i]) return 0;
    return 0;
}
char s[N];
int ty,n,l,r,mid,ten[9];
ll ans;
int main() {
    f[1].l=f[1].a[1]=f[2].l=f[2].a[1]=one.l=one.a[1]=1;ten[1]=1;
    fo(i,2,8) ten[i]=ten[i-1]*10;ten[0]=ten[8];
    fo(i,3,1003) f[i]=add(f[i-1],f[i-2]);
    for(scanf("%d",&ty);ty;ty--) {
        scanf("%d",&r);l=0;r+=3;m.l=0;memset(m.a,0,sizeof(m.a));
        scanf("%s",s+1);int k=0,len=strlen(s+1);
        fd(i,len,1) {
            k=k+(s[i]-'0')*ten[(len-i+1)%8];
            if ((len-i+1)%8==0) m.a[++m.l]=k,k=0;
        }
        if (k) m.a[++m.l]=k;
        note p=add(m,one);
        while (l<r) {
            mid=(l+r)/2;
            if (big(f[mid],p)) r=mid;else l=mid+1;
        }
        l-=2;
        m=dec(m,f[l]);ans=0;
        fd(i,m.l,1) ans=(ans*maxn%mo+m.a[i])%mo;
        printf("%lld\n",ans);
    }
}

果然跪了呀!整场比赛都被第二题的题意搞得不要不要的,只打了第三题的暴力。看来策略还是有问题,不要死磕一道题,尤其是那么KD的题!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值