noip模拟14

这场比赛又因为数组开小暴毙,前两道挂了一共 \(40+20=60\) 分,\(t3\) 由于过于自信口胡 \(dp\) +复杂度算错没打哈希暴力又白丢 \(40\) 分(再也找不出 \(t3\) 这么低的了……),险些掉出前20

pic.PNG


\(\color{white}{zkx\ AK\ IOI}\)

A. 队长快跑

首先第一感觉可能是树状数组优化 \(\mathrm{dp}\) 的那一类套路
首先声明一下,我一般不习惯 \(b_i< a_j\) 这样的条件,所以倒序枚举以便把限制条件翻转过来
开始想状态,一通尝试后发现一维行不通,理性分析一下是应为 \(b\) 数组的单调性不确定,若果当前的 a 比上一个的 \(b\) 大,但并不代表比整个序列里面所有的 \(b\) 都大(进一步理性分析一下为什么没有 \(n^2\) 的部分分
那就来两维,设 \(f[i][j]\) 表示以 \(i\) 开始的且序列里 \(b\) 的最大值为 \(j\) 的序列最大长度
转移方程式:

\[f[i][max(b[i],k)]=max\{f[j][k]+1\} \]

好的,这样60分轻松到手
再考虑优化,这个转移方程式比较特殊,是由 \(max\) 引起的,那么想要把这个拆掉,可以分类讨论 \(b_i\)\(k\) 的大小关系,这样要么转化成后面多个值的最大值更新到 \(b_i\) 这一个值上,要么相当于把自己的值 \(+1\),这需要去加加,区间最大值,单点更新,可以用一棵线段树来维护

代码实现
#include<bits/stdc++.h>
using namespace std;
int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=x*10+ch-48;
		ch=getchar();	
	}
	return x*f;
}
const int maxn=1e6+5;
int n,a[maxn],b[maxn],lsh[maxn],tot,cnt,f[205][505],ans;
struct Seg{
	int l,r,mx,lazy;	
}t[maxn*4];
void build(int p,int l,int r){
	t[p].l=l;
	t[p].r=r;
	if(l==r)return ;
	int mid=l+r>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	return ;	
}
void update(int p){
	t[p].mx=max(t[p<<1].mx,t[p<<1|1].mx);
	return ;	
}
void spread(int p){
	t[p<<1].mx+=t[p].lazy;
	t[p<<1|1].mx+=t[p].lazy;
	t[p<<1].lazy+=t[p].lazy;
	t[p<<1|1].lazy+=t[p].lazy;
	update(p);
	t[p].lazy=0;
	return ;	
}
void change(int p,int pos,int val){
	if(t[p].l==t[p].r&&t[p].l==pos){
		t[p].mx=max(t[p].mx,val);
		return ;
	}
	if(t[p].lazy)spread(p);
	int mid=t[p].l+t[p].r>>1;
	if(pos<=mid)change(p<<1,pos,val);
	else change(p<<1|1,pos,val);
	update(p);
	return ;
}
void add(int p,int l,int r){
	if(t[p].l>=l&&t[p].r<=r){
		t[p].mx++;
		t[p].lazy++;
		return ;
	}
	if(t[p].lazy)spread(p);
	int mid=t[p].l+t[p].r>>1;
	if(l<=mid)add(p<<1,l,r);
	if(r>mid)add(p<<1|1,l,r);
	update(p);
	return ;
}
int ask(int p,int l){
	if(l==0)return 0;
	if(t[p].r<=l)return t[p].mx;
	if(t[p].lazy)spread(p);
	int mid=t[p].l+t[p].r>>1;
	if(l<=mid)return ask(p<<1,l);
	return max(t[p<<1].mx,ask(p<<1|1,l));
}
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
		b[i]=read();
		lsh[++tot]=a[i];
		lsh[++tot]=b[i];
	}
	sort(lsh+1,lsh+tot+1);
	cnt=unique(lsh+1,lsh+tot+1)-lsh-1;
	for(int i=1;i<=n;i++){
		a[i]=lower_bound(lsh+1,lsh+cnt+1,a[i])-lsh;
		b[i]=lower_bound(lsh+1,lsh+cnt+1,b[i])-lsh;
	}
	if(n<=200){
		for(int i=n;i>=1;i--){
			f[i][b[i]]=1;
			for(int j=n;j>=i+1;j--){
				for(int k=1;k<a[i];k++){
					f[i][max(b[i],k)]=max(f[i][max(b[i],k)],f[j][k]+1);
					ans=max(ans,f[i][max(b[i],k)]);
				}
			}	
		}
		cout<<ans;
		return 0;
	}
	build(1,1,cnt);
	for(int i=n;i>=1;i--){
		if(b[i]>=a[i]){
			int x=ask(1,a[i]-1)+1;
			change(1,b[i],x);
		}
		else{
			add(1,b[i],a[i]-1);
			int x=ask(1,b[i]-1)+1;
			change(1,b[i],x);
		}
//		cout<<t[1].mx<<endl;
	}
	cout<<t[1].mx;
	return 0;
}

B. 影魔

开题 cyh 巨佬惊呼原题,我一看题目想着又完了,做过是做过,但是连题是啥都不记得了,直接的是个什么阴间数据结构,岂不是又要完~
吸取上次超级树的教训,还是先看别的题吧……
万万没想到,考完试一看才发现这套题套的是背景,和以前的题没啥关系……

这道题可以每个点开个 \(vector\) 把这个点的所有询问记录下来,开一个树状数组,以深度为下标,记录当前深度的颜色个数
那么对于每个询问相当于询问一个深度范围内的颜色个数,树状数组上前缀和即可
然鹅会有两个问题,一是不在当前子树内的其他点在相同深度的贡献,这个需要dfs到当前点时先减去深度范围内的颜色个数(因为此时树状数组内保存的是子树外其他点的信息);
二是一个子树内相同颜色会算重
这是可以采用线段树合并的思想,每个节点以颜色为下标开一棵线段树,每次回溯将儿子信息合并到父亲上
对于线段树每个节点保存这种颜色最浅深度的多少,更新时去min
合并的时候,如果父亲和儿子都拥有这种颜色,那么可以把儿子记录的深度更深的地方的个数-1,相当于一起合并到深度较浅的位置上

update: 这道题其实有同一复杂度的在线做法
一个颜色算重会在它们的 \(lca\) 处体现,因此可以 \(dfs\) 序维护前驱后继的方式动态求出当前子树区间的答案
由于有深度的限制,那么按照深度插入每个点,建立一棵主席树保存所有版本的信息,由于此时子树内更深的点还没有加入,维护出的信息是符合要求的

代码实现
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
const int maxm=2e5+5;
int n,m,hd[maxn],cnt,val[maxn],dep[maxn],x,y,w,c[maxn],ans[maxn];
vector<pair<int,int> >ask[maxn];
int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=x*10+ch-48;
		ch=getchar();	
	}
	return x*f;
}
struct Edge{
	int nxt,to;	
}edge[maxm];
void add(int u,int v){
	edge[++cnt].nxt=hd[u];
	edge[cnt].to=v;
	hd[u]=cnt;
	return ;
}
namespace p2{
	struct Seg{
		int son[2],l,r,dep;
	}t[maxn*30];
	int ans[maxn],tot,root[maxn];
	void plus(int x,int y){
		for(;x<=n;x+=x&-x)c[x]+=y;
		return ;
	}
	int ques(int x){
		int ans=0;
		for(;x;x-=x&-x)ans+=c[x];
		return ans;	
	}
	int change(int pos,int l,int r,int deep){
		int p=++tot;
		t[p].l=l;
		t[p].r=r;
		if(l==r&&l==pos){
			plus(deep,1);
			t[p].dep=deep;
			return p;	
		}
		int mid=l+r>>1;
		if(pos<=mid)t[p].son[0]=change(pos,l,mid,deep);
		else t[p].son[1]=change(pos,mid+1,r,deep);
		return p;
	}
	int merge(int p,int q){
		if((!p)||(!q))return p+q;
		if(t[p].l==t[p].r){
			plus(max(t[p].dep,t[q].dep),-1);
			t[p].dep=min(t[p].dep,t[q].dep);
			return p;
		}
		if(t[p].son[0]||t[q].son[0])t[p].son[0]=merge(t[p].son[0],t[q].son[0]);
		if(t[p].son[1]||t[q].son[1])t[p].son[1]=merge(t[p].son[1],t[q].son[1]);
		return p;
	}
	void dfs(int u){
		for(int i=0;i<ask[u].size();i++){
			ans[ask[u][i].first]-=ques(min(n,dep[u]+ask[u][i].second))-ques(dep[u]-1);
		}
		root[u]=change(val[u],1,n,dep[u]);
		for(int i=hd[u];i;i=edge[i].nxt){
			int v=edge[i].to;
			dep[v]=dep[u]+1;
			dfs(v);
			root[u]=merge(root[u],root[v]);
		}
		for(int i=0;i<ask[u].size();i++){
			ans[ask[u][i].first]+=ques(min(n,dep[u]+ask[u][i].second))-ques(dep[u]-1);	
		}
		return ;
	}
	void start(){
		dep[1]=1;
		dfs(1);
		for(int i=1;i<=m;i++){
			printf("%d\n",ans[i]);
		}
		return ;
	}
}
int main(){
//	freopen("1.out","w",stdout);
	n=read();
	m=read();
	for(int i=1;i<=n;i++){
		val[i]=read();	
	}
	for(int i=2;i<=n;i++){
		x=read();
		add(x,i);
	}
	for(int i=1;i<=m;i++){
		x=read();
		w=read();
		ask[x].push_back(make_pair(i,w));
	}
	p2::start();
	return 0;
}

C. 抛硬币

万万没想到 \(t3\) 居然是最简单的题……
一开始也想的是 \(dp\),但是不知道为什么想容斥一下于是开始统计本质相同的串的个数,然后设计状态的时候还设计成了以 \(i\) 结尾的序列,弄了一个小时也没打好补丁
正解是状态直接设计成 \(f[i][j]\) 表示结尾在 \(i\) 及之前长度为 \(j\) 的子序列的个数
转移:

\[f[i][j]=f[i-1][j]+f[i-1][j-1]-f[pre[c[i]]-1][j-1] \]

先解释一下:首先之前位置结束的序列直接统计进来,然后之前位置长度 \(j-1\) 的再拼上 \(i\) 位置的字符构成新序列
但是拼上后可能会和原来 \(i-1\) 位置上的序列相同
那么就统计一下以上一个当前字符结尾的子序列个数减去即可

代码实现
#include<bits/stdc++.h>
using namespace std;
const int maxn=3005;
const int mod=998244353;
char c[maxn];
int len,n,last[130],f[maxn][maxn],ans,cnt,l[maxn];
bool vis[maxn];
signed main(){
	scanf("%s",c+1);
	cin>>len;
	n=strlen(c+1);
	for(int i=1;i<=n;i++){
		l[i]=last[c[i]];
		last[c[i]]=i;
		if(!vis[c[i]])cnt++,vis[c[i]]=1;
		f[i][1]=cnt;
		if(!l[i])l[i]++;
//		cout<<l[i]<<" ";
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=len;j++){
			if(!f[i][j])f[i][j]=(((f[i][j]+f[i-1][j])%mod+f[i-1][j-1])%mod-f[l[i]-1][j-1]+mod)%mod;	
		}
	}
	cout<<(f[n][len]%mod+mod)%mod;
	return 0;
}
posted @ 2021-07-14 09:27  y_cx  阅读(41)  评论(0编辑  收藏  举报