Description
基本思路:
区间问题,我最不擅长了
考虑分治:
我们首先可以\(O(n)\)用单调栈搞出没一个值作为最大值的区间的左右端点,并且可以处理出第i位前缀和%k的余数并记录,用一个vector,空间复杂度为\(O(n)\),可以接受,由于我们是顺序遍历,所以在每一个vector中下标是升序的。
然后我们遍历\(1~n\),处理包含\(a[i]\)在内的区间
我们不必左右两个区间都枚举。只需枚举较短的区间,然后在vector中利用lower_bound与upper_bound\(O(logn)\)查询即可;
上代码:
#include<bits/stdc++.h> using namespace std; namespace STD { #define ll long long #define rr register #define inf INT_MAX #define max(x,y) (x>y?x:y) const int SIZE=3e5+4; const int SIZEK=1e6+4; int n,k; ll ans; ll a[SIZE]; int f[SIZE]; int l[SIZE],r[SIZE];//l<r vector<int> b[SIZEK]; int read() { rr int x_read=0,y_read=1; rr char c_read=getchar(); while(c_read<'0'||c_read>'9') { if(c_read=='-') y_read=-1; c_read=getchar(); } while(c_read<='9'&&c_read>='0') { x_read=x_read*10+(c_read^48); c_read=getchar(); } return x_read*y_read; } struct pack { int fi,se; pack(){} pack(int fi_,int se_){this->fi=fi_;this->se=se_;} }; class Stack { private: pack *Top; pack s[SIZE]; public: Stack(){Top=s;} void push(pack x){*(++Top)=x;} void pop(){Top--;} bool empty(){return Top==s;} pack top(){return *Top;} }s; }; using namespace STD; int main() { n=read(),k=read(); b[0].push_back(0); //一定要提前把0压入原因见下 for(rr int i=1;i<=n;i++) { a[i]=read(); f[i]=(f[i-1]+a[i]%k)%k; b[f[i]].push_back(i); } //单调栈 for(rr int i=1;i<=n;i++) { if(s.empty()){s.push(pack(a[i],i));continue;} if(s.top().fi>=a[i]){s.push(pack(a[i],i));continue;} while(!s.empty()&&s.top().fi<a[i]) { r[s.top().se]=i-1; s.pop(); } s.push(pack(a[i],i)); } while(!s.empty()){r[s.top().se]=n;s.pop();} for(rr int i=n;i;i--) { if(s.empty()){s.push(pack(a[i],i));continue;} if(s.top().fi>a[i]){s.push(pack(a[i],i));continue;} while(!s.empty()&&s.top().fi<=a[i]) { l[s.top().se]=i+1; s.pop(); } s.push(pack(a[i],i)); } while(!s.empty()){l[s.top().se]=1;s.pop();} // //这个统计ans的式子是依据%k相同的数相减可以被k整除 //就有j[r]-j[l-1]-a[i]==0 //下面num的计算是依据这个式子移项来的 //注意观察与思考lower_bound第3个参数 for(rr int i=1;i<=n;i++) { if(l[i]==r[i]) continue;//l<r //以i作为某一端点 int num,st,en; if(i-l[i]) { num=((f[i]-a[i]%k)+k)%k; st=lower_bound(b[num].begin(),b[num].end(),l[i]-1)-b[num].begin(); en=upper_bound(b[num].begin(),b[num].end(),i-2)-b[num].begin(); //一定要注意b[num]可能为空,如果不判空就调用会RE //为因为这个没看出来调了一下午,这下要长记性了QAQ。 if(b[num].size()&&b[num][st]<=i-2) ans+=(en-st); } if(r[i]-i) { num=(f[i-1]+a[i]%k)%k; st=lower_bound(b[num].begin(),b[num].end(),i+1)-b[num].begin(); en=upper_bound(b[num].begin(),b[num].end(),r[i])-b[num].begin(); if(b[num].size()&&b[num][st]<=r[i]) ans+=(en-st); } if(!(i-l[i])||!(r[i]-i)) continue; if(i-l[i]<r[i]-i) { for(rr int j=l[i]-1;j<i-1;j++) { num=(f[j]+a[i]%k)%k; st=lower_bound(b[num].begin(),b[num].end(),i+1)-b[num].begin(); en=upper_bound(b[num].begin(),b[num].end(),r[i])-b[num].begin(); if(b[num].size()&&b[num][st]<=r[i]) ans+=(en-st); } } else { for(rr int j=i+1;j<=r[i];j++) { num=((f[j]-a[i]%k)+k)%k; st=lower_bound(b[num].begin(),b[num].end(),l[i]-1)-b[num].begin(); en=upper_bound(b[num].begin(),b[num].end(),i-2)-b[num].begin(); if(b[num].size()&&b[num][st]<=i-2) ans+=(en-st); } } } printf("%lld",ans); }T2:简单的玄学
Description:
很明显,直接求是不可能的,所以我们要求\(1-P(一个相等的也没有)\)。
式子就是:
其实式子很好推,但是难计算,考场上就是因为不会高效计算WA了。
其实可以观察,当m或n比较大时,分子是有可能变成1e6+3的倍数的,所以我们当乘到是模数的倍数时,就跳出,因为还有个“1-”呢,总不可能输出一吧;
2的幂次直接快速幂即可。
至于说约分。
我们可以发现,分母的因子全是2.
因此统计分子有多少个2因子即可
有一个性质:
\(2^n-x\)的2因子个数等于\(x\)中的个数,证明:
假设\(x=2^{m}*j\),\(j\)可以是分数
\[2^{n}-x= \]
\[2^{m}*(2^{n-m}-j) \]
显然\(j\)没有2因子,因而\(2^{n-m}-j\)没有2因子,因而该结论正确
又由于连乘因子无损失,因此我们直接求\(!(m-1)\)的2因子个数即可
这有一个\(O(logn)\)求法,详见代码;
分子约分后值求解直接暴力计算即可。
上代码:
#include<bits/stdc++.h> using namespace std; namespace STD { #define ll long long #define rr register #define inf INT_MAX const ll mod=1e6+3; ll n,m; ll read() { rr ll x_read=0,y_read=1; rr char c_read=getchar(); while(c_read<'0'||c_read>'9') { if(c_read=='-') y_read=-1; c_read=getchar(); } while(c_read<='9'&&c_read>='0') { x_read=x_read*10+(c_read^48); c_read=getchar(); } return x_read*y_read; } ll qpow(ll base,ll exp) { ll ret=1; while(exp) { if(exp&1) ret=ret*base%mod; base=base*base%mod; exp>>=1; } return ret; } }; using namespace STD; int main() { n=read(),m=read(); ll cnt=0; for(ll i=1;(1ll<<i)<=(m-1);i++) cnt+=((m-1)/(1ll<<i)); ll b=qpow(2,n); ll n2=b; ll n3; b=qpow(b,m-1); n3=b; ll x=qpow(2,cnt); x=qpow(x,mod-2); b=b*x%mod; ll a=1ll; for(rr ll i=1;i<=m-1;i++) { a=a*((n2-i%mod)%mod)%mod; if(!a) break; } a=a*x%mod; a=(b-a+mod)%mod;//这里一定要注意用b-a再取模,因为b才是约分后的分母 printf("%lld %lld",a,b); }T3:简单的填数
Description:
基本思路:
贪心法
气死,又是这种小点,像分治,贪心,堆栈,队列这些最基本的小点总是会忽视,气死了。
说思路:我们定义两个数组\(up、down\)记录在i位置我们期望可以得到的最大值最小值及其长度,这个正序\(O(n)\)扫一遍就可以
由于他们维护的是最值,那么我们肯定是希望他们越大(小)越好了,那么当\(up\)的长度满2时\(val\)就加一,\(down\)满五时\(val\)就加一,这样就是所有可能值里的最大(小)值了。
然后倒序\(O(n)\)扫一遍,就可以推出真正的序列了。注意真正的序列不是\(up\)的\(val\)因为假如\(len\)为一,那么这个\(up\)的\(val\)只出现了一次,很明显是不合法的。
上代码:
#include<bits/stdc++.h> using namespace std; namespace STD { #define ll long long #define rr register #define min(x,y) x<y?x:y #define max(x,y) x>y?x:y const int SIZE=2e5+4; const int RANGE=1e5+4; int n; int a[SIZE]; int cnt[RANGE]; struct pack{int val,len;}up[SIZE],down[SIZE]; int read() { rr int x_read=0,y_read=1; rr char c_read=getchar(); while(c_read<'0'||c_read>'9') { if(c_read=='-') y_read=-1; c_read=getchar(); } while(c_read<='9'&&c_read>='0') { x_read=(x_read*10)+(c_read^48); c_read=getchar(); } return x_read*y_read; } }; using namespace STD; int main() { n=read(); for(rr int i=1;i<=n;i++) a[i]=read(); up[1].val=up[1].len=1; down[1].val=1,down[1].len=1; for(rr int i=2;i<=n;i++) { up[i].len=up[i-1].len+1; up[i].val=up[i-1].val; down[i].val=down[i-1].val; down[i].len=down[i-1].len+1; if(up[i].len>2) { up[i].val++; up[i].len=1; } if(down[i].len>5) { down[i].val++; down[i].len=1; } if(!a[i]) continue; if(a[i]<up[i].val) up[i].val=a[i],up[i].len=2; if(down[i].val<a[i]) down[i].val=a[i],down[i].len=1; //要注意的是假如说我a已经有值了就要将它与up、down取最值 //因为这时a已经有值的话a之后的值要以他为基础加一加一地上升 //为了兼顾实际情况,我也要与a取最值,更何况我要依靠up搞出an的最大值 //最后的up一定是要可取的,就这一点来说我也要在之前取一下最值 //因为最后an的取值还是要受之前值得影响的,毕竟有2到5的限制 if(a[i]>up[i].val||a[i]<down[i].val){cout<<-1<<endl;return 0;} //由于我维护的是可能的最小(大)值假如说现在的a比up还大或比down还小,就证明我之前无论怎么努力都升(降)不到a显然是不合法的 } if(up[n].len<2) up[n].val=up[n-1].val,up[n].len=up[n-1].len+1; a[n]=up[n].val; printf("%d\n",a[n]); for(rr int i=n;i>=1;i--) { if(a[i]){cnt[a[i]]++;continue;} a[i]=min(a[i+1],up[i].val); if(cnt[a[i]]==5) a[i]--; cnt[a[i]]++; } for(rr int i=1;i<=n;i++) printf("%d ",a[i]); }后记
关于T1:
考场上那个区间的处理方法我想出来了,与上一次模拟题的T3是一样的;
但是我想的是用桶而不是vector记录再二分来查个数,结果考场上没做出来这道题
而且时间复杂度还算错了,人家实际上是\(O(nlogn)\)我算成了\(O(n^{2})\)QAQ
难受,得学着证明了。
气死。
这道题主要还是学习分治法。
而且要活泛,我一想到用桶,就一直想怎么用桶,其实有想过这种方法是不对的。
但是我以为能干,就一直想着怎么把桶改改了,就没有想用别的了。
关于T2:
式子很好推,然而很难计算。因此我考场上直接跳了这道题。
约分用到的性质我不会,这次要记住了。
总归还是想象力不足,也不是很敢想。
这点要改改了。
关于T3
一开始我想的是模拟,不过挂了。
连判无解都写错了。
贪心法不熟,气死。
2021.7.13 现役