恩偶挨批模拟试题-12

水博客太快乐了

RT

考场

先看 \(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;
}
posted @ 2021-07-12 18:23  Cyber_Tree  阅读(101)  评论(2编辑  收藏  举报