NOIP2015提高组DAY2题解

T1:跳石头

考察知识:二分,模拟

算法难度:XX+ 实现难度:XX+

分析:因为答案具有单调性(或者说这是最大最小问题,为T3做铺垫),我们考虑二分解决

我们二分出最短跳跃距离的最大值mid,然后进行判断:判断至少要移除多少块石头才能满足条件

至于怎么判断,我们可以写一个判断函数,用模拟的方法统计

#include
int L,n,m,a[50005];
bool check(int limt){
    int cnt=0,l=0;//l表示当前石头左边石头(考虑有些石头已经被移除)的坐标
    for(int i=1;i<=n+1;i++)
        if(a[i]-l>1;
        if(check(mid)) l=mid+1,ans=mid;
        else r=mid-1;
    }
    printf("%d\n",ans);
    return 0;
}

T2:子串

考察知识:动态规划,字符串

算法难度:XXXX 实现难度:XX+

我的动态规划水平真的非常差,所以并没有独立想出来,参考了题解:P2679题解

下面为了加深印象,自己写一遍分析:

分析:

设f[i][j][k]为A用到了i,B用到了j,已经用了k个子串,
并且一定用了当前字符(A[i])时的方案数。
设g[i][j][k]为A用到了i,B用到了j,已经用了k个子串,
无论用不用当前字符(A[i])时的方案数总和。

先明确以下:

1.A匹配的子串可以是不连续的,而B必须全部匹配

2.选子串时可以A[ i ]可以选,可以不选,而B[ i ]必须选

定义:

          f(i,j,k)表示字符串A匹配了1...i,字符串B匹配了1...j,且B已经匹配了k个子串,且A[ i ]被选中为子串的方案数

          g(i,j,k)表示字符串A匹配了1...i,字符串B匹配了1...j,且B已经匹配了k个子串,且A[ i ]可能被选中为子串的方案数

状态转移:

说明:

    1.由g(i,j,k)的定义,当g(i,j,k)选中A[ i ]时有f(i,j,k)种情况

    当不选中A[ i ]时,有g(i-1,j,k)种情况,因为:不选A[ i ] 那么可能选A[ i-1 ]所以为g(i-1,j,k)

    2.首先必须有A[ i ]==B[ j ]:当A[ i ] 与A[ i-1 ]共同包含在第k个子串时,有:g(i-1,j-1,k)种情况

    其次,当A[ i ]与A[ x ](0种情况

然后我们不难得出完整的状态转移方程:

对于f(i,j,k):

    当A[ i ]==B[ j ]时:

        f(i,j,k)=f(i-1,j-1,k-1)+g(i-1,j-1,k)

    当A[ i ]!=B[ j ]时:

        f(i,j,k)=0

对于g(i,j,k):

         g(i,j,k)=f(i,j,k)+g(i-1,j,k)

边界:

g(i,0,0)=1\,\,\,(0<i\leqslant n)

代码:(用滚动数组优化)

#include
#include
#include
using namespace std;
const int MOD=1000000007;
char A[1005],B[205];
int n,m,k,f[2][205][205],g[2][205][205];
/*
if(A[i]==B[j])
    f[i][j][k]=f[i-1][j-1][k-1]+g[i-1][j-1][k]
else
    f[i][j][k]=0;

g[i][j][k]=g[i-1][j][k]+f[i][j][k]
*/
int main(){
    scanf("%d%d%d",&n,&m,&k);
    scanf("%s%s",A+1,B+1);
    g[0][0][0]=1;
    for(int i=1;i<=n;i++){
        g[i%2][0][0]=1;
        for(int j=1;j<=m;j++)
        for(int K=1;K<=k;K++){
            if(A[i]==B[j])
                f[i%2][j][K]=(f[(i-1)%2][j-1][K]+g[(i-1)%2][j-1][K-1])%MOD;
            else
                f[i%2][j][K]=0;
            g[i%2][j][K]=(g[(i-1)%2][j][K]+f[i%2][j][K])%MOD;
        }
    }
    printf("%d\n",g[n%2][m][k]);
    return 0;
}

T3:运输计划

考察知识:LCA,二分,树上差分,树链剖分

算法难度:XXXX+ 实现难度:XXXX

说明:在看下面题解之前,请确保你已经掌握了“考察知识”中除了“树链剖分”的所有内容

分析:确实有难度,但是如果你想出了算法,就会发现这道题其实就是一堆模板(LCA,树上差分,二分)的合集。

算法流程+分析:

        1.我们首先计算所有运输计划每一个需要的时间,然后按时间从小到大排序。我们可以用LCA或树链剖分解决。

        2.接着我们要考虑删边了,我们应该删除那一条边呢?枚举显然TLE。因为是问删边后的最大时间时间最小,是最大最小问题,我们考虑用二分解决

        3.我们二分求删边后最小的最大时间,设为mid,那么我们找出所有时间大于mid的运输计划,假设有k个,然后我们树上差分,将所有时间大于mid的运输计划需要经过的边标记+1。

        4.我们dfs计算差分数组,然后枚举标记值等于k的所有的边,如果有一条边:  经过这条边的需要时间+mid>=不删边的最大时间 ,那么mid满足条件,否则不满足,然后我们继续二分就可以了。

代码:

说明:我采用的树链剖分计算LCA和经过边的边权,建议大家自己写代码,这道题细节还是比较多,代码量也比较大,比较容易写错。如果自行用多种方法写出代码,对能力提升是较大的。

#include
#include
#include
#include
using namespace std;
const int maxn=300005;
char ch;
void scan(int& in_){
    ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    in_=0;
    while(isdigit(ch))in_=in_*10+ch-'0',ch=getchar();
}
struct transport_plan{
    int u,v,L,lca;
    friend bool operator < (const transport_plan& A,const transport_plan& B){
        return A.Ldep[y]) swap(x,y);
    ret+=S[seg[y]]-S[seg[x]];
    return ret;
}
int ask_lca(int x,int y){
    int fx=top[x],fy=top[y];
    while(fx!=fy){
        if(dep[fx]dep[y]) swap(x,y);
    return x;
}
//-------------^END^-----------------
int n,m,l,r,k,C[maxn],CC[maxn];
void dfs3(int i,int Fa){//树上差分计算 
    CC[i]=C[i];
    for(int p=head[i];p;p=e[p].next){
        int j=e[p].to;
        if(j==Fa) continue;
        dfs3(j,i);
        CC[i]+=CC[j];
    }
}
bool check(int limt){//二分判断函数 
    tmp.L=limt;
    int i=upper_bound(TP+1,TP+m+1,tmp)-TP;
    k=m-i+1;
    memset(C,0,sizeof(C));
    for(;i<=m;i++)
        C[TP[i].u]++,C[TP[i].v]++,C[TP[i].lca]-=2;
    dfs3(1,0);
    for(int ii=1;ii<=n;ii++)
        if(CC[ii]>=k&&TP[m].L-D[ii]<=limt) return true;
    return false;
}
void build(){ 
    int u,v,L;
    scan(n),scan(m);
    for(int i=1;i>1;
        if(check(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%d\n",ans);
}
int main(){
    build();
    solve();
    return 0;
}

 

你可能感兴趣的:(竞赛考试,NOIP提高组历年考试)