NOIP2016TGDay2

本文解析了三项编程挑战:涉及组合数的问题、蚯蚓增长模型及小鸟射击策略游戏。通过数学方法和高效算法解决复杂问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

总分 255
跟LPL一起明年再战

T1 组合数问题

Description

组合数 C(n,m) 表示的是从 n 个物品中选出 m 个物品的方案数。 举个例子,从 (1, 2, 3) 三个物品中选择两个物品可以有 (1, 2), (1, 3), (2, 3) 这三种选择方法.。小葱想知道如果给定 n, m 和 k ,对于所有的 0 ≤ i ≤ n, 0 ≤ j ≤min(i,m)有多少对(i, j) 满足 C (i,j) 是 k 的倍数。

解题思路

久违的数学水题,虽然考试时由于电脑网速爆炸,导致心态爆炸,结果想出了一个伪正解,当时看到k好小,最大只有21,想都没想就用质因数分解预处理了,然后二维前缀和优化,也能A,不过比较复杂。
我的正解

struct PAC{
    int num[5],cnt[5],tol,tmp[5];
    int S[5][M],sum[M][M];
    void Init(){
        int s=k;
        FOR(i,2,k){
            if(s%i==0){
                num[++tol]=i;
                while(s%i==0){
                    cnt[tol]++;
                    s/=i;
                }
            }
        }
        FOR(i,2,2000){
            FOR(j,1,tol){
                int p=i;
                while(p%num[j]==0){
                    p/=num[j];
                    S[j][i]++;
                }
            }
        }
        FOR(i,1,2000){
            FOR(j,1,tol)tmp[j]=0;
            int a=i,b=1;
            FOR(j,1,i){
                int flag=1;
                FOR(h,1,tol){
                    tmp[h]+=S[h][a]-S[h][b];
                    if(tmp[h]<cnt[h])flag=0;
                }
                a--;b++;
                if(flag)sum[i][j]=1;
            }
            FOR(j,1,2000)sum[i][j]=sum[i][j-1]+sum[i][j];
            FOR(j,1,2000){
                sum[i][j]+=sum[i-1][j];
            }
        }
    }
    void solve(){
        Init();
        int n,m;
        while(t--){
            scanf("%d%d",&n,&m);
            printf("%d\n",sum[n][min(n,m)]);
        }
    }
}pac;

其实直接模k就好了,因为对于一个f(x)=k*a,f(x)mod(k)=0。
手打标程

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<cstring>
using namespace std;
#define FOR(i,a,b) for(int i=(a),i_##END_=(b);i<=i_##END_;++i)
#define REP(i,a,b) for(int i=(a),i_##BEGIN_=(b);i>=i_##BEGIN_;--i)
#define M 2005
int t,k;
struct PAC{
    int C[M][M],sum[M][M];
    void Init(){
        FOR(i,1,2000){
            C[i][0]=C[i][i]=1%k;
            FOR(j,0,i){
                if(j!=0&&j!=i)C[i][j]=(C[i-1][j]+C[i-1][j-1])%k;
                if(!C[i][j])sum[i][j]=1;
            }
            FOR(j,1,2000)sum[i][j]=sum[i][j-1]+sum[i][j];
            FOR(j,1,2000){
                sum[i][j]+=sum[i-1][j];
            }
        }
    }
    void solve(){
        Init();
        int n,m;
        while(t--){
            scanf("%d%d",&n,&m);
            printf("%d\n",sum[n][min(n,m)]);
        }
    }
}pac;
int main(){
    cin>>t>>k;
    pac.solve();
    return 0;
}

T2 蚯蚓

Description

本题中,我们将用符号 LcJ 表示对 c 向下取整,例如: L3.0J = L3.1J = L3.9J= 3 。蛐蛐国最近虹蚓成灾了! 隔壁跳蚤国的跳蚤也拿虹蚓们没办法,蛐蛐国王只好去请神刀子来帮他们消灭虹蚓。蛐蛐国里现在共有 n 只虹蚓( n 为正整数)。 每只虹蚓拥有长度,我们设第 i 只虹 蚓的长度为 ai ( i = 1, 2, … , n ),并保证所有的长度都是非负整数(即:可能存在长度为 0 的虹蚓)。
每一秒,神刀子会在所有的虹蚓中,准确地找到最长的那一只(如有多个则任边 一个)将其切成两半。 神刀子切开虹蚓的位置由常数 p (是满足 0 < p < 1 的有理数) 决定,设这只虹蚓长度为 x ,神刀子会将其切成两只长度分别为 LpxJ 和 x − LpxJ 的虹蚓。特殊地,如果这两个数的其中一个等于 0 ,则这个长度为 0 的虹蚓也会被保留。此外,除了刚刚产生的两只新虹蚓,其余虹蚓的长度都会增加 q (是一个非负整常数)。蛐蛐国王知道这样不是长久之计,因为虹蚓不仅会越来越多,还会越来越长。蛐蛐国王决定求助于一位有着洪荒之力的神秘人物,但是救兵还需要m 秒才能到来… …( m 为非负整数)
蛐蛐国王希望知道这 m 秒内的战况。 具体来说,他希望知道:
• m 秒内,每一秒被切断的虹蚓被切断前的长度(有 m 个数);
• m 秒后,所有虹蚓的长度(有 n + m 个数)。
蛐蛐国王当然知道怎么做啦! 但是他想考考你… …
这个pdf复制下来十分鬼畜,我知道是蚯蚓啊,蚯蚓。

解题思路

刚开始以为只能拿个35模拟分,考试最后20分钟想出70分做法。模拟就不了,反复sort就行了。
q=0时,可以发现所有蚯蚓最后的长度小于等于之前的长度,所以先砍的肯定长一点,并且我们可以发现每次砍下来的两段蚯蚓都是具有单调性的,所以我们把每次砍下来的蚯蚓装进数组,然后三个数组(包含原数组)内的元素都具有单调性,然后用归并处理下,得到最后的答案。
最后20分钟敲得,最后5分钟D的bug,可能有一点问题,望大佬指出。

q=0的代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<cstring>
using namespace std;
#define FOR(i,a,b) for(int i=(a),i_##END_=(b);i<=i_##END_;++i)
#define REP(i,a,b) for(int i=(a),i_##BEGIN_=(b);i>=i_##BEGIN_;--i)
#define M 7000005
int n,m,q,u,v,t;
int A[100005];
int cmp(int a,int b){return a>b;}
struct P70{
    int ans[M],cnt,tol,gg;
    int B[M],C[M],Bag[M+100005],Bag2[M+100005];
    void solve(){
        sort(A+1,A+n+1,cmp);
        int a=1,res1=0,res2=0;
        int ed1=0,ed2=0;
        FOR(i,1,m){
            int mx=max(A[a],max(B[ed1],C[ed2]));
            if(i%t==0)ans[++cnt]=mx;
            if(mx==B[ed1]){
                long long tmp=1ll*u*B[ed1];
                tmp/=v;long long s=B[ed1]-tmp;
                ed1++;
                B[res1++]=tmp;C[res2++]=s;
            }
            else if(mx==C[ed2]){
                long long tmp=1ll*u*C[ed2];
                tmp/=v;long long s=C[ed2]-tmp;
                ed2++;
                B[res1++]=tmp;C[res2++]=s;              
            }
            else if(mx==A[a]){
                long long tmp=1ll*u*A[a];
                tmp/=v;long long s=A[a]-tmp;
                a++;
                B[res1++]=tmp,C[res2++]=s;
            }
        }
        int i=a,j=ed1;
        while(i<=n&&j<=res1){
            if(A[i]>B[j])Bag[++tol]=A[i++];
            else Bag[++tol]=B[j++];
        }
        while(i<=n)Bag[++tol]=A[i++];
        while(j<=res1)Bag[++tol]=B[j++];
        i=1,j=ed2;
        while(i<=tol&&j<=res2){
            if(Bag[i]>C[j])Bag2[++gg]=Bag[i++];
            else Bag2[++gg]=C[j++];
        }
        while(i<=tol)Bag2[++gg]=Bag[i++];
        while(j<=res2)Bag2[++gg]=C[j++];
        FOR(i,1,cnt)printf("%d ",ans[i]);
        puts("");
        FOR(i,1,n+m){
            if(i%t==0)printf("%d ",Bag2[i]);
        }
        puts("");
    }
}p70;
int main(){
    cin>>n>>m>>q>>u>>v>>t;
    FOR(i,1,n)scanf("%d",&A[i]);
    p70.solve();
    return 0;
}

正解

T2正解其实跟q=0差不多,因为长度长的蚯蚓被砍下来的两段分别比短的对应的两段长,其实可以用模拟理解。
比如常数p=0.5,q=2。有一只长度4的蚯蚓和一只长度三的蚯蚓
原序列排序后 4 3
第一次切断后 5 2 2//被切的长度不会增加
第二次切断后 4 4 3 2
可以发现,就算长度会增加,单调性还是不变的,因为每只蚯蚓被切断的那一轮长度都不会增加,所以这一点是平等的,所以单调性依然不变。
正解代码我抄的,不过我真的看懂了

struct PAC {
    int Q1[M],Q2[M],Q3[M],Q4[M+100005];
    int l1,l2,l3,r1,r2,r3,r4;
    int Calc(int x) {
        int s1=x*q+Q1[l1],s2=(x-l2-1)*q+Q2[l2],s3=(x-l3-1)*q+Q3[l3];
        if(l1<r1&&s1>s2&&s1>s3) {
            l1++;
            return s1;
        } else if(l2<r2&&s2>s3) {
            l2++;
            return s2;
        } else if(l3<r3) {
            l3++;
            return s3;
        }
        return -1;
    }
    void solve() {
        sort(A+1,A+n+1,cmp);
        FOR(i,1,n)Q1[i-1]=A[i];
        r1=n;
        FOR(i,1,m) {
            int a=Calc(i-1);
            if(i%t==0)printf("%d ",a);
            long long tmp=1ll*a*u/v;
            long long s=a-tmp;
            Q2[r2++]=tmp;
            Q3[r3++]=s;
        }
        puts("");
        r4=0;
        while(Q4[r4]=Calc(m),Q4[r4]!=-1)r4++;
        for(int i=t-1;i<n+m;i+=t)printf("%d ",Q4[i]);
    }
} pac;

T3 愤怒的小鸟

Description

Kiana最近沉迷于一款神奇的游戏无法自拔。
简单来说,这款游戏是在一个平面上进行的。
有一架弹弓位于 (0, 0) 处,每次Kiana可以用它向第一象限发射一只红色的小鸟, 小鸟们的飞行轨迹均为形如 y = ax^2 + bx 的曲线,其中 a, b 是Kiana指定的参数,且必须满足 a < 0 。当小鸟落回地面(即 x 轴)时,它就会瞬间消失。
在游戏的某个关卡里,平面的第一象限中有 n 只绿色的小猪,其中第 i 只小猪所在的坐标为 (xi, yi) 。如果某只小鸟的飞行轨迹经过了 (xi, yi) ,那么第 i 只小猪就会被消灭掉,同时小鸟将会沿着原先的轨迹继续飞行;如果一只小鸟的飞行轨迹没有经过 (xi, yi) ,那么这只小鸟飞行的全过程就不会对 第 i 只小猪产生任何影响。例如,若两只小猪分别位于 (1, 3) 和 (3, 3) ,Kiana可以选择发射一只飞行轨迹为y = −x^2 + 4x 的小鸟,这样两只小猪就会被这只小鸟一起消灭。 而这个游戏的目的,就是通过发射小鸟消灭所有的小猪。这款神奇游戏的每个关卡对Kiana来说都很难,所以Kiana还输入了一些神秘的指令,使得自己能更轻松地完成这个游戏。 这些指令将在【输入格式】中详述。假设这款游戏一共有 T 个关卡,现在Kiana想知道,对于每一个关卡,至少需要发射多少只小鸟才能消灭所有的小猪。 由于她不会算,所以希望由你告诉她。

解题思路

刚开始只想拿暴力分,结果我瞄了一眼数据,n<=18,我当时的表情大概是这样的。这里写图片描述
这#@#$*(哔~~~)不是裸的状压吗,暴力,不存在的。
我们可以发现,这个发射的抛物线要么只打一只小鸟,要么瞄着两只小鸟打,一只小鸟很简单,直接把状态加进去就好了,两只小鸟就要算出这两只小鸟呈的抛物线,注意精度误差就好了。
对于每个状压位,1表示这只猪还活着,0表示被打死了。对于每个状态,把每个抛物线带进去算一下就好了。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<cstring>
using namespace std;
#define FOR(i,a,b) for(int i=(a),i_##END_=(b);i<=i_##END_;++i)
#define REP(i,a,b) for(int i=(a),i_##BEGIN_=(b);i>=i_##BEGIN_;--i)
#define M 20
#define e 1e-6
typedef double db;
int T,n,m;
struct node{
    db x,y;
}P[M];
int tol,Sit[M*M];
int dp[1<<M];
void Init(){
    FOR(i,1,n)FOR(j,i+1,n){
        if(P[i].x==P[j].x)continue;
        db tmp1=P[i].x*P[i].x,res1=P[i].x;
        db tmp2=P[j].x*P[j].x,res2=P[j].x;
        db aa=res2/res1;
        db a=(P[i].y*aa-P[j].y)/(tmp1*aa-tmp2);
        db b=(P[i].y-a*tmp1)/res1;
        tol++;
        if(a>=0)continue;
        Sit[tol]|=1<<(i-1);
        Sit[tol]|=1<<(j-1);
        FOR(k,1,n){
            if(k==i||k==j)continue;
            if(abs(P[k].x*P[k].x*a+P[k].x*b-P[k].y)<=e)Sit[tol]|=1<<(k-1);
        }
    }
}
int cmp(node a,node b){
    return a.x<b.y;
}
int main(){
    cin>>T;
    while(T--){
        tol=0;
        scanf("%d%d",&n,&m);
        FOR(i,1,n)scanf("%lf%lf",&P[i].x,&P[i].y);
        memset(Sit,0,sizeof(Sit));
        FOR(i,1,n)Sit[++tol]=1<<(i-1);
        Init();
        FOR(i,0,(1<<n)-1)dp[i]=1e9;
        dp[(1<<n)-1]=0;
        REP(i,(1<<n)-1,0){
            FOR(j,1,tol){
                dp[i]=min(dp[i],dp[i|Sit[j]]+1);
            }
        }
        printf("%d\n",dp[0]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值