noip 2015 day2解题报告

写在前面

因为本解题报告采用了许多大佬的题解与代码,所以记录不过来便设置为转载,如果侵犯了大佬的权益请联系我(也就是转侵删)

跳石头

  1. 题目描述

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

代码:
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=50005;
int a[maxn],n,m,ans;
bool check(int x)
{
    int sum,last;
    sum=0;last=0;
    for (int i=1;i<=n;i++)
    {
        if (a[i]-last<x) {  sum+=1;continue;}//如果小于就移走当前的石头 
        last=a[i];
    }
    if (sum>m) return 0;
    return 1;
}
int main()
{
    int L,R,mid;
    scanf("%d%d%d\n",&L,&n,&m);
    for (int i=1;i<=n;i++) scanf("%d\n",&a[i]);
    n+=1;
    a[n]=L;R=L;
    ans=L=0;
    while (L<=R)
    {   
        mid=L+(R-L)/2;
        if (check(mid))
        {
          ans=mid;
          L=mid+1;  
        }
        else R=mid-1;
    }//二分过程 
    printf("%d",ans);
    return 0;
}  

子串

 1. 思路
     首先讲一种最简单的DP吧(也是我最容易看懂的),不过在洛谷上似乎有更优解
     我们令f[i][j][k][0/1]表示A串用了前i个字符,B串已覆盖前j个字符,目前为止已经选了k个子串,最后的0/1表示A串的这个字符选了没有(0没选,1选了)。
     为了得出状态转移方程,我们分情况讨论:先看f[i][j][k][1](当前位选了),显然当且仅当a[i]=b[j]的时候它才有意义,否则f[i][j][k][1]=0。
到这个状态有三种方法:
上一位没有选,新开一个子串 
上一位选了,延续这个子串
上一位选了,但是仍然新开一个子串
因此,我们有
f[i][j][k][1]=f[i-1][j-1][k-1][0]+f[i-1][j-1][k][1]+f[i-1][j-1][k-1][1]。
    状态转移方程中的三项分别对应上述三种情况。注意,因为我们规定了A的这一位必须选(因为状态的最后一维是1),所以所有前驱状态一定是f[i-1][j-1][…][…]。
然后讨论另一种情况:这个字符不选。
这个比较简单,到这个状态有两种方法:
上一位没有选,现在仍然不选
上一位选了,结束这个子串
 因此,我们有
f[i][j][k][0]=f[i-1][j][k][0]+f[i-1][j][k][1]。
合起来就是
f[i][j][k][1]=f[i-1][j-1][k-1][0]+f[i-1][j-1][k][1]+f[i-1][j-1][k-1][1](a[i]=b[j])
f[i][j][k][1]=0(a[i]!=b[j])
f[i][j][k][0]=f[i-1][j][k][0]+f[i-1][j][k][1]
状态转移方程有了,边界也容易确定:f[0][0][0][0]=1。至于最终答案,显然是f[n][m][k][0]+f[n][m][k][1]
然后用滚动数组压掉一维就可以
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1005,M=205,K=205,MOD=1000000007;
int n,m,p;
char a[N],b[M];
int f[2][M][K][2],pre=1,now=0,ans=0;
void dp(){
    //因为我们发现i时的值只由i-1传递过来,所以只需要用pre,now来定义i-1,i; 
    f[pre][0][0][0]=f[now][0][0][0]=1;//初始化 
    for(int i=1;i<=n;i++,swap(now,pre))//把现在的now数组存i,pre为i-1; 
        for(int j=1;j<=m;j++)
            for(int k=1;k<=p;k++){
                if(a[i]==b[j]) 
                    f[now][j][k][1]=(f[pre][j-1][k-1][0]+f[pre][j-1][k][1])%MOD;
                else 
                    f[now][j][k][1]=0;
                    f[now][j][k][0]=(f[pre][j][k][0]+f[now][j][k][1])%MOD;//DP主体; 
            }
}
int main() {
    scanf("%d%d%d%s%s",&n,&m,&p,a+1,b+1);
    dp();
    cout<<f[pre][m][p][0]; 
    return 0;
}

运输计划

  1. 题意
    给你一棵树,有很多条路径,让你把一条边的边权设为0,使路径的最大值最小
  2. 思路
    lca+二分
    大概思路就是首先二分一个答案(时间),然后对各个计划进行遍历求值,记录时间大于答案的路线并维护最大差值sum,然后寻找这几条路线里有没有公共边且使最长公共边长度变为0后是否合法,不合法就把时间扩大,合法就缩小
    (思路确实简单)
/*算法:tarjan + 二分
思路:先找lca,可以在dfs的时候就用tarjan直接算出,然后找出s -> t的路程,即 q[i].dis = deep[q[i].from] + deep[q[i].to] - 2* deep[q[i].lca];
继续进行二分,对于路程大于假定答案的就cnt++,不然就break,
因为提前排过序,最后发现只能对于cnt==num[i]的点进行虫洞操作,最后如果大于就继续缩小,反之增大上限。
c++代码如下:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<queue>
#include<stack>
using namespace std;
const int N = 3e5 + 1;
int head[N],f[N],deep[N],v[N],num[N],headm[N],tot,n,m;
inline void read(int&x)
{
    x=0;char c;int sign=1;
    do{ c=getchar();if(c=='-') sign=-1;}while(c<'0'||c>'9');
    do{ x=x*10+c-'0';c=getchar();}while(c<= '9'&&c>='0');
    x *= sign;
}//读入优化 
struct node
{
    int to,next,val;
}edge[N*4];//结构体 
struct str
{
    int from,to,lca,dis;
}q[N];
void add(int head[],int a,int b,int val)
{
    edge[tot].next=head[a];
    edge[tot].to=b;
    edge[tot].val=val;
    head[a]=tot++;
}
inline int get_fa(int x) { return x==f[x]?x:f[x]=get_fa(f[x]);}//找爸爸(你的就不用找了,因为就是我[滑稽]) 
void dfs(int u,int fa)
{
    f[u]=u;
    for(int i=headm[u];~i/*这表示i=-1就结束因为初始化时数组定的是-1(第87行)*/;i=edge[i].next)
    {
        node &e=edge[i];//其实觉得这一段没这么必要,不过简洁了些; 
        if(f[q[e.to].from]&&u==q[e.to].to) q[e.to].lca=get_fa(q[e.to].from);
        if(f[q[e.to].to]&&u==q[e.to].from) q[e.to].lca=get_fa(q[e.to].to);//求LCA 
    }
    for(int i=head[u];~i;i=edge[i].next)
    {
        node&e=edge[i];
        if(e.to==fa) continue;
        deep[e.to]=deep[u]+e.val;//更新深度 
        dfs(e.to,u);
        v[e.to]=e.val;
    }
    f[u]=fa;//确立父子关系 
}
const bool cmp(str a,str b){ return a.dis>b.dis;}
void find(int u ,int fa)
{
    for(int i=head[u];~i;i=edge[i].next)
    {
        node&e=edge[i];
        if(e.to==fa)continue;
        find(e.to,u);
        num[u]+=num[e.to];
    }
}//标记上推; 
bool check(int x)
{
    int cnt=0,dec=0;
    memset(num,0,sizeof(num));//清零标记; 
    for(int i=1;i<=m;i++)
        if(q[i].dis>x){cnt++;dec=max(dec,q[i].dis-x);num[q[i].from]++;num[q[i].to]++;num[q[i].lca]-=2;}
        //处理非法路,因为待会标记会上推所以最近公共祖先先-=2,这样等下推的时候最近公共祖先的数组值仍会为0,就不会影响结果了; 
        else break;
    find(1,1);
    for(int i=1;i<=n;i++)
        if(cnt==num[i]&&v[i]>=dec)return 1;//如果删去的这个路的值比最大差值大就return 1否则return 0return 0;
}
int main()
{
    memset(head,-1,sizeof(head));memset(headm,0,sizeof(headm));//初始化数组; 
    read(n);read(m);
    int u,v,val;
    for(int i=1;i<n;i++)
    {
        read(u);read(v);read(val);
        add(head,u,v,val);add(head,v,u,val);//边数组 
    }
    for(int i=1;i<=m;i++)
        read(q[i].from),read(q[i].to),add(headm,q[i].from,i,0),add(headm,q[i].to,i,0);//询问数组; 
    dfs(1,1);//也就是所谓的trajan; 
    int l=0,r=0,num=0;
    for(int i=1;i<=m;i++){q[i].dis=deep[q[i].from]+deep[q[i].to]-2*deep[q[i].lca];r=max(q[i].dis,r);}//求最短路; 
    sort(q+1,q+1+m,cmp);//将路径从大到小排序便于进行 
    while(l<=r)
    {
        int mid=(l+r)>> 1;
        if(check(mid))num=mid,r=mid-1;
        else l=mid+1; 
    }//二分主体; 
    cout<<num<<endl;
    return 0;
}

这里算是个人的一些感想???

1.这几道题目可是折磨了我好久(主要第三道题,也体会到了大佬们的超神)
2.找题解的过程里发现好多大佬的网页是真的可爱(雾?),我也最喜欢去看这样的题解,因为看起来不是冷冰冰的,运用到与人交流上或许也是如此。
3.发现大佬的思路跟我完全是不同的,比喻为走路的话我或许是想着怎么走到,而大佬却考虑
的是怎么快点走到,所以有好多优化啊那种东西完全看不懂,这或许也就级别的差距吧,我也要好好的加强自己了,至少要追到能看到大佬的背影(%%%)
这里写图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值