NOIP2012复赛提高组day1(A:Vigenère 密码 B:国王游戏 C:开车旅行)

来源:互联网 发布:安捷伦gpc软件 编辑:程序博客网 时间:2024/05/13 19:58

A题:
在xiaoC创建的题库中有这道题,
我们都已经做过了
但是就算没做过
第一题是不可能会WA的
这题十分的简单
我们通过仔细观察会发现
密文=明文+密钥-‘A’/’a’;
(我们假定’Z’+1=’A’,’z’+1=’a’)
于是乎这题就被完美的解决了。。

#include<bits/stdc++.h>using namespace std;#define M 1005char Key[M],str[M];int main(){    scanf("%s %s",Key,str);    int n=strlen(str),m=strlen(Key);    for(int i=0;i<n;i++){        int j=i%m;        if(str[i]>='A'&&str[i]<='Z'&&Key[j]>='a'&&Key[j]<='z')Key[j]-=32;        else if(str[i]>='a'&&str[i]<='z'&&Key[j]>='A'&&Key[j]<='Z')Key[j]+=32;        if(str[i]>='a'&&str[i]<='z'){            str[i]-=Key[j]-'a';            if(str[i]<'a')str[i]+=26;           }else if(str[i]>='A'&&str[i]<='Z'){            str[i]-=Key[j]-'A';            if(str[i]<'A')str[i]+=26;           }    }puts(str);    return 0;}

B题:
这是有史以来最骚的联赛第二题
别的题最多就sort+贪心就结束了
这题偏要来个高精
还是个最冷门的高精除
不过幸好是高精除低精,还是比较容易写的
然而。。。
蒟蒻的博主连怎样排序都不会。。
傻乎乎的用a/b排序,最后完全过不了样例
于是博主要在此推销两法宝:
rand水分大法以及多排序叠加水分大法
多排序叠加水分大法是1113大神最先开发并使用的
在某一道但是我们整个机房都没几个人想到正解的贪心题中
这位大神足足敲(复制粘贴0.0)了20+种排序算法取最小值作为解,水到了60分,
而这道B题他也水到了60分(这次只用了7种)
于是他获得了一个封号:水分大王
而rand水分大法不知是哪位大神发明的
现在的应用范围十分普遍
使用随机数随便交换两个大臣,经过较多次数变换后取最小的值
蒟蒻博主用这种方法水到了60分
隔壁的jiedai大神40%rand,剩下是无高精的正解写法,最终50分(大神一不小心手抖了一下)
叫上rand水分大法的代码(多排序叠加有点长,不敢发。。):

//PS:这是rand水分大法的代码,要复制正解的同志别复制错了#include<bits/stdc++.h>using namespace std;#define M 1005#define LL long longint n;struct node{    int a,b;}s[M];int check(){    LL tmp=s[0].a,ans=0;    for(int i=1;i<=n;i++){        LL res=tmp/s[i].b;        tmp*=s[i].a;        if(res>ans)ans=res;    }return ans;}int main(){    srand(time(NULL));    scanf("%d",&n);    scanf("%d %d",&s[0].a,&s[0].b);    for(int i=1;i<=n;i++)scanf("%d %d",&s[i].a,&s[i].b);    LL ans=check();    for(int i=1;i<=40000;i++){//rand次数可以在调大一点点,但是不能太大,会TLE的(1e5->50分)        int a,b;        do {            a=rand()%n+1;            b=rand()%n+1;        }while(a==b);        node tmp=s[a];        s[a]=s[b];s[b]=tmp;        LL res=check();        if(res>0&&res<ans)ans=res;    }printf("%d\n",ans);    return 0;}

而后我们就可以来研究一下正解了
首先我们知道,若是最终答案
ni=1aibi,
那么根据bi从小到小排序结果无疑是最小的
为什么呢?

对于某一个x
我们可以假设后面的所有数都已经确定
即前x个数只是需要交换顺序
于是这个时候ni=1ai的值显然是固定的,
那么想让当前的值最小,必然是bi最大的大臣在x这个位置上
以此类推即可

但是我们知道这一题最终的答案并不是这个形式
而是ni=1ai1bi
那么怎么办呢
因为a0是不会变换位置的,我们可以不理它
而对于ain,我们可以使ki=ai*bi;
而后用ki排序就行了
于是值为ni=1aiki
当然,
众大神们还需要敲一个高精乘低精和高精除低精的模板
代码如下:

#include<bits/stdc++.h>using namespace std;#define M 1005#define W 10000#define P 100007#define LL long longstruct Bigint{    int num,val[M];    Bigint operator *(const int &A)const{//高精乘        Bigint res;res.num=num;        for(int i=1;i<=num;i++)res.val[i]=val[i]*A;        for(int i=1;i<=num;i++)            if(res.val[i]>=W){                res.val[i+1]+=res.val[i]/W;                res.val[i]%=W;            }        if(res.val[res.num+1])res.num++;        return res;    }    Bigint operator /(const int &A)const{//高精除        Bigint res;res.num=num;int tmp=val[num];        for(int i=num;i>=1;i--){            res.val[i]=tmp/A;            tmp=tmp%A*W+val[i-1];        }if(!res.val[num])res.num--;        return res;    }    bool operator <(const Bigint &A)const{//比较        if(num!=A.num)return num<A.num;        for(int i=num;i>=1;i--)            if(val[i]!=A.val[i])return val[i]<A.val[i];        return false;    }}ans[M],ANS;struct node{    int a,b,k;    bool operator <(const node &A)const{//排序        return k<A.k;    }}s[M];int main(){    int n;    scanf("%d",&n);    scanf("%d %d",&s[0].a,&s[0].b);    for(int i=1;i<=n;i++){        scanf("%d %d",&s[i].a,&s[i].b);        s[i].k=s[i].a*s[i].b;    }sort(s+1,s+n+1);    Bigint tmp;tmp.val[1]=s[0].a;tmp.num=1;    tmp=tmp*s[1].a;    ans[1]=tmp/s[1].k;ANS=ans[1];    for(int i=2;i<=n;i++){//这部分就没什么好说的了吧,简直是纯净水        tmp=tmp*s[i].a;        ans[i]=tmp/s[i].a/s[i].b;        if(ANS<ans[i])ANS=ans[i];    }printf("%d",ANS.val[ANS.num]);    for(int i=ANS.num-1;i>=1;i--)printf("%04d",ANS.val[i]);    puts("");    return 0;}

C题:
这题的70分是有史以来最简单的70分
但是这题的100分同样是最烦的100分
这题的70分。。。
水,水,水!!!
博主敲完的时候还不敢置信70分竟然这么简单。。
赤裸裸的暴力求最近点以及第二近点,
暴力走点直接70分
代码如下:

PS:这是70分无脑代码#include<bits/stdc++.h>using namespace std;#define M 100005#define oo 2000000000#define LL long longconst LL INF=2e16;void Rd(int &res){    char c;res=0;int k=1;    while(c=getchar(),!isdigit(c)&&c!='-');    if(c=='-')k=-1,c=getchar();    do res=(res<<3)+(res<<1)+(c^48);    while(c=getchar(),isdigit(c));    res*=k;}int H[M];LL l1[M],l2[M];int nxt2[M],nxt1[M];double res=1e15;int ans;void solve(int f,int pre,int s,int x){    int a=0,len1=0,len2=0;    while(1){        if(a==0&&(!nxt2[s]||len1+len2+abs(H[s]-H[nxt2[s]])>x))break;//跳跳跳!!!        if(a==1&&(!nxt1[s]||len1+len2+abs(H[s]-H[nxt1[s]])>x))break;        if(a==0){            len2+=abs(H[s]-H[nxt2[s]]);            s=nxt2[s];        }else {            len1+=abs(H[s]-H[nxt1[s]]);            s=nxt1[s];        }a=1-a;    }if(!f&&len1){//询问1        if(1.0*len2/len1<res){            res=1.0*len2/len1;            ans=pre;        }else if(1.0*len2/len1==res&&H[pre]>H[ans]){            ans=pre;        }    }if(f){//询问2        printf("%d %d\n",len2,len1);    }}int main(){    int n,m;    scanf("%d",&n);    for(int i=1;i<=n;i++){        Rd(H[i]);        l1[i]=INF;l2[i]=INF;    }for(int i=1;i<=n;i++){//暴力寻找nxt1以及nxt2        for(int j=i+1;j<=n;j++){            int h=abs(H[j]-H[i]);            if(h<l1[i]){                l2[i]=l1[i];                nxt2[i]=nxt1[i];                l1[i]=h;                nxt1[i]=j;            }else if(h==l1[i]){                if(H[j]<H[i]){                    l2[i]=l1[i];                    nxt2[i]=nxt1[i];                    l1[i]=h;                    nxt1[i]=j;                }else {                    l2[i]=h;                    nxt2[i]=j;                }            }else if(h<l2[i]){                l2[i]=h;                nxt2[i]=j;            }else if(h==l2[i]&&H[j]<H[i]){                l2[i]=h;                nxt2[i]=j;            }        }    }int x0;    scanf("%d",&x0);    int High=-oo;    for(int i=1;i<=n;i++)//如果是无穷大就有意思了        if(H[i]>High){            High=H[i];            ans=i;        }    for(int i=1;i<=n;i++)solve(0,i,i,x0);    printf("%d\n",ans);    scanf("%d",&m);    while(m--){        int s,x;        Rd(s);Rd(x);        solve(1,s,s,x);    }return 0;}

然而现在我们要对与这个70分代码进行优化
需要优化的地方有两个,
一个是寻找最近点和次近点(暴力O(n^2))
另一个就是走的过程了(暴力O(n*m))
走的过程需要用倍增这是显然的(O(nlogn))
那么重点就是寻找最近点和次近点了(O(n))
这个还是比较难想的
我们可以使用链表寻找
(NOIP2016初赛试卷中有这个方法)
(用堆博主不知道行不行,可能可以吧)
然后之后就是模拟了,
代码量比较大,调试耗时比较高。。

#include<bits/stdc++.h>using namespace std;#define S 22#define oo 2100000000#define M 100005#define LL long longvoid Rd(int &res){    char c;res=0;int k=1;    while(c=getchar(),!isdigit(c)&&c!='-');    if(c=='-')k=-1,c=getchar();    do res=(res<<3)+(res<<1)+(c^48);    while(c=getchar(),isdigit(c));    res*=k;}int H[M],rank[M],pre[M],nxt[M],LEN1[M];//H[x]代表城市x的高度//rank[x]就是排序的时候和求nxt以及pre的时候用的。。(映射下标)//pre[x]代表比点x小的最近点的下标//nxt[x]代表比点x大的最近点的下标//LEN1代表到最近点的距离int nxt2[S][M],nxt1[M];//nxt2[i][x]代表在点x跳2^i步到达的点//nxt1代表最近点LL len1[S][M],len2[S][M];//len1[i][x]代表在点x跳2^i步中小B走的距离(即走最近点)//len2[i][x]代表在点x跳2^i步中小A走的距离(即走次近点)double res=1e15;int ans;void Sort(int l,int r){//快排,由于我们需要维护下标的映射比较麻烦,于是干脆手敲sort,直接在sort中交换下标    int x=H[rank[(l+r)/2]],i=l,j=r,temp;    while(i<=j){        while(H[rank[i]]<x)i++;        while(H[rank[j]]>x)j--;        if(i<=j){//这个‘=’必须加,不然会死循环o!            temp=rank[i];rank[i]=rank[j];rank[j]=temp;            i++;j--;        }    }if(i<r)Sort(i,r);//博主刚开始的时候逗比了一下把递归调用放到了while里,结果瞬间TLE。。    if(l<j)Sort(l,j);//当时我都吓傻了(倍增也能TLE?一脸懵逼的我调试了半个小时,最后为了保护脑子不炸掉去楼下吹风,一回机房就找出了错误)}void solve(int f,int pre,int s,int x){//倍增跳O(nlogn)    int l1=0,l2=0;//没必要long long,题目使其已经<=x了;    for(int i=S-1;i>=1;i--)        while(nxt2[i][s]&&1ll*l1+l2+len1[i][s]+len2[i][s]<=x){//博主的倍增很烦,开了三个数组        //博主也不知道为什么要用while,正常的倍增应该是用if的,但是博主的daima用if就45分。。。            l1+=len1[i][s];//累加A和B分别走过的距离            l2+=len2[i][s];            s=nxt2[i][s];//跳        }    if(nxt2[0][s]&&l1+l2+len2[0][s]<=x){//纯模拟。。。。        l2+=len2[0][s];//累加A走过的距离        s=nxt2[0][s];//跳        if(nxt1[s]&&l1+l2+LEN1[s]<=x){            l1+=LEN1[s];//累加A走过的距离            s=nxt1[s];//跳        }    }if(!f&&l1){//询问1        if(1.0*l2/l1<res){//更新答案            res=1.0*l2/l1;            ans=pre;        }else if(1.0*l2/l1==res&&H[pre]>H[ans]){            ans=pre;        }    }if(f){//询问2        printf("%d %d\n",l2,l1);    }}int main(){    int n,m;    scanf("%d",&n);    for(int i=1;i<=n;i++){        Rd(H[i]);        rank[i]=i;    }int hi,sh;    Sort(1,n);    for(int i=1;i<=n;i++){//用rank数组映射点x的pre以及nxt        pre[rank[i]]=rank[i-1];        nxt[rank[i]]=rank[i+1];    }for(int i=1;i<=n;i++){//链表O(n);        hi=sh=oo;        if(pre[i])            sh=H[i]-H[pre[i]];        if(nxt[i])            hi=H[nxt[i]]-H[i];        if(sh<hi){            nxt1[i]=pre[i];//最近点            LEN1[i]=sh;            sh=oo;            if(pre[pre[i]])                sh=H[i]-H[pre[pre[i]]];            if(sh<=hi&&sh!=oo){                nxt2[0][i]=pre[pre[i]];//次近点                len2[0][i]=sh;            }else if(hi!=oo){//‘!=oo’这个判断尤其重要,缺了必死无疑                nxt2[0][i]=nxt[i];//次近点                len2[0][i]=hi;            }        }else if(sh>hi){            nxt1[i]=nxt[i];//最近点            LEN1[i]=hi;            hi=oo;            if(nxt[nxt[i]])                hi=H[nxt[nxt[i]]]-H[i];            if(sh<=hi&&sh!=oo){                nxt2[0][i]=pre[i];//次近点                len2[0][i]=sh;            }else if(hi!=oo){                nxt2[0][i]=nxt[nxt[i]];//次近点                len2[0][i]=hi;            }        }else {            if(sh!=oo){                nxt1[i]=pre[i];                LEN1[i]=sh;            }if(hi!=oo){                nxt2[0][i]=nxt[i];                len2[0][i]=hi;            }        }nxt[pre[i]]=nxt[i];        pre[nxt[i]]=pre[i];    }for(int j=1;nxt1[nxt2[0][j]];j++){//倍增的预处理(简单的模拟,可别手抖,很难调试的,博主深有体会)        nxt2[1][j]=nxt1[nxt2[0][j]];        len2[1][j]=len2[0][j];        len1[1][j]=LEN1[nxt2[0][j]];    }for(int i=2;i<S;i++){        for(int j=1;nxt2[i-1][nxt2[i-1][j]];j++){            nxt2[i][j]=nxt2[i-1][nxt2[i-1][j]];            len2[i][j]=len2[i-1][j]+len2[i-1][nxt2[i-1][j]];            len1[i][j]=len1[i-1][j]+len1[i-1][nxt2[i-1][j]];        }    }int x0;    scanf("%d",&x0);    int High=-oo;    for(int i=1;i<=n;i++)//当询问1所有点出发都为无穷大时        if(H[i]>High){            High=H[i];            ans=i;        }    for(int i=1;i<=n;i++)solve(0,i,i,x0);//询问1    printf("%d\n",ans);    scanf("%d",&m);    while(m--){        int s,x;        Rd(s);Rd(x);        solve(1,s,s,x);//询问2    }return 0;}

于是day1 100+60+70=230算是拿到了基本分吧
不过照这么下去一步一等奖希望很小啊

3 0