NOIP提高组模拟赛6
A. 简单的区间
这题看着真熟啊,好像把之前的english,入阵曲杂糅了一下。
首先,像入阵曲一样计算出前缀和式子可以转化为求
像english一样
用单调栈处理出以X为最大值的区间,分区间求解
每次枚举一侧区间,已知MAX,只要知道另一侧有多少与之余数相同的即可
然鹅这题数据范围不允许用前缀和优化某区间余数为特定值的个数,这样english中的启发式合并就没有意义,因为两边都要扫,如果数据相对单调,非常容易卡成喜提TLE。
所以揉了两道题做不出怎么办?再揉一道
像之前的数颜色那样用vector存余数为i的前缀和的下标,每次查询直接二分,这样复杂度就可以了,记得存一个0进去
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int MAXN=1000005;
int a[MAXN],l[MAXN],r[MAXN],stc[MAXN],top,n,k;
long long sum[MAXN],ans=0;
vector<int>v[MAXN];
int ask(int ll,int rr,int y){
int p1=lower_bound(v[y].begin(),v[y].end(),ll)-v[y].begin();
int p2=upper_bound(v[y].begin(),v[y].end(),rr)-v[y].begin();
return p2-p1;
}
void work(int ll,int rr,int xx){
if(rr-xx<xx-ll) for(int i=xx;i<=rr;++i)ans+=ask(ll-1,xx-1,(sum[i]-a[xx])%k);
else for(int i=ll;i<=xx;++i)ans+=ask(xx,rr,(sum[i-1]+a[xx])%k);
--ans;
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i)scanf("%d",&a[i]);
for(int i=1;i<=n;++i)sum[i]=sum[i-1]+a[i];
for(int i=1;i<=n;++i){
while(top&&a[stc[top]]<=a[i]){r[stc[top]]=i-1;--top;}
l[i]=stc[top]+1;
stc[++top]=i;
}
while(top){r[stc[top]]=n;--top;}
for(int i=0;i<=n;++i)v[sum[i]%k].push_back(i);
for(int i=1;i<=n;++i)if(l[i]<r[i])work(l[i],r[i],i);
printf("%lld\n",ans);
return 0;
}
B. 简单的玄学
首先能够得到,至少有两个变量取值相同的概率,就是1-任意两个变量取值都不同的概率
即
如何计算这个式子,并且保留分数形式?
----约分
分母只有2这个因子,上下约分一定只能约2的几次方
如何约分?
有:与的2的因子个数一样
性感感性理解下
(q不含2这个因子)
因为q不含2这个因子,所以q是奇数,也是奇数,不含2这个因子,所以与的2的因子个数一样,即P
所以判断能约几个2,判断分母和有几个2这个因子即可
用除以2,得到其中有几个含2这个因子的,除以4得到有几个含两个2这个因子的....不停除以2的此方,直到大于累计一下个数,得到约分的数,上下乘以逆元即可
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
const long long mod=1e6+3,nv=500002;
long long m,n;
long long qp(long long x,long long y){
long long sum=x,ans=1;
while(y){
if(y&1)ans=(ans*sum)%mod;
sum=(sum*sum)%mod;
y>>=1;
}
return ans;
}
int main(){
scanf("%lld%lld",&n,&m);
if(log2(m)>n){printf("1 1\n");return 0;}
long long p=qp(2,n),q=qp(p,m);
long long a=1;
for(int i=0;i<m;++i){a=a*(p-i)%mod;if(!a)break;}
long long yf=n;--m;
for(long long i=2;i<=m;i<<=1)yf+=m/i;
long long inv=qp(nv,yf);
q=(q*inv)%mod; a=(a*inv)%mod;
printf("%lld %lld\n",(q-a+mod)%mod,q%mod);
return 0;
}
C. 简单的填数
想出过卡上下界的做法,但是不知道怎么处理有数的情况,还是太菜
定义二元组,(其实就是两个数组)up[i] 和down[i] ,表示上下界,即到第i个数,最小是down->x,出现次数down->c 最大up-x,出现次数up->c
显然一般情况up每两个加一,down每5个加一,如果有数,先用上下界判是否合法,然后更新上下界,由于要让up最大,down最小,显然令
这样就能判断是否存在合法方案,并且求出最大值,(注意特判一下
需要减1)
然后考虑如何填数,倒叙填数,定义now和sum,sum表示连续填了几个相同的数,每次填;当有数,;sum每到5 now要减一
不是很理解,先口胡一下,不对请大佬指正
这个now大概是个上界,而我们此时需要尽可能小(减的快),就需要用up此时是下界,但是由于正着扫时候有数是直接暴力修改,导致中间会断,部分的下界是错误的,需要用上界修正,而now的作用就在这时候体现了,就是说up和now交替合法
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=200005;
int min(int x,int y){return x<y?x:y;}
int a[maxn],n;
struct eyz{
int z,c;
}up[maxn],down[maxn];
int as[maxn];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%d",&a[i]);
up[1].z=1;up[1].c=1;down[1].c=1;down[1].z=1;
bool flag=0;
for(int i=2;i<=n;++i){
if(up[i-1].c>=2) { up[i].z=up[i-1].z+1; up[i].c=1; }
else { up[i].z=up[i-1].z; up[i].c=up[i-1].c+1;}
if(down[i-1].c<5){ down[i].c=down[i-1].c+1; down[i].z=down[i-1].z;}
else{ down[i].c=1; down[i].z=down[i-1].z+1;}
if(a[i]){
if(a[i]<down[i].z||a[i]>up[i].z){flag=1;break;}
if(up[i].z>a[i]){up[i].z=a[i];up[i].c=2;}
if(down[i].z<a[i]){down[i].z=a[i];down[i].c=1;}
}
}
if(up[n].c==1){up[n].c=up[n-1].c+1;up[n].z=up[n-1].z;}
if(flag||up[n].z<down[n].z){printf("-1\n");return 0;}
int now=up[n].z,num=1;
for(int i=n;i>=1;--i){
if(a[i]&&now>a[i])now=a[i],num=0;
if(num==5)--now,num=0;
++num;
as[i]=min(now,up[i].z);
}
printf("%d\n",up[n].z);
for(int i=1;i<=n;++i)printf("%d ",as[i]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】