这篇总结比我写的好多了建议直接去看
T1 简单的序列
考场:
愣了一会,想到以最大值分治。每次枚举最大值两侧更小的区间,st表预处理前缀和和最大值,用桶统计答案.
注意分治时要去掉最大值。
const int N = 3e5+5, X = 1e6+5; int n,k,a[N]; int m,s[N],head[N],nxt[N*36],val[N*36],op[N*36],cnt[X]; LL ans; namespace st { int lg[N],pw[19],f[19][N],p[19][N]; void init() { lg[1] = 0; For(i,2,n) lg[i] = lg[i>>1]+1; pw[0] = 1; For(i,1,18) pw[i] = pw[i-1]<<1; For(i,1,n) f[0][i] = a[i], p[0][i] = i; For(j,1,18) for(int i = 1; i+pw[j]-1 <= n; ++i) { int mid = i+pw[j-1]; if( f[j-1][i] > f[j-1][mid] ) f[j][i] = f[j-1][i], p[j][i] = p[j-1][i]; else f[j][i] = f[j-1][mid], p[j][i] = p[j-1][mid]; } } int maxx(int l,int r) { int k = lg[r-l+1], mid = r-pw[k]+1; return f[k][l]>f[k][mid] ? p[k][l] : p[k][mid]; } } void add(int x,int l,int r) { x %= k; if( x < 0 ) x += k; // printf(">%d %d %d ",x,l,r); if( l ) val[++m] = x, op[m] = -1, nxt[m] = head[l-1], head[l-1] = m; val[++m] = x, op[m] = 1, nxt[m] = head[r], head[r] = m; } void solve(int l,int r) { if( l >= r ) { ans += l==r; return; } int p = st::maxx(l,r); // printf(">%d %d %d ",l,p,r); if( p-l < r-p ) For(i,l,p) add(s[i-1]+a[p],p,r); else For(i,p,r) add(s[i]-a[p],l-1,p-1); solve(l,p-1), solve(p+1,r); } signed main() { read(n,k); For(i,1,n) read(a[i]), s[i] = (s[i-1] + a[i]) %k; st::init(); solve(1,n); For(i,0,n) { ++cnt[s[i]]; for(int j = head[i]; j; j = nxt[j]) ans += op[j] * cnt[val[j]]; } printf("%lld",ans-n); return 0; }
上界 $O(nlog(n))$
或者传统的以 $mid$ ,严格 $O(nlog(n))$
#include <bits/stdc++.h> using namespace std; #define ll long long const int N=300010,M=1100000; int t[M][2],a[N],s[N],mx[N],pos[N],n,k,H; long long ans; inline void read(int &e) { e=0;int w=1;char ch=getchar(); while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();} while(isdigit(ch)){e=e*10+ch-'0';ch=getchar();} e*=w;return; } inline void fenzhi(int l,int r) { if(l==r) return; int mid=(l+r)>>1; s[mid]=H=mx[0]=0; for(int i=mid+1;i<=r;i++) { if(a[i]>a[mx[H]]) mx[++H]=i; s[i]=((s[i-1]+a[i])%k+k)%k; t[((s[i]-a[mx[H]])%k+k)%k][0]++; pos[i]=mx[H]; } mx[H+1]=r+1; int p = 1,z = 0,p1 = mid + 1,mx1 = 0; for(int i=mid;i>=l;i--) { z=(z+a[i])%k,mx1=max(mx1,a[i]); while(p<=H&&a[mx[p]]<=mx1) ++p; while(p1<mx[p]) { t[(s[p1]-a[pos[p1]]%k+k)%k][0]--; t[s[p1]][1]++; p1++; } ans+=t[(k+mx1%k-z)%k][1]; if(p<=H)ans+=t[(k-z)%k][0]; } for(int i=mid+1;i<p1;++i) t[s[i]][1]--; for(int i=p1;i<=r;++i) t[(s[i]-a[pos[i]]%k+k)%k][0]--; fenzhi(l,mid); fenzhi(mid + 1,r); } int main () { read(n);read(k); for(int i = 1;i <= n; ++i) { read(a[i]); } fenzhi(1,n); printf("%lld ",ans); return 0; }
T2 简单的玄学
显然有:
$egin{align}ans=frac{prod_{i=n-m+1}^{n-1}2^n-i}{2^{n(m-1)}}\end{align}$
考场:不难发现连乘部分是两端阶乘之商,而 $m$ 范围较大,阶乘中极易“经过”模数 $1000003$ 的倍数,那么只需要预处理至模数的阶乘就可以,对作为除数的阶乘求逆元。
但如果作为阶乘取模变为0,但是两者仍然要约分怎么办,可以先取模再约分吗?如果先约分再取模是不是太难了,应该是先取模再约分,举了个例子没有发现问题。
于是炸了。
题解:先取模再约分的话就太水了,所以先约分再取模。
显然有 $2^n-i$ 中2的个数等于 $i$ 中2的个数。
故对上面的式子分母两次快速幂 $egin{align}a^{b} mod s=a^{b mod (s-1)} mod send{align}$ ,之后统计分子中2的因数个数,相当于统计 $(m-1)!$ 阶乘中2的因数个数:核心代码:
long long ercnt=n; for(long long i=1;(1LL<<i)<m;i++) { ercnt+=(m-1)/(1LL<<i); }
约分后取模,可以取模后分子分母同乘2的逆元的因数个数幂。切记不要想当然除gcd。
#include<bits/stdc++.h> using namespace std; const int mod=1e6+3,phi=1e6+2; long long jc[mod+100],a,b,n,m,ercnt; long long quick_pow(long long x,long long y) { long long rec=1; while(y) { if(y&1) { rec=rec*x%mod; } y>>=1; x=x*x%mod; } return rec; } int main() { scanf("%lld%lld",&n,&m); long long ercnt=n; for(long long i=1;(1LL<<i)<m;i++) { ercnt+=(m-1)/(1LL<<i); } long long maxn=quick_pow(2,n%phi); b=quick_pow(maxn,m%phi); a=1; for(long long i=0;i<m;i++) { a=a*(maxn-i)%mod; if(!a) break; } b=b*quick_pow(500002,ercnt)%mod;//2逆元500002 a=a*quick_pow(500002,ercnt)%mod; printf("%lld %lld ",(b-a+mod)%mod,b); return 0; }
T3 简单的填充
考场:没看
DP,维护 $up$ 和 $down$,分别表示当前格子最大可能能放的数字以及这个数字是第几次重复,和当前格子最小可能能放的数字以及这个数字是第几次重复。遍历时两者都继承上一个格子,上限每重复两次就更新,下限则是五次。如果当前格子有值就用它纠正二者,矛盾则impossible。为满足题意,最后剩下的格子(与 $n$ 点间没有确定的数)尽量多放大数。
#include<bits/stdc++.h> using namespace std; const int N=1100000; int upc[N],upx[N],a[N],n,downc[N],downx[N],tong[N]; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); } if(a[1]!=1&&a[1]!=0) { puts("-1"); return 0; } upc[1]=1;upx[1]=1; downc[1]=1;downx[1]=1; for(int i=2;i<=n;i++) { upc[i]=upc[i-1];upx[i]=upx[i-1]; downc[i]=downc[i-1];downx[i]=downx[i-1]; if(++upc[i]>2) upc[i]=1,upx[i]++; if(++downc[i]>5) downc[i]=1,downx[i]++; if(a[i]) { if(upx[i]>a[i]) { upx[i]=a[i]; upc[i]=2; } else if(upx[i]==a[i]) { upc[i]=min(upc[i],2); } if(downx[i]<a[i]) { downx[i]=a[i]; downc[i]=1; } if(upx[i]<a[i]||downx[i]>a[i]) { puts("-1"); return 0; } } } if(upc[n]==1) { upx[n]=upx[n-1]; } if(upx[n]<downx[n]) { puts("-1"); return 0; } printf("%d ",upx[n]); tong[upx[n]]++; a[n]=upx[n]; for(int i=n-1;i>=1;i--) { if(!a[i]) { int tmp=min(a[i+1],upx[i]); if(tong[tmp]==5) tmp--; a[i]=tmp; } tong[a[i]]++; } for(int i=1;i<=n;i++) { printf("%d ",a[i]); } return 0; }
T1&T2都几乎正解思路但是打成不如暴力分QWQ