NOIP 2015 Day2 解题报告(全面)

本文解析了三项比赛中的算法问题,包括如何通过二分和贪心策略解决跳石头比赛中岩石移除问题,利用动态规划技巧处理子串匹配问题,以及采用图论方法解决宇宙航道优化挑战。

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

彩蛋

跳石头

(stone.cpp/c/pas)

【问题描述】

一年一度的“跳石头”比赛又要开始了!
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 N 块岩石(不含起点和终点的岩石) 。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 M 块岩石(不能移走起点和终点的岩石) 。

【 数据规模与约定 】

对于 20%的数据,0 ≤ M ≤ N ≤ 10。
对于 50%的数据,0 ≤ M ≤ N ≤ 100。
对于 100%的数据,0 ≤ M ≤ N ≤ 50,000,1 ≤ L ≤ 1,000,000,000。

思路

你看那个L那么大,如果暴力枚举,循环一遍都不行。很明显要减少石头之间的大量没用距离的运算。
利用二分的思想,(二分答案),加上一点点贪心,就是两个石子尽可能靠近。

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
const int N=50000;
int l,n,m,maxn,minn,ans,line[N+5];
int find(int x)
{
    int num=0,now=0;
    for (int i=1;i<=n+1;i++)
    if (line[i]-line[now]>=x) num++,now=i;
    return num>n-m;
}
int main()
{
    freopen("stone.in","r",stdin);
    freopen("stone.out","w",stdout);
    scanf("%d%d%d",&l,&n,&m);
    for (int i=1;i<=n;i++)
    scanf("%d",&line[i]);
    line[n+1]=l,maxn=l;
    while(minn<=maxn)
    {
        int mid=(maxn+minn)>>1;
        if (find(mid)) ans=mid,minn=mid+1;
        else maxn=mid-1;
    }
    printf("%d",ans);
    return 0;
}

子串

(substring.cpp/c/pas)

【问题描述】

有两个仅包含小写英文字母的字符串 A 和 B。 现在要从字符串 A 中取出 k 个 互不重
叠 的非空子串, 然后把这 k 个子串按照其在字符串 A 中出现的顺序依次连接起来得到一
个新的字符串,请问有多少种方案可以使得这个新串与字符串 B 相等?注意:子串取出
的位置不同也认为是不同的方案 。

【数据规模与约定】

对于第 1 组数据:1≤n≤500,1≤m≤50,k=1;
对于第 2 组至第 3 组数据:1≤n≤500,1≤m≤50,k=2;
对于第 4 组至第 5 组数据:1≤n≤500,1≤m≤50,k=m;
对于第 1 组至第 7 组数据:1≤n≤500,1≤m≤50,1≤k≤m;
对于第 1 组至第 9 组数据:1≤n≤1000,1≤m≤100,1≤k≤m;
对于所有 10 组数据:1≤n≤1000,1≤m≤200,1≤k≤m。

思路

首先,一般来说,字符串的题不是用纯的字符串方法,就是用动态规划。
这道题如果用纯的字符串方法,KMP找匹配然后在求组合数?求后缀?很明显不是。再看看这明显的“动规套路”,就知道这是DP了。可以用三维数组,dp【A第i位】【B第i位】【分成p组】,然后两个动规。我用的是一个四维的数组dp【A第i位】【B第i位】【分成p组】【子串是否存在于B中】,然后三个循环内一起搞就好了。
这里要注意了,上述算法是没有正确性上的问题的,时间复杂度也没有问题。但是由于内存的原因,数组不可以开大了,所以要滚动,滚动第一维。可以用&,也可以%,反正把内存降下去了。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
const int mod=1000000007;
const int N=210;
using namespace std;
int n,m,p,s,f[2][N][N][2];
char A[N*5],B[N];
int main()
{
//  freopen("substring.in","r",stdin);
//  freopen("substring.out","w",stdout);
    scanf("%d%d%d",&n,&m,&p);
    scanf("%s%s",A+1,B+1);
    for(int i=1;i<=n;i++)
        {
            f[i%2][1][1][0]=s;
            if(A[i]==B[1])f[i%2][1][1][1]=1,s++;
            for(int j=2;j<=m;j++)
            for(int k=1;k<=p;k++)
            {
                f[i%2][j][k][0]=(f[(i+1)%2][j][k][0]+f[(i+1)%2][j][k][1])%mod;
                if(A[i]==B[j]) f[i%2][j][k][1]=((f[(i+1)%2][j-1][k-1][1]+f[(i+1)%2][j-1][k][1])%mod+f[(i+1)%2][j-1][k-1][0])%mod;
            }
            for(int j=1;j<=m;j++)
            for(int k=1;k<=p;k++)
            f[(i+1)%2][j][k][1]=f[(i+1)%2][j][k][0]=0;
        }
    printf("%d\n",(f[n%2][m][p][0]+f[n%2][m][p][1])%mod);
    return 0;
}

运输计划

(transport.cpp/c/pas)

【问题描述】

公元 2044 年,人类进入了宇宙纪元。
L 国有 n 个星球,还有 n-1 条 双向 航道,每条航道建立在两个星球之间,这 n-1 条航道 连通 了 L 国的所有星球。
小 P 掌管一家物流公司,该公司有很多个运输计划,每个运输计划形如:有一艘物流飞船需要从 u i 号星球沿 最快 的宇航路径飞行到 v i 号星球去。 显然, 飞船驶过一条航道是需要时间的,对于航道 j,任意飞船驶过它所花费的时间为 t j ,并且任意两艘飞船之间 不会 产生任何干扰。
为了鼓励科技创新,L 国国王同意小 P 的物流公司参与 L 国的航道建设,即允许小P 把某一条航道改造成虫洞,飞船驶过虫洞 不消耗 时间。
在虫洞的建设完成前小 P 的物流公司就预接了 m 个运输计划。在虫洞建设完成后,这 m 个运输计划会 同时开始,所有飞船 一起 出发。当这 m 个运输计划 都完成 时,小 P 的物流公司的阶段性工作就完成了。
如果小 P 可以 自由选择 将哪一条航道改造成虫洞,试求出小 P 的物流公司完成阶段性工作所需要的最短时间是多少?

思路

我不会。。。代码抄的

代码

#include<cstdio>
#include<cstring>
#include<iostream> 
#define N 300010
using namespace std;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
struct edge{
    int next,data,d;
}e[N*2];
struct qedge{
    int next,d,lca,now;
}qe[N*2];
int n,m,i,j,q[N],efree,x,y,z,l,r,w,dfsx[N],mid,d[N],ans,ff[N],s[N],f[N],qq[N],qefree;
int max(int x,int y){return x>y?x:y;}
void add(int x,int y,int z){
    e[++efree].d=y;
    e[efree].data=z;
    e[efree].next=q[x];
    q[x]=efree;
}
void qadd(int x,int y){
    qe[++qefree].d=y;
    qe[qefree].now=x;
    qe[qefree].next=qq[x];
    qq[x]=qefree;
}
void dfs1(int x,int y,int z){
    ff[x]=y;
    d[x]=z;
    for(int i=q[x];i;i=e[i].next)
        if(e[i].d!=y)dfs1(e[i].d,x,z+e[i].data);
    dfsx[++w]=x;
}
int gf(int x){return f[x]==x?x:f[x]=gf(f[x]);}
void tarjan(int x){
    f[x]=x;
    for(int i=q[x];i;i=e[i].next)
        if(e[i].d!=ff[x]){
            tarjan(e[i].d);
            f[e[i].d]=x;
        }
    for(int i=qq[x];i;i=qe[i].next)
        if(f[qe[i].d])qe[i].lca=gf(qe[i].d);
}
bool pd(int x){
    int i,ans=0,total=0,now=0;
    memset(s,0,sizeof(s));
    for(i=1;i<=qefree;i+=2){
        qe[i].lca=max(qe[i].lca,qe[i+1].lca);
        if(d[qe[i].now]+d[qe[i].d]-2*d[qe[i].lca]>x){
            total++;
            s[qe[i].now]++;
            s[qe[i].d]++;
            s[qe[i].lca]-=2;
            now=max(now,d[qe[i].now]+d[qe[i].d]-2*d[qe[i].lca]-x);
        }
    }
    for(i=1;i<=n;i++)s[ff[dfsx[i]]]+=s[dfsx[i]];
    for(i=1;i<=n;i++)
        if(s[i]==total)ans=max(ans,d[i]-d[ff[i]]);
    return ans>=now;
}
int main(){
   n=read();m=read();
    for(i=1;i<n;i++){
        x=read();y=read();z=read();
        add(x,y,z),add(y,x,z);
        r+=z;
    }
    for(i=1;i<=m;i++){
        x=read();y=read();
        qadd(x,y),qadd(y,x);
    }
    dfs1(1,0,0);
    tarjan(1);
    while(l<=r){
        mid=(l+r)>>1;
        if(pd(mid))ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%d",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值