NOIP2016提高组Day1

本文解析了几种算法竞赛题目,包括玩具谜题中的模拟方法、天天爱跑步中的树上前缀和应用及路径切割技巧,以及换教室问题中的动态规划策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、玩具谜题

数据:
这里写图片描述

其实数据范围和切分没有用的……直接模拟就能过的说……

二、天天爱跑步
数据:
这里写图片描述

我考试时一档档写过来,原来以为有80分,但是只有65,是链的15分写错了,后来发现想得太简单了。

Si==Ti和Wj==0只要循环判断就行。链推一下也简单。

正解之前先说Si==1和Ti==1的情况

Si==1:
此时相当于有很多条链挂在了根节点上往下,这样就用前缀和(树上前缀和)就行了。具体就是把末端点加一,起点的父亲减一,然后从下往上前缀和。

for(int i=1;i<=m;i++){
    cnt[fa[0][A[i].l]]--;
    cnt[A[i].r]++;
}
for(int i=n;i>=1;i--)cnt[fa[0][dfl[i]]]+=cnt[dfl[i]];
for(int i=1;i<=n;i++)
    if(T[i]==dep[i]-1)printf("%d ",cnt[i]);
    else printf("0 ");
puts("");

这里的cnt[i]就是每一个节点有多少条链经过他。因为起点固定是一,那么到节点深度就是深度减一(根节点深度为1),再循环判一下就行了。

Ti==1:
这个也可以看做是在根节点上挂链,只是反过来挂。此时的时间就不能这么简单的判断了,直接上代码,其中ans是答案,sum是维护的前缀和,cnt是这个节点上有几个链的起点

voidf(int x,int fa1){
    ans[x]=sum[dep[x]+T[x]];
    for(int i=0;i<(int)edge[x].size();i++){
        int y=edge[x][i];
        if(y==fa1)continue;
        f(y,x);
    }
    sum[dep[x]]+=Cnt[x];
    ans[x]=sum[dep[x]+T[x]]-ans[x];
}

sum里的装的是一层的总和,先加再减,加的时候自己子树还没算,减的时候子树算好了,减一下就刚好是子树里的了。这样才能把自己这棵子树里的收集上来。

既然已经有端点从下到上和从上到下的,显然这就是提示要把一条路径切成这样两半来写。切的中心点就是LCA。这样切开来就是st->LCA,LCA->ed两部分。但是LCA这样就会被算两次,我的写法是第二段不把LCA算进去,也可以都算,如果LCA满足再减一,但是我觉得这样麻烦。

for(int i=1;i<=n;i++){//路径处理段
    A[i].lca=LCA(A[i].l,A[i].r);
    int D=dep[A[i].l]+dep[A[i].r]-2*dep[A[i].lca];
    //A[i].l->lca的一段
    G[0][A[i].l].push_back((node1){dep[A[i].l],1});
    G[0][fa[0][A[i].lca]].push_back((node1){dep[A[i].l],-1});
    //lca->A[i].r的一段,这一段装的时候要处理,D-dep[A[i].r],然后转化为同一问题
    G[1][A[i].r].push_back((node1){D-dep[A[i].r],1});
    G[1][A[i].lca].push_back((node1){D-dep[A[i].r],-1});
}

void f(int x,int fa1){//递归段
    int l=dep[x]+T[x],r=T[x]-dep[x];
    ans[x]-=cnt[0][l]+cnt[1][r];//先减
    for(int i=0;i<(int)edge[x].size();i++){
        int y=edge[x][i];
        if(y==fa1)continue;
        f(y,x);
    }
    //前缀和维护
    for(int i=0;i<(int)G[0][x].size();i++)cnt[0][G[0][x][i].x]+=G[0][x][i].y;
    for(int i=0;i<(int)G[1][x].size();i++)cnt[1][G[1][x][i].x]+=G[1][x][i].y;
    ans[x]+=cnt[0][l]+cnt[1][r];//再加
}

三、换教室
数据:
这里写图片描述

因为v<=300,所以直接用弗洛伊德最快,预处理出每个教室间的长度

m==0、m==1和m==2的三种情况很好算。
m==0:就是求路径和
m==1:先枚举一个时间,申请这个点交换
m==2:枚举两个点,其他和上面的一样
也可以用二进制枚举,但是大一点的时候算不出来。

正解肯定是dp,看的出来,只是考试不敢打,怕改太久。
定义dp[i][j][2]
dp[i][j][0] 是前i个点,申请了j个,第i个点没有申请
dp[i][j][1] 是前i个点,申请了j个,第i个点申请
转移就挺明显的了,就是i、i-1两个点申不申请,那就分两种:
1、dp[i][j][0] 由 dp[i-1][j][0] 和 dp[i-1][j][1] 更新
2、dp[i][j][1] 由 dp[i-1][j-1][0] 和 dp[i-1][j-1][1] 更新
然后对应的把值更新过来就行了。但是,dp的转移简直无语

for(inti=2;i<=n;i++){
    dp[i][0][0]=dp[i-1][0][0]+dis[A[i-1]][A[i]];
    for(int j=1;j<=m;j++){
        if(j>i)break;
        dp[i][j][0]=min(dp[i-1][j][0]+dis[A[i-1]][A[i]],//i-1和i都不申请
                        dp[i-1][j][1]+dis[B[i-1]][A[i]]*P[i-1]//i-1申请(分成功和失败两块)
                        );
        dp[i][j][1]=min(dp[i-1][j-1][0]+dis[A[i-1]][B[i]]*P[i]+dis[A[i-1]][A[i]]*(1-P[i]),
                dp[i-1][j-1][1]
                +dis[B[i-1]][B[i]]*P[i-1]*P[i]//i-1成功,i成功
                +dis[A[i-1]][B[i]]*(1-P[i-1])*P[i]//i-1成功,i失败
                +dis[B[i-1]][A[i]]*P[i-1]*(1-P[i])//i-1失败,i成功
                +dis[A[i-1]][A[i]]*(1-P[i-1])*(1-P[i])//i-1失败,i失败
                );          
    }
}

其实挺简单的,就是容易打错。

今天考试最后一题e、v打反了,72变成8,不然就上240了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值