NOIP2012提高组Day1

NOIP2012Day1

一、Vigenère 密码

数据:密钥长度∈[1,100],密文长度∈[1,1000]

很简单的一道题,只要对着推一下公式就行了。

二、国王游戏

数据:
对于 20%,n∈[1,10]
对于 60%,n∈[1,100],保证答案不超过 109
对于 100%,n∈[1,1,000]

20%的暴力全排就行了

答案要超long long,所以要高精度

正解是贪心。对于一个数Num(i),他的奖金是包括自己前面所有人的L的积去除以自己左右手上的数字。那么只要按照每个人的左右手乘积排个序就行了。
当乘积相同的时候,L小的在前面,这样保持乘积最小。

Code:

#include<bits/stdc++.h>
#define M 10000
#define ll long long
using namespace std;
struct node{ll l,r;}A[1005];
bool cmp(node x,node y){//按照左右手乘积排序
    if(x.l*x.r==y.l*y.r)return x.l>y.l;
    return x.l*x.r<y.l*y.r;
}
struct bignum{//高精度
    int num[1005];
    int len;
    bignum(){memset(num,0,sizeof num);len=1;}
    bignum operator *(ll x){//高精*低精
        bignum tmp;
        tmp.len=len;
        for(int i=1;i<=len;i++){
            tmp.num[i]+=num[i]*x;
            tmp.num[i+1]+=tmp.num[i]/M;
            tmp.num[i]%=M;
        }
        while(tmp.num[tmp.len+1])tmp.len++;
        return tmp;
    }
    bignum operator /(ll x){//高精/低精
        bignum tmp;
        tmp.len=len;
        for(int i=1;i<=len;i++)tmp.num[i]=num[i];
        for(int i=len;i>=1;i--){
            if(i>1)tmp.num[i-1]+=tmp.num[i]%x*M;
            tmp.num[i]=tmp.num[i]/x;
        }
        while(tmp.num[tmp.len]==0&&tmp.len>1)tmp.len--; 
        return tmp;
    }
    bool operator <(bignum x){//重载小于运算符
        if(len!=x.len)return len<x.len;
        for(int i=len;i>=1;i--)if(num[i]!=x.num[i])return num[i]<x.num[i];
        return 0;
    }
    void Print(){
        printf("%d",num[len]);
        for(int i=len-1;i>=1;i--)printf("%04d",num[i]);
        puts("");
    }
};
int main(){
    int n;
    scanf("%d",&n);
    for(int i=0;i<=n;i++)scanf("%lld%lld",&A[i].l,&A[i].r);
    sort(A+1,A+1+n,cmp);
    bignum h,mx;h.num[1]=1;h=h*A[0].l;
    for(int i=1;i<=n;i++){//直接计算
        if(mx<h/A[i].r)mx=h/A[i].r;
        h=h*A[i].l;
    }
    mx.Print();
    return 0;
}

三、开车旅行

数据:
对于 70%,N∈[1,1e3],M∈[1,1e5]
对于 100%,N∈[1,1e5],M∈[1,1e5]

这道题暴力可以过70分,只要把每个点往后一个和两个的城市全部预处理出来就行了。这样就只用nm了。

这道题也可以分成两个问题:
1、快速求一个城市往后一个、两个的城市
2、快速求往后一段距离可以行走的距离

对于1,暴力的解法是n^2,把所有后面的点都判断一遍。实际上真正会影响到的点只有四个。这四个是在用了数据结构后的说法。对于每一个点,和他距离相近的两个肯定位于这个点往后两个和往前两个。可以用set,multiset,双向链表等。想法也很简单,倒着把每个数放进去,这样可以保持方向的一致,然后用一些可以保持高度从小到大顺序的数据结构就行了。预处理就成了n log n。

对于2,可以用倍增。倍增的时候要把小A和小B“绑起来”,看做一个人来计算,但是存距离的时候要分开来,因为要分开来算。计算就和以前的一模一样。

Code:

#include<bits/stdc++.h>
#define M 100005
#define ll long long
using namespace std;
int n,H[M],nxt[2][M];
struct node{
    int x,id;
    bool operator < (const node &s)const{
        return x<s.x;
    }
}A[M];
int dis(int x,int y){
    if(y==0)return 2e9;
    return int(abs(H[x]-H[y]));
}
set<node>Set;
set<node>::iterator it;
void solve(int i){//四条判断,也可以存起来然后用sort
    if(dis(i,(*it).id)<dis(i,nxt[0][i]))nxt[1][i]=nxt[0][i],nxt[0][i]=(*it).id;
    else if(dis(i,(*it).id)==dis(i,nxt[0][i])&&H[(*it).id]<H[nxt[0][i]])nxt[1][i]=nxt[0][i],nxt[0][i]=(*it).id;
    else if(dis(i,(*it).id)<dis(i,nxt[1][i]))nxt[1][i]=(*it).id;
    else if(dis(i,(*it).id)==dis(i,nxt[1][i])&&H[(*it).id]<H[nxt[1][i]])nxt[1][i]=(*it).id;
}
void Init(){//预处理1:处理每个点往后的城市
    for(int i=n;i>=1;i--){
        Set.insert((node){H[i],i});
        it=Set.find((node){H[i],i});
        if(it!=Set.end()){//往后
            it++;int p=1;
            if(it!=Set.end()){solve(i);p=0;}
            it++;
            if(it!=Set.end()&&p==0)solve(i);
        }
        it=Set.find((node){H[i],i});
        if(it!=Set.begin()){//往前
            it--;solve(i);
            if(it!=Set.begin()){it--;solve(i);}
        }
    }
}//这么写有点麻烦,可以有简便的方法
ll da[20][M],db[20][M],fa[20][M];
void Init1(){//预处理2:把倍增处理出来
    for(int i=1;i<=n;i++){
        fa[0][i]=nxt[0][nxt[1][i]];
        if(nxt[1][i]){//往后第二个城市能到达
            da[0][i]=dis(i,nxt[1][i]);
            if(nxt[0][nxt[1][i]])db[0][i]=dis(nxt[1][i],nxt[0][nxt[1][i]]);//往后一个能到达
        }
    }
    for(int j=1;j<20;j++)
        for(int i=1;i<=n;i++){
            fa[j][i]=fa[j-1][fa[j-1][i]];
            da[j][i]=da[j-1][i]+da[j-1][fa[j-1][i]];
            db[j][i]=db[j-1][i]+db[j-1][fa[j-1][i]];
        }
}
void Walk(int s1,ll x,ll &Da,ll &Db){
    for(int i=19;i>=0;i--)//倍增跳跃
        if(da[i][s1]+db[i][s1]+Da+Db<=x)
            Da+=da[i][s1],Db+=db[i][s1],s1=fa[i][s1];
    if(da[0][s1]+Da+Db<=x)//单独判断小A能否再走一次
        Da+=da[0][s1];
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&H[i]),A[i]=(node){H[i],i};
    Init();Init1();
    ll x0;int ans1=1;double mn=1e9;
    scanf("%lld",&x0);
    for(int i=1;i<=n;i++){
        ll Da=0,Db=0;//高度很大,要开long long
        Walk(i,x0,Da,Db);
        if(Db==0&&mn==1e9){if(H[ans1]<H[i])ans1=i;}
        else if(mn-1.0*Da/Db>1e-9){ans1=i;mn=1.0*Da/Db;}
        else if(mn-1.0*Da/Db<1e-9&&mn-1.0*Da/Db>=0&&H[ans1]<H[i])ans1=i;
    }
    printf("%d\n",ans1);
    int m;
    scanf("%d",&m);
    while(m--){
        ll x1,Da=0,Db=0;int s1;
        scanf("%d%lld",&s1,&x1);
        Walk(s1,x1,Da,Db);
        printf("%lld %lld\n",Da,Db);
    }
    return 0;
}

考试的时候第三题暴力里把一个变量名打错了,然后跟上面的变量名重复了,然后就只有5分,改了一下就70了。要是改成函数就不会错了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值