NOIP模拟题[二分][树规][倍增][双向链表]

11 篇文章 0 订阅
8 篇文章 0 订阅

感觉自己的弱点在代码实现上面,就是知道怎么写但写要很久而且易错难调,解决措施:
1.敲键盘之前要画好流程图,想好一共需要几个函数,函数的功能以及要怎么实现,需要用到哪些库函数哪些数据结构等等,思路要清晰,思路清不清晰看程序就知道。
2.如果是改程序,就先理清楚标程是怎么实现的,基本上按照标程写,把握标程好的代码特色。
3.如果是调试,就在每一步理清楚程序的数学本质,如果还调不好就:
1)对比标程,有利于对程序的理解(改考试时)
2)用易错数据,有利于培养考试能力(平时刷题时);
注意效率,不要一直交一直错。
来正事:

T1:
题意:给定x,y轴正半轴上的n个点,一一相连使不相交,然后针对每个提问(共m个)回答与多少条线段相交。
分析:
一眼二分题。
即对x,y轴上的点排序,对每个询问,二分在x,y轴上的点的相对位置,判断是否相交。
二分很容易错,要么就老老实实地进行检验,要么就用好的模板(我选择两个一起)(下面附模板)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define clr(x) memset(x,0,sizeof(x))
using namespace std;
const int maxn=1e5+5;
int n,q;
long long x[maxn],y[maxn],cur1,cur2;
int read()
{
    char tmp[11];int ret=0;clr(tmp);
    scanf("%s",tmp);int len=strlen(tmp);
    for(int i=0;i<len;i++)ret=ret*10+tmp[i]-'0';
    return ret;
}
void init()
{
    n=read();
    for(int i=1;i<=n;i++)x[i]=(long long)read();
    for(int i=1;i<=n;i++)y[i]=(long long)read();
    q=read();sort(x+1,x+n+1);sort(y+1,y+n+1);
}
void work()
{
    int lef=0,rig=n,mid;
    while(lef<=rig){
        mid=(lef+rig)/2;
        long long res1=cur2*x[mid]+cur1*y[mid];
        long long res2=x[mid]*y[mid];
        if(res1>res2)lef=mid+1;
        else if(res1<res2)rig=mid-1;
        else break;
    }
    mid=(rig+lef)/2;
    long long res1=cur2*x[mid]+cur1*y[mid];
    long long res2=x[mid]*y[mid];
    if(res1<res2)mid--;
    res1=cur2*x[mid+1]+cur1*y[mid+1];
    res2=x[mid+1]*y[mid+1];
    if(res1>res2)mid++;
    printf("%d\n",mid);
}
int main()
{
    freopen("geometry.in","r",stdin);
    freopen("geometry.out","w",stdout);
    init();
    for(int i=1;i<=q;i++){
        cur1=(long long)read();
        cur2=(long long)read();
        work();
    }
    return 0;
}

某大神的模板,基本上不用判断,不过考试的时候还是分析一下会不会有特殊情况吧。

int l=1 , r=n+1;
    while(l<r) // the minimum line above P(x0,y0)
    {
        int m = (l+r)>>1;
        if(check(m)) r = m;//在下面;
        else l = m + 1;
    }
    return l-1;

(为什么不特判:每次都是l在加一,则最后出问题的要么是r=l+1时m=l不满足,则l-1可以
要么是l=m+1=r时m满足r不满足,则l-1也可以
综上,该返回值总是没问题。)
T2:
题意:在一棵树上某个点可以付出一些代价获取向上走若干步的能力,且在走的过程中可以放弃已获得的能力换取该点能力,当然也要付出相应的代价,给出该树和“能力售卖点”,求从某个点走到根的最小代价;
分析:
第一眼,树规,直接往上跳有80分。
第二眼,用倍增保存某段上能力最小值可以过完。
具体来说就是对于每个出发点都必须在那里买一张票,f[i][j]就存从包括i点的共2的j次方个点出发的最小代价,然后在dfs的同时枚举这个点的方案数,找出在这里买了票能到的点的代价最小值,然后再对于这个点及祖先节点倍增处理,
一个经典的错,f和fa里的第二维一个包括自己一个不包括,我错误地当成了二倍关系。所以模板要多写啊,不然容易犯理所当然的错,毕竟一开始照着模板并没有推敲每一步的正确性。
还有写pow函数的习惯不好,,,下次改成位运算不然容易T QAQ

#include<iostream>
#include<cstdio>
#include<cstring>
#define clr(x) memset(x,0,sizeof(x))
using namespace std;
const int maxn=1e5;
int fr[maxn],tov[maxn],des[maxn],frqua[maxn],tovqua[maxn],w[maxn],k[maxn],fa[maxn*4][20],f[maxn*4][20];
int n,m,v,sta,fin,q;
int read()
{
    char tmp[11];
    int len,ret=0;clr(tmp);
    scanf("%s",tmp);len=strlen(tmp);
    for(int i=0;i<len;i++)ret=ret*10+tmp[i]-'0';
    return ret;
}
void addedge(int cur)
{
    sta=read();fin=read();
    tov[cur]=fr[fin];fr[fin]=cur;des[cur]=sta;
    fa[sta][0]=fin;
}
void addedge2(int cur)
{
    v=read();k[cur]=read();w[cur]=read();
    tovqua[cur]=frqua[v];frqua[v]=cur;
}
int poww(int t)
{
    int p=1;
    for(int i=1;i<=t;i++)p*=2;
    return p;
}
void bz(int u)
{
    if(u==1)return;
    f[u][0]=1e9;
    for(int i=frqua[u];i;i=tovqua[i]){
        int tmp=k[i],tmp3=fa[u][0],tmp2=f[tmp3][0];
        for(int j=17;j>=0;j--)if(tmp>=poww(j)){
            tmp2=min(tmp2,f[tmp3][j]);
            tmp3=fa[tmp3][j];
            tmp-=poww(j);
        }
        f[u][0]=min(f[u][0],tmp2+w[i]);
    }
    for(int i=1;i<=17;i++)
        fa[u][i]=fa[fa[u][i-1]][i-1];
    for(int i=1;i<=17;i++)
        f[u][i]=min(f[u][i-1],f[fa[u][i-1]][i-1]);
}
void dfs(int u)
{
    bz(u);
    for(int i=fr[u];i;i=tov[i])
        dfs(des[i]);
}
void init()
{
    n=read();m=read();
    for(int i=1;i<n;i++)addedge(i);
    for(int i=1;i<=m;i++)addedge2(i);
    dfs(1);
    q=read();
}
void work()
{
    sta=read();
    printf("%d\n",f[sta][0]);
}
int main()
{
    freopen("party.in","r",stdin);
    freopen("party.out","w",stdout);
    init();
    for(int i=1;i<=q;i++)work();
    return 0;
}

T3:
题意:对一个字符串做操作,包括光标左右移,插入删除显示和反转左右光标间的字符。
第一眼:双向链表;
第二眼:标记;
具体来说,就是双线链表支持各种操作,特别的,对于反转操作,要把要反转的两边都改完,反转部分的端点改完,剩下的在操作的时候检验反向就可以了。
还有链表的特色,前两个点一个是开始节点一个是结束节点,只有这样才好判断这样也很巧妙!
开始写之前一定要把这种细节想清楚不然敲键盘效率会很低!
还有就是要学会抓住问题的关键,这里的左右光标居然把我难倒了明明可以用一种方法表示。

再谈一谈常数的问题:
不改inline,getchar()过不完,改了也没过完,再改一个show()里的putchar()变成了0.8s,改完了putchar()变成了0.4s,某大神位运算0.3s,这里就是常数,至少十倍。
代码风格真的很重要,尤其是大数据输入输出,inline和位运算,不仅让代码好看,还快很多啊。

顺便,思路理清楚以后,虽然代码130+行,写起来很快很清晰,这就是磨刀不误砍柴工啊。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=8e6+5;
int suf[maxn],pre[maxn],pos[2],cnt[2],n,len,idx,m;
char ch[maxn],c,w,add,tmp[maxn/2];
void init()
{
    scanf("%s",tmp);len=strlen(tmp);
    pos[0]=pos[1]=cnt[0]=cnt[1]=1;
    pre[1]=-1;suf[1]=2;
    pre[2]=1;suf[2]=-1;
    idx=2;
    for(int i=0;i<len;i++){
        ch[++idx]=tmp[i];
        pre[idx]=i==0?1:idx-1;
        suf[idx]=i==len-1?2:idx+1;
    }
    if(len>0){
        pos[1]=idx;cnt[1]=idx+1;
        suf[1]=3;
        pre[2]=idx;
    }
    scanf("%d",&m);
}
inline int dir()
{
    getchar();w=getchar();
    return w=='L'?0:1;
}
inline void movelef(int opt)
{
    if(pos[opt]==1)putchar('F');
    else{
        int u=pos[opt],v=pre[u];
        if(u!=suf[v])swap(pre[v],suf[v]);
        pos[opt]=v;
        cnt[opt]--;
        putchar('T');
    }
    putchar('\n');
}
inline void moverig(int opt)
{
    if(suf[pos[opt]]==2)putchar('F');
    else{
        int u=suf[pos[opt]],v=suf[u];
        if(pre[v]!=u)swap(pre[v],suf[v]);
        pos[opt]=u;
        cnt[opt]++;
        putchar('T');
    }
    putchar('\n');
}
inline void inser(int opt)
{
    getchar();add=getchar();
    int u=pos[opt],v=suf[pos[opt]];
    ch[++idx]=add;
    pre[idx]=pos[opt];
    suf[idx]=v;
    pre[v]=idx;
    suf[u]=idx;
    pos[opt]=idx;
    //if(cnt[opt^1]==cnt[opt])pos[opt^1]=idx;
    if(pos[opt^1]==u)pos[opt^1]=idx;
    if(cnt[opt^1]>=cnt[opt])cnt[opt^1]++;
    cnt[opt]++;
    putchar('T');
    putchar('\n');
}
inline void del(int opt)
{
    if(suf[pos[opt]]==2)putchar('F');
    else{
        int u=suf[pos[opt]],v=suf[u];
        if(pre[v]!=u)swap(pre[v],suf[v]);
        suf[pos[opt]]=v;
        pre[v]=pos[opt];
        if(pos[opt^1]==u)pos[opt^1]=v;//关联;
        if(cnt[opt^1]>cnt[opt])
        cnt[opt^1]--;
        putchar('T');
    }
    putchar('\n');
}
inline void rever()
{
    if(cnt[1]-cnt[0]<=0)putchar('F');//读题;
    else {
    if(cnt[1]-cnt[0]>1){
    int a=pos[0],b=suf[a],c=pos[1],d=suf[c];
    swap(suf[b],pre[b]);swap(suf[c],pre[c]);
    suf[a]=c;pre[c]=a;
    suf[b]=d;pre[d]=b;
    pos[1]=b;
    }
    putchar('T');
    }
    putchar('\n');
}
inline void showin()
{
    int u=1;
    while(true){
        if(pre[suf[u]]!=u)swap(suf[suf[u]],pre[suf[u]]);
        u=suf[u];
        if(u==2)break;
        putchar(ch[u]);
    }
    putchar('\n');
}
void work()
{
    for(int i=1;i<=m;i++){
        getchar();
        c=getchar();
        if(c=='<')movelef(dir());
        else if(c=='>')moverig(dir());
        else if(c=='I')inser(dir());
        else if(c=='D')del(dir());
        else if(c=='R')rever();
        else showin();
    }
}
int main()
{
    freopen("editor.in","r",stdin);
    freopen("editor.out","w",stdout);
    init();
    work();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值