NOIP2016 DAY2

T1 组合数问题:

luoguP2822组合数问题
一句话题意:

有t组数据,如果给定n,m和k,求各组数据对于所有的0 <= i <= n,0 <= j <= min(i,m)有多少对 (i,j)满足 Cjik

由于k已经给出,0< m,n< 2000 因此可以直接跑杨辉三角,边跑边mod k 然后维护( 0 , 0 )到 ( i , j ) 中c[ i ][ j ]==0的前缀和即可
时间复杂度 O(n×m) 每次询问 O(1) 总复杂度 O(T×m×n)
代码如下:

c[0][0]=1;
    for(int i=1;i<=2000;i++)
    {
        c[i][0]=1;
        for(int j=1;j<=i;j++)
        {
            c[i][j]=(c[i-1][j-1]+c[i-1][j])%k;
        }
    }
    for(int i=1;i<=2000;i++)
    {
        for(int j=1;j<=2000;j++)
        {
            sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
            if(j<=i&&c[i][j]==0) sum[i][j]++;
        }
    }
    while(t--)
    {
        int n=read(),m=read();
        cout<<sum[n][m]<<"\n";
    }

T2 蚯蚓:

luoguP2827蚯蚓

题目贼长就不描述了

分析之后可以发现
每次切完取出的肯定要比前一次切的长度短

简单证明一下:

假设有a>b>a/2 每次切成两半 每秒加q
那么第一次应该取出的是a
切出来的就是a/2 a/2 b+q
下一次切b
切出来是 a/2+q 、a/2+q 、 (b+q)/2 、 (b+q)/2
显然a/2+q依然大于(b+q)/2

那么直接开三个队列 第一个排下序存初始的 第二第三个存被切成的两半
每次找队首的三个最大的
拿出来切掉
注意:

除了被切的每条每秒都增加q就相当于被切时-q最后再加上q*t
每次拿出来时都要加上q*(t-1)恢复原长再切再减q *t 放回去
取的时候要特别特别注意!!因为不是每个队里都有元素!而且有可能是负数!

for(int i=1;i<=n;i++)
    {
        a[i]=read();
    }
    sort(a+1,a+n+1);
    for(int i=n;i>=1;i--)
    {
        q1.push(a[i]);
    }
    for(int i=1;i<=m;i++)
    {
        bool b1=0,b2=0,b3=0;
        ll get=-19260817;
        if(!q1.empty()){
            get=q1.front();b1=1;
        }
        if(!q2.empty()&&(q2.front()>get||get==-19260817)){
            get=q2.front();b2=1;b1=0;
        }
        if(!q3.empty()&&(q3.front()>get||get==-19260817)){
            get=q3.front();b3=1;b2=0;b1=0;
        }
        if(b1)q1.pop();
        if(b2)q2.pop();
        if(b3)q3.pop();
        get+=q*(i-1);
        if(i%t==0)
        cout<<get<<" ";
        ll get1=get*u/v;
        ll get2=get-get1;
        get1-=q*i;
        get2-=q*i;
        q2.push(get1);
        q3.push(get2);
    }cout<<"\n";
    for(int i=1;i<=n+m;i++)
    {
        bool b1=0,b2=0,b3=0;
        ll get=-19260817;
        if(!q1.empty()){
            get=q1.front();b1=1;
        }
        if(!q2.empty()&&(q2.front()>get||get==-19260817)){
            get=q2.front();b2=1;b1=0;
        }
        if(!q3.empty()&&(q3.front()>get||get==-19260817)){
            get=q3.front();b3=1;b2=0;b1=0;
        }
        if(b1)q1.pop();
        if(b2)q2.pop();
        if(b3)q3.pop();
        if(i%t==0)cout<<get+q*m<<" ";
    }cout<<"\n";

T3 愤怒的小鸟

luoguP2831愤怒的小鸟

状压dp

非常惭愧地说我这是去年noip后
第一次接触状压dp

大概就是用二进制的每一位表示状态
比如10001表示选了第一个和第五个其他都没选
就不用再开多维数组记录状态了

对于这个题
先注意精度问题
由于开始没有精度要求 那么直接eps=1e-7

double equa(double x){
    return (x>-eps)&&(x<eps);
}

预处理两只猪i j 路线上能同时打掉多少猪 记为bit[ i ] [ j ]
由两只猪坐标待定系数解二元一次方程得a b
然后把所有猪坐标带进去看符不符合符合就加进去

for(int i=1;i<=n;i++)
        {
            for(int j=i+1;j<=n;j++)
            {
                bit[i][j]=0;
                if(x[i]!=x[j]){//如果同列显然不能同时打掉
                    double a=y[i]*x[j]-y[j]*x[i];
                    double b=y[j]*x[i]*x[i]-y[i]*x[j]*x[j];
                    double mu=x[i]*x[j]*(x[i]-x[j]);//左边除以分母改为右边乘分母
                    if(a*mu<0)
                    { 
                        for(int k=1;k<=n;k++)
                        {   
                            if(equa(a*x[k]*x[k]+b*x[k]-mu*y[k]))
                            {
                                 bit[i][j]|=1<<(k-1);
                             }
                        }
                    }
                }
            }
        }

之后就可以转移了
从0到(1 << n)-1转移
dp [ S ] 可以转移到dp[ S | bit [ i ] [ j ] ]
注意处理单个情况 不然可能推不到终点

memset(dp,0x3f,sizeof(dp));  
        dp[0]=0;
        for(int k=0;k<(1<<n);k++)
            for(int i=1;i<=n;i++)
            {
                dp[k|1<<(i-1)] = min(dp[k|1<<(i-1)],dp[k]+1);
                //处理单个情况
                for(int j=i+1;j<=n;j++)
                {
                    if(!equa(x[i]-x[j]))
                    {
                        dp[k|bit[i][j]]=min(dp[k|bit[i][j]],dp[k]+1);
                    }
                }
            }
        cout<<dp[(1<<n)-1]<<"\n";

可是事实证明这样会T三个点

于是用了无敌题解法

对于枚举的每种情况 先找到第一个0的位置 再从这个0的位置开始往后枚举 j
因为前面的1是显然不用再打一遍的

貌似优化不大 不过乘上上一层循环(1<

for(int k=0;k<(1<<n);k++){
                int i=1;
                while(k>>(i-1)&1)i++;
                dp[k|1<<(i-1)]=min(dp[k|1<<(i-1)],dp[k]+1);//同样要处理单个情况
                for(int j=i+1;j<=n;j++) dp[k|bit[i][j]]=min(dp[k|bit[i][j]],dp[k]+1);
            }

165滚粗了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值