hzwer2015.9.13 NOIP模拟题 explo seq earth[DP][数论][二分][SPFA]

19 篇文章 0 订阅
11 篇文章 0 订阅

一套有难度的题,据说平均水平一百多,我感觉…差不多吧。
静下心来搞程序。
T1.
题意:对一个数给操作,分别是获得这个数乘以某个数的收益且成比例减小此数,或相反。求最后的最大收益。
分析:反向DP,因为当前决策会对后来产生影响,所以如果正向的话显然是不能存的,然后可以发现,现在的能力值对后面的影响是成正比的,并且这个无法用单价的提高来替换——毕竟单价本身不影响后面的收益,不过想到这里我们就可以发现,如果倒着推就可以排除能力值的影响,直接把能力值的影响体现在单价上——毕竟,现在的能力值不会影响之前的单价,而且最后的能力值对结果无影响。所以,就得到了反向推最高单价的做法。

#include<iostream>
#include<cstdio>
using namespace std;
int n,typ[100000+5];
double a[100000+5],k,c,w,p;
int main()
{
    freopen("explo.in","r",stdin);
    freopen("explo.out","w",stdout);
    scanf("%d %lf %lf %lf",&n,&k,&c,&w);
    for(int i=1;i<=n;i++)scanf("%d %lf",&typ[i],&a[i]);
    for(int i=n;i>=1;i--){
        if(typ[i]==1)
            p=max(p,p*(1-k/100)+a[i]);
        else
            p=max(p,p*(1+c/100)-a[i]);
    }
    printf("%.2lf",p*w);
    return 0;
}

T2:
题意:给定一个数列,求该数列在某一区间的所有子序列的和摸以一个数的最小值。
分析:
80分做法:
由于被模数比较小,就可以考虑抽屉原理,即只要区间长度大于p就直接输出0,否则就暴力加一个小剪枝(这个剪枝可以再过一组随机数据,就是得到答案0了直接退出),一共80分;
100分做法:
还是区间大于p就直接输出0,然后我们会发现剩下的30分前缀和暴力拿不完,接着分析,发现我们如果对于这个区间做一个模意义上的前缀和(即先前缀和再摸,不一定单增),就可以利用set的lower bound 和upper bound(因为数据必须放进去就有序,然后又要能取任意数,不写平衡树就只能用set或者map了),查看比当前正在枚举的数小的最大的数(且该数一定在正被枚举的数左边,因为边枚举边插入),这样就可以把第二个p变成logp;
为什么要选择比正在枚举的数小的最大数呢,首先,我们想象一个数轴,由前缀和的定义可知,必须是正枚举数减去其左边的数,否则结果就不对了(这是很显然的,你把一个数取相反数以后加上p,和这个数本身肯定不一样),那么为什么要比它大呢,在这个数轴AB上值域是0到p-1,我们想象一个点M为当前点,那么另一个点N若比M小,则答案是MN,反之,则答案为AM+BN(因为M-N后要+P),而AM>=MN,所以用小值一定更优,若不幸地找不到小值,则用当前最大值,目的是为了减小BN,毕竟,也许虽然正在枚举的点找不到比它更小的,但却足以更新最小值(这是很可能发生的));

80分程序:

#include<iostream>
#include<cstdio>
using namespace std;
long long a[500005],f[500005];
int lef,rig,p,n,m;
int main()
{
    freopen("seq.in","r",stdin);
    freopen("seq.out","w",stdout);
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++){
    scanf("%d",&a[i]);
    f[i]=f[i-1]+a[i];
    }
    for(int i=1;i<=m;i++){
        scanf("%d %d %d",&lef,&rig,&p);
        if(rig-lef+1>p)printf("0\n");
        else {//分斥要考虑全面啊。 
            long long mini=2e9;
            for(int j=lef;j<=rig;j++){
                if(!mini)break;
                for(int k=j;k<=rig;k++){
                    mini=min(mini,(f[k]-f[j-1])%(long long)p);
                if(!mini)break;
                }
            }
            printf("%I64d\n",mini);
        }
    }
    return 0;
}

100分程序:(hzw手写的平衡树,set由于有些情况未考虑成功被卡常熟,我这个程序在学校电脑上仍是80分,要跑1.2-1.6s的样子,但我还没有那个能力学习平衡树,所以我就只能写到这个程度了,思路是这样的。

#include<iostream>
#include<cstdio>
#include<set>
#include<algorithm>
using namespace std;
long long a[500005],f[500005],p;
set<int>ff;
int lef,rig,n,m;
int main()
{
    freopen("seq.in","r",stdin);
    freopen("seq.out","w",stdout);
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++){
    scanf("%I64d",&a[i]);
    f[i]=f[i-1]+a[i];
    }
    for(int i=1;i<=m;i++){
        scanf("%d %d %I64d",&lef,&rig,&p);
        if(rig-lef+1>p)printf("0\n");
        else {//分斥要考虑全面啊。 
            long long mini=2e9;
            long long mini2=f[lef-1]%p,maxn=f[lef-1]%p;
            ff.clear();
            ff.insert(f[lef-1]%p);
            for(int j=lef;j<=rig;j++){
                if(f[j]%p<mini2)mini=min(mini,f[j]%p-maxn+p)%p;
                else
                    mini=min(mini,f[j]%p-*(--ff.upper_bound(f[j]%p)));
                ff.insert(f[j]%p);
                mini2=min(mini2,f[j]%p);
                maxn=max(maxn,f[j]%p);
            }
            printf("%I64d\n",mini);
            }
    }
    return 0;
}

注意边界处理,ub是大于的第一个,lb是大于等于的第一个;

T3:
题意:给定一可能有复权的有向图,你可以给所有路径加上一个数,然后走1到n的最短路,要求加上这个t之后,1到n的路要是>=0条件下的最小值。
分析:首先,1到n的最短路随t增加不下降,所以这里的t可以二分。
我一直不擅长写二分这次居然一遍过了感觉准度有提升开心。
然后最短路就SPFA好写也不慢,判负环用cnt记松弛次数,要注意的是若负环不在1到N的所有路上是不会影响的,若在则会导致没有最小路,这里比较难处理,我是把负环上的所有路(即所有松弛次数超过N的路,可以简单证明这个方法可以修改负环上的所有路)的f设为-INF,这样1到N的路就一定是负数,也就一定可以排除这种情况了,我的程序60行,std写了100+,感觉黄学长真勤劳….

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#define clr(x) memset(x,0,sizeof(x))
using namespace std;
int f[105],fr[105],tov[10000+5],des[10000+5],wor[10000+5],cnt[105];
int t,n,m,sta,fin,mini,ans,mid;
int main()
{
    freopen("earth.in","r",stdin);
    freopen("earth.out","w",stdout);
    scanf("%d",&t);
    for(int i=1;i<=t;i++){
        clr(fr);mini=2e9;
        scanf("%d %d",&n,&m);
        for(int j=1;j<=m;j++){
        scanf("%d %d %d",&sta,&fin,&wor[j]);
        tov[j]=fr[sta];fr[sta]=j;des[j]=fin;
        }
        int lef=-1e5,rig=1e5;
        while(lef<=rig){
        ans=2e9;clr(cnt);
        mid=(lef+rig)/2;
        memset(f,127,sizeof(f));
        queue<int>q;
        q.push(1);f[1]=0;
        while(!q.empty()){
            int u=q.front();q.pop();
            if(cnt[u]>n){
            ans=-1;
            break;
            }
            for(int j=fr[u];j;j=tov[j])
                if(f[des[j]]>f[u]+wor[j]+mid){
                f[des[j]]=f[u]+wor[j]+mid;
                cnt[des[j]]++;
                q.push(des[j]);
                }
            }
            if(ans>=0)ans=f[n];
            if(ans>0){
                rig=mid-1;
                mini=min(mini,ans);
            }
            else if(ans<0)lef=mid+1;
            else {
                mini=0;break;
            }
        }
           if(mini!=2e9)printf("%d\n",mini);
            else printf("-1\n");
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值