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; }
为什么用自己当时的做法结合一下只能得八十分呢~好奇怪,最容易错的反而没有错:
#include <bits/stdc++.h> using namespace std; const int N=405,inf=0x3f3f3f3f; int n,m,c1,c2,s,a[N][N],sol[N][N],vis[N][N][20][20]; int d[8][2]={{0,1},{0,-1},{1,0},{-1,0},{1,1},{1,-1},{-1,1},{-1,-1}}; int sx,sy,ex,ey,ans=inf,ans1=inf,ans2=inf; struct node{ int x,y,c1,c2,num; }; void pre(int x,int y,int dis,int step)//标记士兵范围的区域 { int rx,ry; if(step==dis) return; else{ for(int i=0;i<4;i++){ rx=x+d[i][0]; ry=y+d[i][1]; if(!sol[rx][ry]&&!a[rx][ry]&&rx>=1&&rx<=n&&ry>=1&&ry<=m){ a[rx][ry]=1;//表示在士兵范围内 pre(rx,ry,dis,step+1); } } } } int num(string s) { int ans=0; for(int i=0;i<s.length();i++) ans=ans*10+s[i]-'0'; return ans; } void bfs() { queue<node> q; q.push((node){sx,sy,0,0,0}); vis[sx][sy][0][0]=1; while(!q.empty()) { node p=q.front();q.pop(); if(p.num>ans) continue;//剪枝 if(p.x==ex&&p.y==ey){ if(p.num<ans){ans=p.num;ans1=p.c1;ans2=p.c2;} else{ if(p.c1+p.c2<ans1+ans2){ans=p.num;ans1=p.c1;ans2=p.c2;} else if(p.c1+p.c2==ans1+ans2&&ans1>p.c1){ans=p.num;ans1=p.c1;ans2=p.c2;} } continue; } for(int i=0;i<8;i++){ int rx=p.x+d[i][0],ry=p.y+d[i][1]; if(rx<1||rx>n||ry<1||ry>m) continue;//越界 if(sol[rx][ry]>0) continue;//有士兵 if(!a[rx][ry]&&!vis[rx][ry][p.c1][p.c2]){//不在士兵的观察范围内并且没有访问过 vis[rx][ry][p.c1][p.c2]=1;//标记 q.push((node){rx,ry,p.c1,p.c2,p.num+1}); } else if(p.c1+1<=c1&&!vis[rx][ry][p.c1+1][p.c2]){//在士兵的观察范围内,使用隐身 vis[rx][ry][p.c1+1][p.c2]=1;//标记 q.push((node){rx,ry,p.c1+1,p.c2,p.num+1}); } }//只使用隐身或者直接走的情况 if(p.c2+1>c2) continue;//已经不能使用瞬移就直接结束 for(int i=0;i<4;i++) { int rx=p.x+d[i][0]*s,ry=p.y+d[i][1]*s; if(rx<1||rx>n||ry<1||ry>m) continue;//越界 if(sol[rx][ry]>0) continue;//有士兵 if(!a[rx][ry]&&!vis[rx][ry][p.c1][p.c2+1]){//不在士兵的观察范围内并且没有访问过 vis[rx][ry][p.c1][p.c2+1]=1;//标记 q.push((node){rx,ry,p.c1,p.c2+1,p.num+1}); } else if(p.c1+1<=c1&&!vis[rx][ry][p.c1+1][p.c2+1]){//在士兵的观察范围内,额外使用隐身 vis[rx][ry][p.c1+1][p.c2+1]=1;//标记 q.push((node){rx,ry,p.c1+1,p.c2+1,p.num+1}); } } } } int main() { //cin>>n>>m; //pre(3,3,3,1); scanf("%d %d %d %d %d",&n,&m,&c1,&c2,&s); string c; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { cin>>c; if(c[0]=='S') {sol[i][j]=-1;sx=i;sy=j;} else if(c[0]=='T'){sol[i][j]=-2;ex=i;ey=j;} else if(c[0]>='0'&&c[0]<='9'){ sol[i][j]=num(c); pre(i,j,sol[i][j],1); } } bfs(); if(ans==inf)printf("-1");//无解 else printf("%d %d %d",ans,ans1,ans2);//输出 /* for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) cout<<a[i][j]<<" "; cout<<endl; } cout<<endl; for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) cout<<sol[i][j]<<" "; cout<<endl; } */ //dfs(sx,sy,0,0,0); return 0; }
来了,找到原因了,补充来了:
dfs:
void pre(int x,int y,int dis,int step)//标记士兵范围的区域 { int rx,ry; if(step==dis) return; for(int i=0;i<4;i++){ rx=x+d[i][0]; ry=y+d[i][1]; if(sol[rx][ry]<=0&&rx>=1&&rx<=n&&ry>=1&&ry<=m){//错误做法就是加上一个条件!a[rx][ry] 能得80分 a[rx][ry]=1;//表示在士兵范围内 pre(rx,ry,dis,step+1); } } }
bfs:
void pre(int x,int y,int dis,int step) { queue<obs> q;int rx,ry;//还有可能超空间 q.push((obs){x,y,step}); while(!q.empty()) { obs p=q.front();q.pop(); if(p.step==dis) continue; for(int i=0;i<4;i++){ rx=p.x+d[i][0]; ry=p.y+d[i][1]; if(sol[rx][ry]<=0&&rx>=1&&rx<=n&&ry>=1&&ry<=m){ a[rx][ry]=1;//表示在士兵范围内 q.push((obs){rx,ry,p.step+1}); } } } }
题目三:建设城市(分为四段,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; }
在来简单版的讲解:
#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); } ll quickpow(ll a,int b){//快速幂写法一 if(b==1) return a; ll res=(ll)quickpow(a,b>>1); if(b%2==1) return (ll)res*res%mod*a%mod; else return (ll)res*res%mod; } /* ll quickpow(ll a,int b){//快速幂写法二 ll res=1; while(b) { if(b&1) res=(ll)res*a%mod; a=(ll)a*a%mod; b>>=1; } return res; }*/ int main(){ scanf("%d%d%d%d",&m,&n,&x,&y); k[0]=invk[0]=1; 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)quickpow(k[i],mod-2);//处理阶乘数组的逆元 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; }
好吧下一次的noi online 又要来了,慌张。。。