恩偶挨批模拟试题-14

水博客太快乐了

RT

考场

先看 \(T1\)
题中给出的式子怎么看都像某种逆序对,考虑各种分治,感觉肯定能 \(A\)
想了一个小时没想出来,一看时间发现不太对,用半个小时把 \(T2\) \(T3\) 的暴力打了,由于一直还想着写 \(T1\) 而且暴力分给的真的多,所以没去想后两题的正解,实际上 \(T3\) 是个 \(O(n^{2})\) 的水 \(dp\) ,血亏。

之后的时间一直挂在 \(T1\) 上,一直在想用各种奇奇怪怪的方法优化分治,从单调栈想到主席树,发现都是假的。。。。。
直到最后才想到 \(dp\) ,但是感觉时间不太对,也没往下想。。。
最后交了一个玄学,两个暴力上去。。。

分数

预估 : \(t1\) \(60pts\) \(+\) \(t2\) \(40pts\) \(+\) \(t3\) \(50pts\) \(=\) \(150pts\)
实际 : \(t1\) \(10pts\) \(+\) \(t2\) \(50pts\) \(+\) \(t3\) \(50pts\) \(=\) \(110pts\)
发现 \(t1\) 挂成 \(10pts\) 了,连暴力也没过。
\(t2\) 本来觉得是能过 \(n,m \le 1000\) 和随机数据的点,结果随机数据的点挂了,莫名过了 \(n,m \le 20000\) 的点???数据水。。。

题解

A. 队长快跑

这题给的式子乍一看就很可以分治。
所以请务必不要往分治上想

这种求子序列的题显然是 \(dp\) 啊。
先离散化。。。
\(f_{i,j}\) 表示选到第 \(i\) 个数,之前选择的最大值是 \(j\) 是最长的序列。。。
写出状态转移方程就很好想了,但是我懒得写了
列出转移方程发现这是个 \(O(n^{3})\) \(dp\) 但是显然可以用线段树优化。。。
最终时间复杂度 \(O(n\) \(logn)\)

code
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+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, a[N], b[N], c[N];
struct TRE{
	int l, r, num, lz;
}t[N<<2];
void built(int l, int r, int p){
	t[p].l=l, t[p].r=r;
	if(l==r) return;
	int mid=(l+r)>>1;
	built(l, mid, p<<1);
	built(mid+1, r, p<<1|1);
}
void pushdown(int p){
	if(!t[p].lz) return;
	t[p<<1].lz+=t[p].lz, t[p<<1|1].lz+=t[p].lz;
	t[p<<1].num+=t[p].lz, t[p<<1|1].num+=t[p].lz;
	t[p].lz=0;
}
int maxn(int l, int r, int p){
	if(l<=t[p].l&&r>=t[p].r) return t[p].num;
	pushdown(p); int mid=(t[p].l+t[p].r)>>1;
	if(l<=mid&&r>mid) return max(maxn(l, r, p<<1), maxn(l, r, p<<1|1));
	if(l<=mid) return maxn(l, r, p<<1);
	return maxn(l, r, p<<1|1);
}
void add(int l, int r, int p, int x){
	if(l<=t[p].l&&r>=t[p].r) { t[p].lz+=x, t[p].num+=x; return; }
	pushdown(p); int mid=(t[p].l+t[p].r)>>1;
	if(l<=mid) add(l, r, p<<1, x);
	if(r>mid) add(l, r, p<<1|1, x);
	t[p].num=max(t[p<<1].num, t[p<<1|1].num);
}
void add1(int x, int y, int p){
	t[p].num=max(t[p].num, y);
	if(t[p].l==x&&t[p].r==x) return;
	pushdown(p); int mid=(t[p].l+t[p].r)>>1;
	if(x<=mid) add1(x, y, p<<1);
	else add1(x, y, p<<1|1);
}
int main(void){
	n=read();
	for(int i=1; i<=n; ++i) c[i]=a[i]=read(), c[i+n]=b[i]=read();
	sort(c+1, c+1+(n<<1)); int len=unique(c+1, c+1+(n<<1))-c-1;
	for(int i=1; i<=n; ++i){
		a[i]=lower_bound(c+1, c+1+len, a[i])-c;
		b[i]=lower_bound(c+1, c+1+len, b[i])-c;
	}
	built(1, ++len, 1);
	for(int i=1; i<=n; ++i){
		if(a[i]<=b[i]){
			int ul=maxn(b[i]+1, len, 1);
			add1(a[i], ul+1, 1);
		}else{
			int ul=maxn(a[i]+1, len, 1);
			add(b[i]+1, a[i], 1, 1);
			add1(a[i], ul+1, 1);
		}
	}
	printf("%d\n", maxn(1, len, 1));
	return 0;
}

B. 影魔

本场比赛最难的题。

既然没有强制在线,那么这一定是离线题
先将所有询问离线下来,存到每一个节点上。
看到题中要求距离小于 \(d\) 的子树,想到以深度为下标建立树状数组, \(dfs\) 时更新树状数组,每个询问直接从树状数组中查询。
但是同一种颜色可能在树状数组中多次出现,怎么办?
显然对于一种颜色,只要在它最浅的位置存下来就可以了,所以在回溯的时候用线段树合并(以颜色为下标,每个节点存这个颜色出现的最浅位置),将同一种颜色较深的点删掉。
还有一个问题,每 \(dfs\) 进入一个节点时,树状数组中都已经包含了不属于当前节点子树的信息,如果这样直接处理,显然答案会偏大,所以 \(dfs\) 一进入该节点,就立即让每个询问的答案减去当前树状数组中对应深度的值即可。。。

。。。就这样吧。。。
。。直接看代码吧。。
。。。异常好写。。。

code
#include<bits/stdc++.h>
using namespace std;
#define d first
#define id second
const int N=2e5+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, m, a[N], dep[N], ans[N], rt[N], tot;
#define lb(x) x&-x
class T{
private :
	int a[N], n;
	inline int sum(int p) { int ans=0; for(; p; p-=lb(p)) ans+=a[p]; return ans; }
public :
	inline void init(int x) { n=x; }
	inline void add(int p, int x) { for(; p<=n; p+=lb(p)) a[p]+=x; }
	inline int query(int l, int r) { return sum(r)-sum(l-1); }
}t1;
#undef lb
struct TRE{
	int l, r, dep, ls, rs;
}t[N<<4];
vector<int> l[N];
vector<pair<int, int> > q[N];
void add(int l, int r, int x, int &p){
	if(!p) p=++tot; t[p]=(TRE){ l, r, dep[x] };
	if(l==r) return; int mid=(l+r)>>1;
	if(a[x]<=mid) add(l, mid, x, t[p].ls);
	else add(mid+1, r, x, t[p].rs);
}
void merge(int &l, int r){
	if(!l) { l=r; return; }
	if(!r) return;
	if(t[l].l==t[l].r){
		if(t[l].dep>=t[r].dep) t1.add(t[l].dep, -1);
		else t1.add(t[r].dep, -1);
		t[l].dep=min(t[l].dep, t[r].dep);
		return;
	}
	merge(t[l].ls, t[r].ls);
	merge(t[l].rs, t[r].rs);
}
void dfs(int u){
	for(pair<int, int > Q : q[u]) ans[Q.id]-=t1.query(dep[u], min(dep[u]+Q.d, n));;
	add(1, n, u, rt[u]); t1.add(dep[u], 1);
	for(int v : l[u]){
		dep[v]=dep[u]+1; dfs(v);
		merge(rt[u], rt[v]);
	}
	for(pair<int, int > Q : q[u]) ans[Q.id]+=t1.query(dep[u], min(dep[u]+Q.d, n));
}
int main(void){
	n=read(); m=read(); t1.init(n);
	int x, y;
	for(int i=1; i<=n; ++i) a[i]=read();
	for(int i=2; i<=n; ++i)	l[read()].push_back(i);
	for(int i=1; i<=m; ++i){
		x=read(), y=read();
		q[x].push_back(make_pair(y, i));
	}
	dep[1]=1; dfs(1);
	for(int i=1; i<=m; ++i) printf("%d\n", ans[i]);
	return 0;
}

C. 抛硬币

本场比赛最水的题。。。
本来是送温暖的,然而我拿了暴力就跑了。。。

一看数据范围就是 \(O(n^{2})\) \(dp\)
\(f_{i,j}\) 表示前 \(i\) 个字符,选了 \(j\) 个的总方案数。
设计出状态转移就很好想了。。。。
主要是我懒得写了。。。

因为是本质不同,所以要去重。。。。
就这样。。

code
#include<bits/stdc++.h>
using namespace std;
const int N=3010, mod=998244353;
char a[N];
int l, vis[N];
long long f[N][N];
int main(void){
	scanf("%s", a+1);
	scanf("%d", &l);
	int len=strlen(a+1);
	for(int i=0; i<=len; ++i) f[i][0]=1;
	for(int i=1; i<=len; ++i){
		int ul=a[i];
		for(int j=1; j<=l; ++j){
			f[i][j]=(f[i-1][j]+f[i-1][j-1])%mod;
			if(vis[ul]) f[i][j]-=f[vis[ul]-1][j-1];
			f[i][j]=(f[i][j]%mod+mod)%mod;
		}
		vis[ul]=i;
	}
	printf("%lld\n", f[len][l]);
	return 0;
}

反思

由于这场比赛实在考的太差了(rk19),所以有必要写下反思。。

看到一个题像是某个算法,想不出来怎么写不行就换个思路(虽然第一印象往往是对的),或先看看其他题,不要一直干一个题(老毛病了,该改改了)。。。

\(dp\) 学的太烂了(那么明显两个 \(dp\) 都看不出来),滚去重学 \(dp\) 吧。

一直不会写就去上个厕所。。。

posted @ 2021-07-14 12:16  Cyber_Tree  阅读(80)  评论(0编辑  收藏  举报