noi online round2(入门组)

Posted sunny99

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了noi online round2(入门组)相关的知识,希望对你有一定的参考价值。

又来了,第一题还是比较简单的,而且正好前面在弄最长不下降子序列的时候学到了二分的函数,lower_bound()和upper_bound的知识,刚好用上了,但是后面两个题目,第二题稍微写了下,但是越写越觉得不对。。第三题就一直放着,趁着写博客又来攻克一下吧~┭┮﹏┭┮脑壳不够用


 

题目一:末了(真的挺简单的,我这种菜鸡还是能做出来心里还是很开心,不是零分了嘤)简单思路就是贪心加二分,没什么好说的,看代码应该就可以看懂,也可以比距离

#include <bits/stdc++.h>
using namespace std;
const int N=200050,inf=0x3f3f3f;
int a[N];
double t[N],ask;
int n,l,v,q;
bool cmp(int x,int y) {return x>y;}
int main()
{
    cin>>n>>l>>v;
    for(int i=1;i<=n;i++) cin>>a[i];
    sort(a+1,a+1+n,cmp);
    t[0]=l*1.0/(v*1.0);
    for(int i=1;i<=n;i++)
        t[i]=t[i-1]+a[i]*1.0/(v*1.0);
    cin>>q;
    for(int i=1;i<=q;i++){
        cin>>ask;
        if(ask>=t[n]){
            cout<<"-1"<<endl;continue;
        }
        cout<<upper_bound(t,t+n,ask)-t<<endl;
    }
    return 0;
}

题目二:荆轲刺秦王

 先来学习一下差分:

从一个例子开始:将2~5区间内的每个数加上6

原数组                    5 2 0 1 3 1 4

修改后的数组          5 8 6 7 9 1 4

原差分数组              5 -3 -2 1 2 -2 3

修改后的差分数组   5 3 -2 1 2 -8 3

我们可以发现,只有头和尾的差分会发生变化,并且变化规律为  a[l]+=k,(-3+6=3)  a[r+1]-=k;(-2-6=-8)

可能对于差分的理解不够,还没有找到我特别能理解的差分的典型例子,找到了再来补一补吧

但是大概能看懂代码:

技术图片
#include<bits/stdc++.h>
using namespace std;
int n,m,c1,c2,d;//如题
int sx,sy,ex,ey;//起点终点坐标
string s;//读入的数据
int a[351][351];
int flag[351][351];
bool v[351][351][16][16];//剪枝二
void add(int i,int j,int x){
    for(int k=-x+1;k<=x-1;k++){//差分使复杂度降为n^3
        if(k+i<1||k+i>n)continue;//超过边界 
        int p=x-1-(k<0?-k:k);//可以自己找规律
        if(j-p<1)a[k+i][0]+=1;
        else a[k+i][j-p]+=1;
        if(j+p+1>m);
        else a[k+i][j+p+1]-=1;
    }
}
struct zj{
    int x,y,u1,u2,t;//坐标,隐身使用次数,瞬移使用次数,已经过了多长时间
};
int ans1=0x3fffffff,ans2=0x3fffffff,ans=0x3fffffff;
int X[8]={0,0,1,-1,1,1,-1,-1};
int Y[8]={1,-1,0,0,1,-1,1,-1};
void bfs(){
    queue<zj> q;
    q.push((zj){sx,sy,0,0,0});
    v[sx][sy][0][0]=1;//起点已经过
    while(!q.empty()){
        zj x=q.front();
        q.pop();
        if(x.t>ans)continue;//剪枝一
        if(x.x==ex&&x.y==ey){
            if(x.t<ans){
                ans=x.t;
                ans1=x.u1;
                ans2=x.u2;
            }
            else{
                if(ans1+ans2>x.u1+x.u2){//魔法使用次数少
                    ans=x.t;
                    ans1=x.u1;
                    ans2=x.u2;
                }
                else if(ans1+ans2==x.u1+x.u2&&ans1>x.u1){//魔法一样,隐身少
                    ans=x.t;
                    ans1=x.u1;
                    ans2=x.u2;                    
                }
            }
            continue;
        }
        for(int i=0;i<8;i++){
            int xx=x.x+X[i],yy=x.y+Y[i];
            if(xx<1||xx>n||yy<1||yy>m)continue;//越界
            if(flag[xx][yy]==1)continue;//有士兵
            if(a[xx][yy]<=0&&v[xx][yy][x.u1][x.u2]==0){//不在士兵的观察范围内
                v[xx][yy][x.u1][x.u2]=1;//标记
                q.push((zj){xx,yy,x.u1,x.u2,x.t+1});
            }
            else if(x.u1+1<=c1&&v[xx][yy][x.u1+1][x.u2]==0){//在士兵的观察范围内,使用隐身
                v[xx][yy][x.u1+1][x.u2]=1;//标记
                q.push((zj){xx,yy,x.u1+1,x.u2,x.t+1});
            }
        }
        if(x.u2+1>c2)continue;//无法使用瞬移
        for(int i=0;i<4;i++){
            int xx=x.x+X[i]*d,yy=x.y+Y[i]*d;
            if(xx<1||xx>n||yy<1||yy>m)continue;
            if(flag[xx][yy]==1)continue;
            if(a[xx][yy]<=0&&v[xx][yy][x.u1][x.u2+1]==0){
                v[xx][yy][x.u1][x.u2+1]=1;
                q.push((zj){xx,yy,x.u1,x.u2+1,x.t+1});
            }
            else if(x.u1<c1&&v[xx][yy][x.u1+1][x.u2+1]==0){
                v[xx][yy][x.u1+1][x.u2+1]=1;
                q.push((zj){xx,yy,x.u1+1,x.u2+1,x.t+1});
            }
        }
    }
}
int main(){
    scanf("%d%d%d%d%d",&n,&m,&c1,&c2,&d);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>s;
            if(s=="S")flag[i][j]=-2,sx=i,sy=j;//记录起点位置 
            else if(s=="T")flag[i][j]=-1,ex=i,ey=j;//记录终点位置 
            else if(s==".");
            else{
                flag[i][j]=1;
                int x=s[0]-0;
                for(int i=1;i<s.length();i++)x=x*10+s[i]-0;//存入卫兵可以观察的距离 
                add(i,j,x);//处理 
            }
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            a[i][j]+=a[i][j-1];//注意差分要加回去
        }
    }
    bfs();
    if(ans==0x3fffffff)printf("-1");//无解
    else printf("%d %d %d",ans,ans1,ans2);//输出
    return 0;
}
View Code

 

 题目三:建设城市(分为四段,1-i i+1-n n+1-j j+1-2n

dp[i][j]:长度为i,末尾为j的单调递增的序列个数

dp[i][j]=Σk=1jdp[i-1][k];

优化:也可以状态转移为:dp[i][j]表示长度为i,末尾<=j的单调递增序列,d[i][j]=dp[i][j-1]+dp[i-1][j]   60分

继续优化:排列组合的插板?~dp[i][j]=C(i+j-1,i)=(i+j-1)!/i!(j-1)!

线性求阶乘逆元优化    100分

好吧:又来学习一下做这个题目应该掌握的预备知识了

来了,第一步:如果没有第5个要求的话,那么,我们只来看一看左边上升部分有多少种情况呢?我们有n栋楼,所有楼的高度范围为(1-m),那么我们可以把这个模型抽象为有n个球,现在把它们放入m个盒子中(编号就为1-m,在在哪个盒子就高度是多少,可以相同),所以我们就是允许有些盒子为空的,那么我们就根据插板法:

总结:

1:如果是把n个求放入k个盒子中(每个盒子必须要有球),那么由插板法得 方案数为 C(n-1,k-1);

2:如果是把n个求放入k个盒子中(盒子可以为空),那么由插板法得 方案数为 C(n+k-1,k-1);我们很显然是这个情况~(get it)

 第二步:那么我们现在要把第5个要求加进来的话,现在就有两种情况了,x/y在两侧还是同侧,我们先枚举这两栋楼高度为i

1、在两侧的话:

x左边的x栋楼高度范围为(1-i);x右边到n左边(包括n)n-x栋楼的高度范围为(i,m);

n右边(不包括n)到y左边y-n-1栋楼的高度范围为(i,m);y右边的2n-y栋楼的高度范围为(1-i)

就把四种情况乘起来就是答案了

技术图片

2、如果在一侧的话:就把x,y之间的高楼看成一个高楼

就有c(n+m-1,m-1)*c(n+x-y+m-1,m-1);

又有问题来了:我们知道c(n+m-1,m-1)=(n+m-1)!/(m-1)!*n!

就有乘法的逆元:

(a+b)%p=(a%p+b%p)%p

(a-b)%p=(a%p-b%p)%p

(a*b)%p=(a%p*b%p)%p

但是(a/b)%p!=(a%p/b%p)%p;除法不满足我们的分配律,但我们也需要在这个过程中去模,否则中间值会出现太大的情况,所以我们要用到乘法逆元:

乘法逆元一般用于求a/b(mod)p的值,是解决模意义下分数值的必要手段。

逆元定义:若a*x=1(mod b),且a与b 互斥,那么我们就能定义x为a的逆元,记为a-1,所以我们也可以称x为a在mod意义下的倒数。所以对于a/b(mod p) ,我们就可以求出 b 在mod p 下的逆元,然后乘上 a ,再 mod p就是这个分数的值了。。。没太看懂,举例,5和14是互斥的,所以就存在5关于模14的乘法逆元为3,3*5-14=1;

可以看成一个公式就是ax-pb=1,把减号变成加号,所以有一个公式就变成:ax+by=1;,然后可以扩展欧几里得定理来扩展了。

应用(求取(a/b)%p等同于求取a*(b的逆元)%p)

证明:

技术图片

 那么问题又来了,怎么去求解乘法的逆元是多少呢?方法很多种:费马小定理(p为质数)、扩展欧几里得、线性递推......

费马小定理:假如a是一个整数,p是一个质数,那么

1、如果a是p的倍数 a^p=a(mod p)

2、如果a不是p的倍数,a^(p-1)=1(mod p)    乘法逆元中要求互斥,所以肯定不是倍数

同余式:a=b(mod n)表示a和b对模n同余,即正整数a-b能被n整除

所以   a*a^(p-2)=1 (mod p)那么a^(p-2) 就是 a 的逆元了~

但是需要注意:上面都不是等号,有(mod p)的这种都是同余符号,三个横线;所以这个式子完整的应该是(a%p)*(a^(p-2))%p=1%p,这才是等式

例如:a=5,p=3;那么a的逆元就是 5^(3-2)%3=2;,所以在这里就可以也可以用快速幂进行优化;

ll fpm(ll x,ll power,ll mod)//x^power%mod就是要求的逆元 
{
    x%=mod;
    ll ans=1;
    for(;power;power>>=1,(x*=x)%=mod)
        if(power&1) (ans*=x)%=mod;
    return ans; 
}

****求一串数字的逆元可以用线性算法,代码过背吧...

ll inv[maxn]={0,1};
int main(){
    int n,p;
    scanf("%d%d",&n,&p);
    printf("1
");
    for(int i=2;i<=n;i++)
        inv[i]=(ll)p-(p/i)*inv[p%i]%p,printf("%d
",inv[i]);
    return 0;
}

 

那么整个题目的分析应该就完了

最后再来总结一下这个题目,

首先我们清楚了n栋楼房在高度范围为<=m的所有排列情况,因为可以为空所以一共有c(n+m-1,m-1)种方案;

然后我们加上限制条件5,两种情况:一种四段相乘,一种两段相乘 

然后我们知道c(m+n-1,m-1)=(m+n-1)!/n!*(m-1)!,由于数会很大,所以我们需要模p,但是除法不满足分配律,我们利用乘法逆元把它变成乘法的模,就可以防止中间数会很大溢出;

我们知道要用乘法逆元之后,选择求出逆元的方法有很多,这里用费马小定理,那么我们在求的时候还可以加上快速幂,那么关于

over!下面就来看代码了~(好吧,尽量理解了)

 !!!!!!!!我感觉思路没有什么问题,但答案就是不对。。。

技术图片
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=998244353;
ll ans=0;
int n,m,x,y;
ll fpm(int num,int power)//x^power%mod就是要求的逆元 
{
    num%=mod;
    ll res=1;
    for(;power;power>>=1,(num *= num) %=mod)
        if(power&1) (res *= num) %=mod;
    /*应该就是快速幂 
    for(;power;power>>=1,(x*=x)%=mod)
        if(power&1) (ans*=x)%=mod;
    */
    return res; 
}
ll c(int a,int b)
{
    ll sum=1;
    for(int i=1;i<=b;i++)
    {
        sum=sum%mod*(a-b+i)%mod;
        sum=sum%mod*fpm(i,mod-2)%mod;
    } 
    return sum;
}
int main()
{
    cin>>m>>n>>x>>y;
    if(x<=n&&y>=n)//在两侧的情况
    {
        for(int i=1;i<=m;i++)//枚举相等的两栋楼的情况
            //ans=(ans+((ll)f(x-1,i)*f(n-x,m-i+1)%mod*f(y-n-1,m-i+1)%mod*f(n*2-y,i)%mod))%mod;
            ans+=c(x+i-1,i-1)%mod*c(n-x+m-i,m-i)%mod*c(y-n+m-i,m-i)%mod*c(2*n-y+i-1,i-1)%mod;
    } 
    else 
    {
        //ans=(ll)c(n+m,m)*c(x+n-y+m,m)%mod;
        ans=c(n+m-1,m-1)%mod*c(n+m-1-x+y,m-1)%mod;
    }
    cout<<ans<<endl;
    return 0;
}
有问题

有人知道就评论一下吧,所以现在换一个做法,其实思路差不多就是划分的区域和代码有点出入。

划分区域:1x1,x+1n,n+1y1,y+1n2

那么就是f(i,j)=(i+j-1)!/(j-1)!*i!

所以先进行预处理求出相关的逆元与阶乘,所以就可以用到线性逆元的方法,前面也写了,直接记住也可以:

技术图片
#include<bits/stdc++.h>
#define ll long long
#define mod 998244353
using namespace std;
const int N=200001;
int n,m,x,y;
ll k[N],inv[N],invk[N];
ll ans=0;
ll f(ll a,ll b)
{
    return (ll)(k[a+b-1]*invk[a]%mod*invk[b-1]%mod);
}

int main(){
    scanf("%d%d%d%d",&m,&n,&x,&y);
    k[0]=inv[1]=invk[0]=1;
    for(int i=2;i<=n+m;i++) inv[i]=((ll)mod-mod/i)*inv[mod%i]%mod;//处理1到n的逆元
    for(int i=1;i<=n+m;i++) k[i]=(ll)k[i-1]*i%mod;//处理阶乘
    for(int i=1;i<=n+m;i++) invk[i]=(ll)invk[i-1]*inv[i]%mod;//处理阶乘数组的逆元
    if(x<=n&&y>n){
        for(int i=1;i<=m;i++) ans=(ans+(ll)(f(x-1,i)*f(n-x,m-i+1)%mod*f(y-n-1,m-i+1)%mod*f(n*2-y,i)%mod))%mod;
        printf("%lld",ans);
    }
    else{
        printf("%lld",(ll)f(n,m)*f(x+n-y,m)%mod);
    }
    return 0;
}
View Code

 


 

好吧下一次的noi online 又要来了,慌张。。。

以上是关于noi online round2(入门组)的主要内容,如果未能解决你的问题,请参考以下文章

P6185 [NOI Online #1 提高组] 序列(二分图)

NOI Online 2020 #1

noip online 入门组

[NOI Online 2022]如何正确地排序

第三次noi online爆零记

第三次noi online爆零记