NOIP2016提高组Day2

来源:互联网 发布:手机淘宝详情制作软件 编辑:程序博客网 时间:2024/06/11 11:24

一、组合数问题

数据:
这里写图片描述

数据也没有用……因为所有询问中k的值是一样的,直接预处理组合数。处理时一直模k,这样求出来的就是这个数模k的余数。等于0的就是k的倍数。然后前缀和一下就行了。

二、蚯蚓

数据:
这里写图片描述

当m==0时就是排从大到小排序输出

前60分按照题目模拟,在加上优先级队列或者手打堆就能过了。基本上就是每次弹出最大值,然后拆分,再放回去。

但是显然log n 太慢了,能不能快一点?注意到拆分的时候,每次拆的都是最大的,那么拆掉的数放到原序列里不一定有序,但是如果重新开两个数组存放,一个放拆掉后大的,一个放小的,那么肯定这两个数组肯定是有序的了。为什么?先放的数是由较大的数拆分而得,显然大。那么就可以在常数时间内把最大的找到,可以从三个地方:原序列、拆分后较大的、拆分后较小的,然后拆完后继续放进去就行了。输出使只要不断询问下一个最大值,知道没有时返回一个-1,判断一下就行了。
还有一点,因为拆的时候是不会增长的,所以放进去的时候要进行一番处理

#include<bits/stdc++.h>#define M 100005#define N 7000005#define ll long longusing namespace std;int n,m,zt,t,U,V;int n1,n2,n3,m1,m2,m3;ll A1[M+N],A2[M+N],A3[M+N];//三个数组:原序列,切完后较大的、较小的bool cmp(int x,int y){return x>y;}//原序列先排序ll Find(int x){    int k1=A1[m1]+zt*x,k2=A2[m2]+zt*x,k3=A3[m3]+zt*x;    //分别是此时三个序列里最大的数    if(m1>n1)k1=-1;if(m2>n2)k2=-1;if(m3>n3)k3=-1;//如果没了赋值为-1    if(k1>=k2&&k1>=k3){m1++;return k1;}    if(k2>=k1&&k2>=k3){m2++;return k2;}    if(k3>=k1&&k3>=k2){m3++;return k3;}    return -1;}int main(){    scanf("%d%d%d%d%d%d",&n,&m,&zt,&U,&V,&t);    for(int i=1;i<=n;i++)scanf("%lld",&A1[i]);    sort(A1+1,A1+1+n,cmp);    n1=n;n2=n3=0;m1=m2=m3=1;//ni为边界,mi为此时用到第几个数    for(int i=1;i<=m;i++){        ll x=Find(i-1);//找最大值        if(i%t==0)printf("%lld ",x);        ll a1=1.0*x*U/V,a2=x-a1;//拆分        if(a1>a2){A2[++n2]=a1-zt*i;A3[++n3]=a2-zt*i;}//此时减去现在的增长量,上面再加总增长量,这样就抵消了        else{A2[++n2]=a2-zt*i;A3[++n3]=a1-zt*i;}//分开来放    }    puts("");    ll x=Find(m);int cnt=1;    while(x!=-1){//不断弹出最大值        if(cnt%t==0)printf("%lld ",x);        x=Find(m);        cnt++;    }    puts("");    return 0;}

三、愤怒的小鸟

数据:
这里写图片描述

这道题分成两块:一块是判断两个点是否在一个抛物线上;另一个是状压dp。

判断抛物线我的方法很麻烦,先用手写的方法一点点算出中间变量,最后把算出方程式y=a * x^2+b * x 中的a和b(x、y是已知的),由因为还有一个点是(0,0),所以最后有判定了一遍是否符合。最后把a、b记录下来,这样抛物线就存下来了,为了后面预处理判定用。

因为不同抛物线射到的个数不同,所以要预处理那些可以社到的抛物线。已经有一个点(0,0),再枚举两个点就能确定一个抛物线,再一遍循环把剩下的点在这条抛物线上的点取出来,这样预处理就好了。

最后是状压dp了。这个想到应该不难,因为n的范围小于等于18,而且又是多个里选几个,那么就是状压dp了。转移也很简单,看代码

#include<bits/stdc++.h>using namespace std;struct node{double x,y;}A[20];struct node1{double a,b;};int B[405],dp[1<<19];node1 f(int x,int y){//根据手推的方法计算y=a*x^2+b*x中的a和b,并且返回a、b    double y1=A[x].y,y2=A[y].y;    double k1=A[x].x*A[x].x,k2=A[y].x*A[y].x;    double b1=A[x].x,b2=A[y].x;    double s1=k1/k2,s2=b1/b2;    double b=(y1-y2*s1)/(b1-b2*s1);    double a=(y1-y2*s2)/(k1-k2*s2);    return (node1){a,b};}bool Judge(node1 a,int x,int y){//判定在两点是不是在此抛物线上    if(a.a>=0)return 0;    if(abs(a.a*A[x].x*A[x].x+a.b*A[x].x-A[x].y)<1e-6&&abs(a.a*A[y].x*A[y].x+a.b*A[y].x-A[y].y)<1e-6)return 1;    return 0;}int main(){    memset(dp,63,sizeof dp);    int cas;    scanf("%d",&cas);    while(cas--){        int n,m;        scanf("%d%d",&n,&m);        for(int i=1;i<=n;i++)scanf("%lf%lf",&A[i].x,&A[i].y);        int n1=0;        for(int i=1;i<=n;i++){//枚举两个点            for(int j=i+1;j<=n;j++){                node1 a1=f(i,j);                if(!Judge(a1,i,j))continue;//如果这两个点在抛物线上才往下                int h=(1<<(i-1))+(1<<(j-1));                for(int k=j+1;k<=n;k++)if(Judge(a1,i,k))h+=(1<<(k-1));//把后面的在抛物线上的移进去                B[++n1]=h;            }            B[++n1]=1<<(i-1);        }        dp[0]=0;        for(int i=0;i<(1<<n);i++){            for(int j=1;j<=n1;j++)                if((i&B[j])==B[j])continue;//现在方案包含B[i]方案                else dp[i|B[j]]=min(dp[i|B[j]],dp[i]+1);//能多打就转移            if(i!=(1<<n)-1)dp[i]=1e9;//多组数据为后面的清无限大        }        printf("%d\n",dp[(1<<n)-1]);        dp[(1<<n)-1]=1e9;    }    return 0;}

可悲的我考试时三道题,前两道对了,第三道判断的地方精度不够,只开了1e-4,改1e-6就对了。而且第一题还暴零了,因为数组越界了,当cnt[0][0] 时还访问了cnt[i-1][j] 等,这样就到-1了。但是只超了一位来着,网站上都对的……大佬说测评机数组越界时地址很神奇……以后越界要小心了。

原创粉丝点击