文章标题

4 篇文章 0 订阅
2 篇文章 0 订阅

NOIP2016 D2

T1: 组合数问题
题目描述
组合数 表示的是从 n 个物品中选出 m 个物品的方案数。举个例子,从 (1,2,3) 三个物品中选择两个物品可以有 (1,2),(1,3),(2,3) 这三种选择方法。根据组合数的定义,我们可以给出计算组合数 的一般公式:
这里写图片描述

其中 n!=1×2×…×n 。

小葱想知道如果给定 n,m 和 k,对于所有的 0≤i≤n,0≤j≤min(i,m) 有多少对 (i,j) 满足 是 k 的倍数。

输入格式
第一行有两个整数 t,k,其中 t 代表该测试点总共有多少组测试数据,k 的意义见【问题描述】。

接下来 t 行每行两个整数 n,m,其中 n,m 的意义见【问题描述】。

输出格式
输出 t 行,每行一个整数代表所有的 0≤i≤n,0≤j≤min(i,m) 中有多少对 (i,j) 满足 是 k 的倍数。

分析:N*N预处理组合数,直接mod K,对答案求前缀和即可。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
int f[2010][2010],ans[2010][2010],n,m,t,k;
int read()
{
    int x=0;
    char ch;
    int f=1;
    for(ch=getchar();ch!='-'&&(ch<'0'||ch>'9');ch=getchar());
    if(ch=='-')
    {
        f=-1;
        ch=getchar();
    }
    for(;ch>='0'&&ch<='9';ch=getchar())
      x=(x<<1)+(x<<3)+ch-'0';
    return x*f;
}
int main()
{
    t=read();
    k=read();
    for(int i=0;i<=2000;++i) f[i][0]=f[i][i]=1;
    for(int i=2;i<=2000;++i)
      for(int j=1;j<i;++j)
        f[i][j]=(f[i-1][j-1]+f[i-1][j])%k;
    for(int i=1;i<=2000;++i)
    {
        for(int j=1;j<=2000;++j)
        {
            if(j<=i) ans[i][j]=ans[i-1][j]+ans[i][j-1]-ans[i-1][j-1];
            if(j==i) ans[i][j]=ans[i][j-1];
            if(j>i) ans[i][j]=ans[i][i];
            if(f[i][j]==0&&j<=i) ans[i][j]++;
        }
    }
    for(;t;t--)
    {
        n=read();
        m=read();
        cout<<ans[n][min(n,m)]<<endl;
    }
    return 0;
}

T2: 蚯蚓
题目描述
本题中,我们将用符号 表示对 c 向下取整,例如:

蛐蛐国最近蚯蚓成灾了!隔壁跳蚤国的跳蚤也拿蚯蚓们没办法,蛐蛐国王只好去请神刀手来帮他们消灭蚯蚓。

蛐蛐国里现在共有 n 只蚯蚓(n为正整数)。每只蚯蚓拥有长度,我们设第 i 只蚯蚓的长度为 ai (i=1,2,… ,n),并保证所有的长度都是非负整数(即:可能存在长度为0的蚯蚓)。

每一秒,神刀手会在所有的蚯蚓中,准确地找到最长的那一只(如有多个则任选一个)将其切成两半。神刀手切开蚯蚓的位置由常数 p(是满足 0<p<1 的有理数)决定,设这只蚯蚓长度为 x ,神刀手会将其切成两只长度分别为 的蚯蚓。特殊地,如果这两个数的其中一个等于 0 ,则这个长度为 0 的蚯蚓也会被保留。此外,除了刚刚产生的两只新蚯蚓,其余蚯蚓的长度都会增加 q(是一个非负整常数)。

蛐蛐国王知道这样不是长久之计,因为蚯蚓不仅会越来越多,还会越来越长。蛐蛐国王决定求助于一位有着洪荒之力的神秘人物,但是救兵还需要 m 秒才能到来……(m为非负整数)

蛐蛐国王希望知道这 m 秒内的战况。具体来说,他希望知道:

m 秒内,每一秒被切断的蚯蚓被切断前的长度(有 m 个数);
m 秒后,所有蚯蚓的长度(有 n+m 个数)。
蛐蛐国王当然知道怎么做啦!但是他想考考你……
输入格式
第一行包含六个整数 n,m,q,u,v,t,其中:n,m,q 的意义见【问题描述】;u,v,t 均为正整数;你需要自己计算 p=u/v(保证p大于0小于1);t 是输出参数,其含义将会在【输出格式】中解释。

第二行包含 n 个非负整数,为 a1,a2,… ,an,即初始时 n 只蚯蚓的长度。

同一行中相邻的两个数之间,恰好用一个空格隔开。

保证 1≤n≤105,0≤m≤7×106,0<u<v≤109,0≤q≤200,1≤t≤71,0≤ai≤108。

输出格式
第一行输出 个整数,按时间顺序,依次输出第 t 秒,第 2t 秒,第 3t 秒,……被切断蚯蚓(在被切断前)的长度。

第二行输出 个整数,输出 m 秒后蚯蚓的长度;需要按从大到小的顺序,依次输出排名第 t,第 2t,第 3t,……的长度。

同一行中相邻的两个数之间,恰好用一个空格隔开。即使某一行没有任何数需要输出,你也应输出一个空行。

分析:
维护3个数组,第一个存原始蚯蚓从大到小的排序,第二个和第三个分别存x*p于1-x*p的蚯蚓,之后每次操作从三个数组中队首取出最大的(因为这样具有单调性,后面讲),切了之后就将切出的2只放入2,3数组队尾(保证了单调性)。
对于单调性的简单证明:
假设有a,b先后被作为最长的蚯蚓被切,那么b被切后的状态(这里用切出的1只就可以,另外一只其实一样):a切出的为a*p+q , b切出的为b*p+q*p,显然有a*p+q>b*p+q*p,这样2,3数组的单调性得到了保证。
代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<vector>
#include<map>
#include<queue>
#include<algorithm>
using namespace std;
int n,m,u,v,t,q;
int a[7100010],b[7100010],c[7100010],e[8000010];
double p;
int read()
{
    int x=0;
    char ch;
    int f=1;
    for(ch=getchar();ch!='-'&&(ch<'0'||ch>'9');ch=getchar());
    if(ch=='-')
    {
        f=-1;
        ch=getchar();
    }
    for(;ch>='0'&&ch<='9';ch=getchar())
      x=(x<<1)+(x<<3)+ch-'0';
    return x*f;
}
void write(int x)
{
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
bool comp(int x,int y)
{
    return x>y;
}
int main()
{
    n=read();
    m=read();
    q=read();
    u=read();
    v=read();
    t=read();
    p=(double)u/v;
    for(int i=1;i<=n;++i)
      a[i]=read();
    int la=1,lb=1,lc=1;
    sort(a+1,a+1+n,comp);
    int x,cou=0;
    for(int i=1;i<=m;++i)
    {
        if(la<=n)
        {
            x=max(a[la]+q*(i-1),max(b[lb]+q*(i-1-lb),c[lc]+q*(i-1-lc)));
            if(x==a[la]+q*(i-1)) la++;
            else
            if(x==b[lb]+q*(i-1-lb)) lb++;
            else
                if(x==c[lc]+q*(i-1-lc)) lc++; 
        }
        else
        {
            x=max(b[lb]+q*(i-1-lb),c[lc]+q*(i-1-lc));
            if(x==b[lb]+q*(i-1-lb)) lb++;
            else
                if(x==c[lc]+q*(i-1-lc)) lc++; 
        }
        b[++cou]=floor(double(x*p));
        c[cou]=x-floor(double(x*p));
        if(i%t==0)
        {
            write(x);
            putchar(' ');
        }
    }
    puts("");
    int cnt=0;
    for(int i=1;i<=n+m;++i)
    {
        if(la<=n&&lb<=m&&lc<=m)
        {
            x=max(a[la]+q*m,max(b[lb]+q*(m-lb),c[lc]+q*(m-lc)));
            if(x==a[la]+q*m) la++;
            else
              if(x==b[lb]+q*(m-lb)) lb++;
              else
                if(x==c[lc]+q*(m-lc)) lc++;
        }
        else
          if(lb<=m&&lc<=m)
          {
            x=max(b[lb]+q*(m-lb),c[lc]+q*(m-lc));
            if(x==b[lb]+q*(m-lb)) lb++;
              else
                if(x==c[lc]+q*(m-lc)) lc++;
          }
          else
            if(lb<=m&&la<=n)
            {
                x=max(a[la]+q*m,b[lb]+q*(m-lb));
                if(x==a[la]+q*m) la++;
                  else
                    if(x==b[lb]+q*(m-lb)) lb++;

            }
            else
              if(la<=n&&lc<=m)
              {
                x=max(a[la]+q*m,c[lc]+q*(m-lc));
                if(x==a[la]+q*m) la++;
                  else
                    if(x==c[lc]+q*(m-lc)) lc++;
              }
              else
                if(la<=n)
                {
                    x=a[la]+q*m;
                    la++;
                }
                else
                  if(lb<=m)
                  {
                    x=b[lb]+q*(m-lb);
                    lb++;
                  }
                  else
                    if(lc<=m)
                    {
                        x=c[lc]+q*(m-lc);
                        lc++;
                    }
        if(i%t==0) 
          write(x),putchar(' '); 
    }
    return 0;
}

T3: 愤怒的小鸟
题目描述
Kiana 最近沉迷于一款神奇的游戏无法自拔。简单来说,这款游戏是在一个平面上进行的。

有一架弹弓位于 (0,0) 处,每次 Kiana 可以用它向第一象限发射一只红色的小鸟,小鸟们的飞行轨迹均为形如 y=ax2+bx 的曲线,其中 a,b 是 Kiana 指定的参数,且必须满足 a<0。

当小鸟落回地面(即x轴)时,它就会瞬间消失。

在游戏的某个关卡里,平面的第一象限中有 n 只绿色的小猪,其中第 i 只小猪所在的坐标为 (xi,yi) 。

如果某只小鸟的飞行轨迹经过了(xi,yi),那么第 i 只小猪就会被消灭掉,同时小鸟将会沿着原先的轨迹继续飞行;

如果一只小鸟的飞行轨迹没有经过(xi,yi),那么这只小鸟飞行的全过程就不会对第 i 只小猪产生任何影响。

例如,若两只小猪分别位于 (1,3) 和 (3,3) ,Kiana 可以选择发射一只飞行轨迹为 y=-x2+4x 的小鸟,这样两只小猪就会被这只小鸟一起消灭。

而这个游戏的目的,就是通过发射小鸟消灭所有的小猪。

这款神奇游戏的每个关卡对 Kiana 来说都很难,所以 Kiana 还输入了一些神秘的指令,使得自己能更轻松地完成这个游戏。这些指令将在【输入格式】中详述。

假设这款游戏一共有 T 个关卡,现在 Kiana 想知道,对于每一个关卡,至少需要发射多少只小鸟才能消灭所有的小猪。由于她不会算,所以希望由你告诉她。

输入格式
下面依次输入这 T 个关卡的信息。每个关卡第一行包含两个非负整数 n,m ,分别表示该关卡中的小猪数量和 Kiana 输入的神秘指令类型。接下来的 n 行中,第 i 行包含两个正实数 xi,yi ,表示第 i 只小猪坐标为 (xi,yi)。数据保证同一个关卡中不存在两只坐标完全相同的小猪。

如果 m=0,表示 Kiana 输入了一个没有任何作用的指令。
如果 m=1 ,则这个关卡将会满足:至多用 只小鸟即可消灭所有小猪。
如果 m=2 ,则这个关卡将会满足:一定存在一种最优解,其中有一只小鸟消灭了至少 只小猪。
保证 1≤n≤18,0≤m≤2,0<xi,yi<10,输入中的实数均保留到小数点后两位。

输出格式
对每个关卡依次输出一行答案。
输出的每一行包含一个正整数,表示相应的关卡中,消灭所有小猪最少需要的小鸟数量。

分析:
正解状压DP,N*N预处理f[i][j],表示经过i,j两只鸟的抛物线可以经过的鸟(用2进制0,1来表示这位的鸟是否被消灭),之后用状压DP转移状态 , 复杂度为 1<

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<vector>
#include<map>
#include<queue>
#include<algorithm>
using namespace std;
const double eps=1e-10;
int t,n,m,f[22][22],dp[1<<19];
double x[22],y[22];
int read()
{
    int x=0;
    char ch;
    int f=1;
    for(ch=getchar();ch!='-'&&(ch<'0'||ch>'9');ch=getchar());
    if(ch=='-')
    {
        f=-1;
        ch=getchar();
    }
    for(;ch>='0'&&ch<='9';ch=getchar())
      x=(x<<1)+(x<<3)+ch-'0';
    return x*f;
}
void write(int x)
{
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
void pre()
{
    int i,j,k;
    for(int i=1;i<=n;++i)
      for(int j=i+1;j<=n;++j)
      {
        double a=(y[i]*x[j]-x[i]*y[j])/(x[j]*x[i]*(x[i]-x[j]));
        double b=(y[i]*x[j]*x[j]-y[j]*x[i]*x[i])/(x[i]*x[j]*(x[j]-x[i]));
        if(a>=0) continue;
        for(int k=1;k<=n;++k)
        {
            double temp=x[k]*x[k]*a+b*x[k];
            if(y[k]-eps<=temp&&temp<=y[k]+eps)
            {
                f[i][j]|=(1<<(k-1));
            }
        }
      }
}
int dynamic()
{
    int k;
    dp[0]=0;
    for(int i=0;i<=(1<<n)-2;++i)
    {
        k=0;
        while((1<<k)&i) k++; k++;
        dp[i|(1<<(k-1))]=min(dp[i|(1<<(k-1))],dp[i]+1);
        for(int j=k+1;j<=n;++j)
          dp[i|f[k][j]]=min(dp[i|f[k][j]],dp[i]+1);
    }
    return dp[(1<<n)-1];
}
int main()
{
    t=read();
    while(t--)
    {
        n=read();
        m=read();
        memset(f,0,sizeof(f));
        memset(dp,0x3f,sizeof(dp));
        for(int i=1;i<=n;++i) cin>>x[i]>>y[i];
        pre();
        write(dynamic());
        puts("");
    }
    return 0;
}

总结:D2的题总体感觉比D1要简单一点,T1送温暖;T2要观察到题中单调性的性质,之后就是代码的细节处理;T3是个穿了纸的状压DP(虽然蒟蒻的DP水的不行,考试的时候根本写不出来),而且这道题搜索还是一种很好的得分策略。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值