NOIP 2015 提高组 day2 解题报告

T1 跳石头:

题目描述 Description

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


输入描述 Input Description

输入文件名为 stone.in。

输入文件第一行包含三个整数L,N,M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。

接下来N行,每行一个整数,第i行的整数Di(0 < Di < L)表示第i块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。


输出描述 Output Description

输出文件名为stone.out。

输出文件只包含一个整数,即最短跳跃距离的最大值。


样例输入 Sample Input

25 5 2

2

11

14

17

21


样例输出 Sample Output

4


数据范围及提示 Data Size & Hint

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

对于50%的数据,0≤M≤N≤100。

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


题解:

二分答案裸题,对于此类求最小值最大,很多时候需要二分一个答案,这个时候我们进行二分,然后在check(也就是我的query函数)的时候进行计数,如果计的数大于了需要移走的个数,则说明我的距离大了,导致必须移动更多的石头来满足二分到的答案,因此把距离往小二分,反之把距离往大二分

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#define MAXN  50000+50
using namespace std;
int L,N,M;
int cnt;
int loc[MAXN];
int maxn;
int len[MAXN];
int query(int num){
    int last=0;
    int cnt=0;
    for(register int i=1;i<=N+1;i++){
        if(loc[i]-last<num) cnt++;
        else last=loc[i];
    }
    return cnt;
}
int main(){
    freopen("stone.in","r",stdin);
    freopen("stone.out","w",stdout);
    scanf("%d%d%d",&L,&N,&M);
    for(register int i=1;i<=N;i++){
        scanf("%d",&loc[i]);
        len[i]=loc[i]-loc[i-1];
        maxn=max(maxn,len[i]);
    }
    loc[N+1]=L;
    len[N+1]=loc[N+1]-loc[N];
    maxn=max(maxn,len[N+1]);
    int l=0;
    int r=maxn;
    while(l<=r){
        int mid=(l+r)>>1;
        int temp=query(mid);
        if(temp>M) r=mid-1;
        else l=mid+1;
    }
    //int te=lower_bound(len+1,len+N+1,l)-len;
    printf("%d",l-1);
    return 0;
}

这里面的len数组没有什么卵用…懒得删了,之前的思路不一样
对于printf的l-1可以这样思考:
对于while,如果当前的l已经等于r了,那么如果query(mid)成立,那么l就会++,然而大于mid的都是不成立的,所以这个时候输出l–也就是之前的mid,如果query(mid)不成立,那么r就会- -,并且l这处也是不成立的,所以往左走,因为左边的数据都是满足的(因为每次都是只要满足就往右走)
这里写图片描述


T2:子串

时间限制: 1 s
空间限制: 128000 KB


题目描述 Description

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


输入描述 Input Description

第一行是三个正整数n,m,k,分别表示字符串A的长度,字符串B的长度,以及问题描述中所提到的k,每两个整数之间用一个空格隔开。

第二行包含一个长度为n的字符串,表示字符串A。 第三行包含一个长度为m的字符串,表示字符串B。


输出描述 Output Description

输出共一行,包含一个整数,表示所求方案数。由于答案可能很大,所以这里要求输出答案对1,000,000,007取模的结果。


样例输入 Sample Input

【Input1】

6 3 1

aabaab

aab

【Input2】

6 3 2

aabaab

aab

【Input3】

6 3 3

aabaab

aab


样例输出 Sample Output

【Output1】

2

【Output2】

7

【Output3】

7


数据范围及提示 Data Size & Hint

对于第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。


题解:

显然是一道dp题
设两串字符的数组名分别为A和B
我们令 f[i][j][k] 表示当前匹配到A的i-1个数,B的j-1个数,当前匹配k次(即提取了k个A的子串),且A中的第i为恰好被用到
s[i][j][k] 表示当前匹配到A的i-1个数,B的j-1个数,当前匹配了k次(即提取了k个A的子串),且A中的第i个不一定恰好被用到

我们可以自然地推出s的状态转移

s[i][j][k]=s[i1][j][k]+f[i][j][k]

可以这样思考

s[i][j][k] 等于不选A的第i个字符和选A的第i个字符的情况和

那么 f[i][j][k] 的值怎么算呢?

在匹配 f[i][j][k] 的时候我们其实是在匹配 A[i] B[j]
如果 A[i]==B[i] ,那么我们有两种选择
第一种是让目前匹配上的和之前的合并,也是匹配了k次,方案数不变
还有一种是让目前的匹配自成一派,也就是之前匹配了k-1次,然后现在再匹配一次

所以可以得到

f[i][j][k]=f[i1][j1][k]+s[i1][j1][k1]

如果 A[i]B[i]

那么显然此时的 f[i][j][k]=0 原因是f数组表示的是刚好要取i-1时才有值,否则值为0

所以现在我们已经得到了所有的s和f数组的状态转移,显然最后的答案在 s[n][m][k]

显然如果两个数组都开三维的话,是会爆空间的,所以我们需要用滚动来优化空间,就像背包一样~

#include<cstdio>
#include<cstring>
#include<iostream>
#define MAXN 1000+10
#define MINN 200+20
#define idy 1000000007
using namespace std;
int N,M,K;
char A[MAXN],B[MAXN];
int f[MAXN][MINN],s[MAXN][MINN];
int main(){
    scanf("%d%d%d%s%s",&N,&M,&K,&A,&B);
    s[0][0]=1;
    for(register int i=1;i<=N;i++){
        for(register int j=M;j>=1;j--){
            if(A[i-1]==B[j-1])
            for(register int k=min(j,K);k>=1;k--){
                f[j][k]=(f[j-1][k]+s[j-1][k-1])%idy;
                s[j][k]=(s[j][k]+f[j][k])%idy;
            }else fill(f[j],f[j]+min(j,K)+1,0);
        } 
    }
    printf("%d",s[M][K]%idy);
    return 0;
}

这里写图片描述


T3:运输计划

题目描述 Description

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


输入描述 Input Description

第一行包括两个正整数 n,m,表示 L 国中星球的数量及小 P 公司预接的运输计划的数量,星球从 1 到 n 编号。接下来 n−1 行描述航道的建设情况,其中第 i 行包含三个整数 ai,bi 和 ti,表示第 i 条双向航道修建在 ai 与 bi 两个星球之间,任意飞船驶过它所花费的时间为 ti。数据保证 1<=ai,bi<=n 且 0<=ti<=1000。接下来 m 行描述运输计划的情况,其中第 j 行包含两个正整数 uj 和 vj,表示第 j 个运输计划是从 uj 号星球飞往 vj号星球。数据保证 1<=ui,vi<=n


输出描述 Output Description

输出文件只包含一个整数,表示小 P 的物流公司完成阶段性工作所需要的最短时间。

样例输入 Sample Input

6 3
1 2 3
1 6 4
3 1 7
4 3 6
3 5 5
3 6
2 5
4 5


样例输出 Sample Output

11


数据范围及提示 Data Size & Hint

样例解释:

将第 1 条航道改造成虫洞: 则三个计划耗时分别为:11,12,11,故需要花费的时间为 12。
将第 2 条航道改造成虫洞: 则三个计划耗时分别为:7,15,11,故需要花费的时间为 15。
将第 3 条航道改造成虫洞: 则三个计划耗时分别为:4,8,11,故需要花费的时间为 11。
将第 4 条航道改造成虫洞: 则三个计划耗时分别为:11,15,5,故需要花费的时间为 15。
将第 5 条航道改造成虫洞: 则三个计划耗时分别为:11,10,6,故需要花费的时间为 11。
故将第 3 条或第 5 条航道改造成虫洞均可使得完成阶段性工作的耗时最短,需要花费的时间为 11。


测试数据及约定:

所有数据:

1 <= ai, bi, uj, vj <= n, 0 <= ti <= 1000


这道题显然也是一个二分加LCA加差分裸题,比较水,只需要将所有询问的LCA处理出来,然后每次二分最长路径长度,然后寻找非法路径,然后寻找所有路径的交,再在所有路径的交里寻找一条最长的边,然后对所有非法路径减去这条边,判断一下是否满足二分的值就好了

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#define MAXN 300010

using namespace std;

int readin()
{
    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 Line{
    int from,to,val,nxt;
}line[MAXN*2];

struct queryL{
    int from,to,nxt,lca;
}question[MAXN*2];

int n,m,s[MAXN],f[MAXN],dfn[MAXN],dis[MAXN],fa[MAXN],head_line[MAXN],head_question[MAXN],tail_line,tail_question,tail_dfs;
void add_line(int from,int to,int val){
    tail_line++;
    line[tail_line].from=from;
    line[tail_line].to=to;
    line[tail_line].val=val;
    line[tail_line].nxt=head_line[from];
    head_line[from]=tail_line;
}

void add_question(int from,int to){
    tail_question++;
    question[tail_question].from=from;
    question[tail_question].to=to;
    question[tail_question].nxt=head_question[from];
    head_question[from]=tail_question;
}

void dfs(int u,int father,int cnt){
    fa[u]=father;
    dis[u]=cnt;
    for(register int i=head_line[u];i;i=line[i].nxt){
        int v=line[i].to;
        if(v!=father) dfs(v,u,cnt+line[i].val);
    }
    dfn[++tail_dfs]=u;
}

int find(int x){if(x==f[x]) return x;else return f[x]=find(f[x]);}

void tarjan(int u){
    f[u]=u;
    for(register int i=head_line[u];i;i=line[i].nxt){
        int v=line[i].to;
        if(v!=fa[u]){
            tarjan(v);
            f[v]=u; 
        }
    }
    for(register int i=head_question[u];i;i=question[i].nxt){
        int t=question[i].to;
        if(f[t]) question[i].lca=find(t);
    }
}
bool check(int maxn){
    int ans=0,total=0,Maxn=0;
    memset(s,0,sizeof(s));
    for(register int i=1;i<=tail_question;i+=2){
        question[i].lca=max(question[i].lca,question[i+1].lca);
        int len=dis[question[i].from]+dis[question[i].to]-2*dis[question[i].lca];
        if(len>maxn){
            total++;
            s[question[i].from]++;
            s[question[i].to]++;
            s[question[i].lca]-=2;
            Maxn=max(Maxn,len-maxn);
        }
    } 
    for(register int i=1;i<=n;i++) s[fa[dfn[i]]]+=s[dfn[i]];
    for(register int i=1;i<=n;i++){
        if(s[i]==total) ans=max(ans,dis[i]-dis[fa[i]]);
    }
    return ans>=Maxn;
}
int main(){
    freopen("transport.in","r",stdin);
    freopen("transport.out","w",stdout);
    n=readin();m=readin();
    int from,to,val,l=0,r=0;
    for(register int i=1;i<=n-1;i++){
        from=readin();to=readin();val=readin();
        add_line(from,to,val),add_line(to,from,val);
        r+=val;
    }
    for(register int i=1;i<=m;i++){
        from=readin();to=readin();
        add_question(from,to);
        add_question(to,from);
    }
    dfs(1,0,0);
    tarjan(1);
    int ans=0;
    while(l<=r){
        int mid=(l+r)>>1;
        if(check(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%d",ans);
    return 0;
}

这里写图片描述

这里写图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值