恩偶挨批模拟试题-12
水博客太快乐了
考场
先看 \(t1\) ,
这不是开个桶就完了?
再看 \(t2\) ,
这不是推推式子就完了??
再看 \(t3\) 确实没啥思路。。。。。
重新看题,发现 \(t1\) 看错题了,想了半个多小时,只写了暴力。(此时一位 \(HZ\) 巨佬已经场切了 \(t1\) 。。。。
又用半个小时写了 \(t2\) \(70pts\) ,剩下 \(30pts\) 感觉不是很好想,不要了。。(血亏。。。。
\(t3\) 是真的毒瘤,啥也不会,不过感觉像是个线性 \(dp\) 。。。。
发现 \(t1\) 给了 \(20pts\) 随机数据,想到用单调栈优化一下,在随机数据下跑的飞快,相当于是 \(O(n log_{n})\) 。。。大概骗到 \(50pts\) 。。。(此时一位 \(HZ\) 巨佬已经场切了 \(t2\) 。。。。
剩下的时间用来对拍,考试快结束才想起来 \(t3\) 忘输出 \(-1\) 骗分了。。。
提交后比赛已经结束了 \(17s\) 。。。我大意了。。。
分数
预估 : \(t1\) \(50pts\) \(+\) \(t2\) \(70pts\) \(+\) \(t3\) \(10pts\) \(=\) \(130pts\)
实际 : \(t1\) \(40pts\) \(+\) \(t2\) \(70pts\) \(+\) \(t3\) \(0pts\) \(=\) \(110pts\)
\(t1\) 第一个点莫名 \(WA\) 了???
重交就过了。。。。
\(t3\) 忘骗分了。。。。
题解
T1 简单的区间
考虑分治。
对于分治得到的每一个区间,统计 \(l \le mid\) 且 \(r > mid\) 的区间数量,若每个区间内能做到 \(O(n)\) ,则总时间复杂度为 \(O(n log_{n})\) ,可以轻松过。。。
考虑以 \(mid\) 为起点向右构造前缀 \(pre\) 向左构造后缀 \(suf\) ,则显然满足以下关系的区间 \([l,r]\) 会被统计到答案中:
\(suf_{l}+pre_{r}-max_{i=l}^{i \le r} a \equiv 0(mod\) \(k)\)
考虑移项:
\(pre_{r}-max_{i=l}^{i \le r} a \equiv -suf_{l}(mod\) \(k)\)
\(max_{i=l}^{i \le r} a -suf_{l} \equiv pre_{r}(mod\) \(k)\)
左右两项在 \(mod\) \(k\) 意义下是相等的,显然可以开一个桶存下来。
然而在处理时 \(l,r\) 两点间的最大值显然不是一个定值,所以需要在处理时动态地进行修改。
考虑处理出区间 \([mid+1, r]\) 中每一个点到点 \(mid\) 的最大值,将所有出现过的最大值存到一个栈中,处理区间 \([l, mid]\) 时,设当前处理到第 \(i\) 号点,将 \([i, mid]\) 的最大值与栈中的值比较,并对桶进行更新,并根据如上两个式子统计以 \(i\) 为左边节点的答案。
code
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10, K=1e6+10;
inline int read(){
int f=1, x=0; char ch=getchar();
while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
return f*x;
}
int n, k;
int a[N], s[N], pos[N];
int t1[K], t2[K], stk[N], top;
long long ans;
void solve(int l, int r){
if(l==r) return;
int mid=(l+r)>>1;
int sum=0; top=0; s[mid]=0;
for(int i=mid+1; i<=r; ++i){
if(a[i]>a[stk[top]]) stk[++top]=i;
s[i]=(s[i-1]+a[i])%k; pos[i]=stk[top];
t1[(s[i]-a[pos[i]]%k+k)%k]++;
}
stk[top+1]=r+1;
int p2=mid+1, p1=1, maxn=0;
for(int i=mid; i>=l; --i){
maxn=max(maxn, a[i]); sum+=a[i]; sum%=k;
while(maxn>=a[stk[p1]]&&p1<=top) p1++;
while(p2<stk[p1]){
t1[(s[p2]-a[pos[p2]]%k+k)%k]--;
t2[s[p2++]]++;
}
if(p1<=top) ans+=t1[(k-sum)%k];
ans+=t2[(maxn%k-sum+k)%k];
}
for(int i=mid+1; i<p2; ++i) t2[s[i]]--;
for(int i=p2; i<=r; ++i) t1[(s[i]-a[pos[i]]%k+k)%k]--;
solve(l, mid); solve(mid+1, r);
}
int main(void){
n=read(), k=read();
for(int i=1; i<=n; ++i) a[i]=read();
solve(1, n); printf("%lld\n", ans);
return 0;
}
然而巨佬 401rk8 显然有更好的做法。。。。
我才不会说是因为我懒得写所以放链接。。。。
T2 简单的玄学
全场最简单的题。。。。
然而我还是不会做。。。
貌似没啥可说的,直接推式子,梦回初中课堂。
\(ans = 1 - \frac{\prod_{i=1}^{m-1} 2^{n}-i}{2^{n(m-1)}}\)
分母显然可以用快速幂,然而分子却是一个 \(m-1\) 项的连乘,要直接处理显然会惨 \(t\) ,此时可以注意到 \(mod=1e6+3\) ,是一个很奇怪的数,然而确实是一个很不常见的质数。
为什么它会顶替 \(1e9+7\) 出现在这里?
因为它很小。。。。当 \(m-1>mod\) 时,显然分子至少有一项是 \(mod\) 的倍数,那么分子在 \(mod\) 意义下等于零,不用处理。。。而若 \(m-1<mod\) 那么线性可过。
还要约分?显然分母中质因子只有 \(2\) 一个。。。。
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e6+3, T=5e5+2;
inline int read(){
int f=1, x=0; char ch=getchar();
while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
return f*x;
}
inline int qp(int n, int m){
int ans=1;
while(m){
if(m&1) ans=ans*n%mod;
m>>=1; n=n*n%mod;
}
return ans;
}
int n, m, a, b;
signed main(void){
n=read(), m=read(); a=1;
int len=0, y=m;
while(y) len++, y>>=1;
if(len>n) { printf("1 1\n"); return 0; }
int p=qp(2, n%(mod-1)), ul=0, u=m-1;
b=qp(p, (m-1)%(mod-1));
while(u) ul+=(u>>=1);
if(m<=mod) for(int i=1; i<m; ++i) a=a*(p-i)%mod;
else a=0;
a=(a%mod+mod)%mod; a*=qp(T, (ul)%(mod-1)); b*=qp(T, ul%(mod-1)); a=b-a;
a=(a%mod+mod)%mod; b=(b%mod+mod)%mod;
printf("%lld %lld\n", a, b);
return 0;
}
T3 简单的填数
完全没有思路。。。
后来看巨佬的代码才明白。。。
定义二元组 \((x,y)\) 表示当前位置储存的数是 \(x\) ,之前已经有 \(y\) 个和它相同的数。
每个点开两个如上二元组 \(up,down\) ,分别表示当前位置存最大数和最小数的可行方案。
顺序递推求出 \(up,down\) ,再倒序求出序列。
感觉很玄学???
实际上这很科学。。。。
具体怎么科学直接看代码吧。。。。。。
我懒得写了。。。
code
#include<bits/stdc++.h>
using namespace std;
#define f first
#define s second
const int N=2e5+10, S=5;
inline int read(){
int f=1, x=0; char ch=getchar();
while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
return f*x;
}
int n, a[N], ans[N], vis[N];
pair<int, int > up[N], down[N];
int main(void){
n=read();
for(int i=1; i<=n; ++i) a[i]=read();
if(a[1]>1) { printf("-1\n"); return 0; }
up[1]=down[1]=make_pair(1, 1); a[1]=1;
for(int i=2; i<=n; ++i){
up[i]=make_pair(up[i-1].f, up[i-1].s+1);
down[i]=make_pair(down[i-1].f, down[i-1].s+1);
if(up[i].s>2) ++up[i].f, up[i].s=1;
if(down[i].s>5) ++down[i].f, down[i].s=1;
if(a[i]){
if(up[i].f>a[i]) up[i]=make_pair(a[i], 2);
if(down[i].f<a[i]) down[i]=make_pair(a[i], 1);
if(down[i].f>a[i]||up[i].f<a[i]) { printf("-1\n"); return 0; }
}
}
if(up[n].s==1) up[n].f--, up[n].s=up[n-1].s+1;
ans[n]=up[n].f; vis[ans[n]]=1;
for(int i=n-1; i; --i){
if(a[i]) ans[i]=a[i];
else{
ans[i]=min(ans[i+1], up[i].f);
if(vis[ans[i]]==5) ans[i]--;
}
vis[ans[i]]++;
}
printf("%d\n", up[n].f);
for(int i=1; i<=n; ++i) printf("%d ", ans[i]);
printf("\n");
return 0;
}