NOIP2015提高组DAY2题解

版权声明:欢迎转载+原文章地址~ https://blog.csdn.net/Hi_KER/article/details/82759911

T1:跳石头

考察知识:二分,模拟

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

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

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

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

#include<cstdio>
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<limt) cnt++;
        else l=a[i];
    return cnt<=m;
}
int main(){
    scanf("%d%d%d",&L,&n,&m);a[n+1]=L;
    for(int i=1;i<=n;i++) scanf("%d",a+i);
    int l=1,r=L,ans;
    while(l<=r){
        int mid=(l+r)>>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<x<i)不为同一字串(A[ i ]为第k个,A[ x](0<x<i)为第k-1个),时有f(i-1,j-1,k)种情况

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

对于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<cstdio>
#include<cstring>
#include<algorithm>
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<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
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.L<B.L;
    }
}TP[maxn],tmp;
struct edge{
    int to,next,L;
}e[maxn*2];
int head[maxn],np;
void adde(int u,int v,int L){
    e[++np]=(edge){v,head[u],L};
    head[u]=np;
    e[++np]=(edge){u,head[v],L};
    head[v]=np;
}
//-----------V树链剖分V------------- 
int fa[maxn],son[maxn],dep[maxn],sz[maxn],seg[maxn],top[maxn];
int S[maxn],D[maxn],P;
void dfs1(int i,int Fa){
    dep[i]=dep[Fa]+1,sz[i]=1,fa[i]=Fa;
    for(int p=head[i];p;p=e[p].next){
        int j=e[p].to;
        if(j==Fa) continue;
        dfs1(j,i);
        sz[i]+=sz[j],D[j]=e[p].L;
        if(sz[son[i]]<sz[j]) son[i]=j;
    }
}
void dfs2(int i){
    if(son[i]){
        top[son[i]]=top[i],seg[son[i]]=++P,S[P]=D[son[i]];
        dfs2(son[i]);
    }
    for(int p=head[i];p;p=e[p].next){
        int j=e[p].to;
        if(top[j]) continue;
        top[j]=j,seg[j]=++P,S[P]=D[j];
        dfs2(j);
    }
}
int ask_path(int x,int y){
    int fx=top[x],fy=top[y],ret=0;
    while(fx!=fy){
        if(dep[fx]<dep[fy]) swap(x,y),swap(fx,fy);
        ret+=S[seg[x]]-S[seg[fx]]+D[fx];
        x=fa[fx],fx=top[x];
    }
    if(dep[x]>dep[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[fy]) swap(x,y),swap(fx,fy);
        x=fa[fx],fx=top[x];
    }
    if(dep[x]>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<n;i++)
        scan(u),scan(v),scan(L),adde(u,v,L);
    for(int i=1;i<=m;i++)
        scan(u),scan(v),TP[i].u=u,TP[i].v=v;
    dfs1(1,0);
    top[1]=seg[1]=P=1;
    dfs2(1);
    for(int i=1;i<=n;i++) S[i]+=S[i-1];
    for(int i=1;i<=m;i++)
        TP[i].L=ask_path(TP[i].u,TP[i].v),
        TP[i].lca=ask_lca(TP[i].u,TP[i].v);
    sort(TP+1,TP+m+1);
}
void solve(){
    int l=0,r=TP[m].L,ans=0;
    while(l<=r){
        int mid=(l+r)>>1;
        if(check(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%d\n",ans);
}
int main(){
    build();
    solve();
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Hi_KER/article/details/82759911