ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

noi online round2(入门组)

2020-05-03 20:07:15  阅读:219  来源: 互联网

标签:noi power int ll ans 逆元 online round2 mod


又来了,第一题还是比较简单的,而且正好前面在弄最长不下降子序列的时候学到了二分的函数,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\n");
    for(int i=2;i<=n;i++)
        inv[i]=(ll)p-(p/i)*inv[p%i]%p,printf("%d\n",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;
}
有问题

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

划分区域:1∼x−1,x+1∼n,n+1∼y−1,y+1∼n∗2

那么就是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,power,int,ll,ans,逆元,online,round2,mod
来源: https://www.cnblogs.com/sunny99/p/12790071.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有