noip模拟14
这场比赛又因为数组开小暴毙,前两道挂了一共 分, 由于过于自信口胡 +复杂度算错没打哈希暴力又白丢 分(再也找不出 这么低的了……),险些掉出前20
A. 队长快跑
首先第一感觉可能是树状数组优化 的那一类套路
首先声明一下,我一般不习惯 这样的条件,所以倒序枚举以便把限制条件翻转过来
开始想状态,一通尝试后发现一维行不通,理性分析一下是应为 数组的单调性不确定,若果当前的 a 比上一个的 大,但并不代表比整个序列里面所有的 都大(进一步理性分析一下为什么没有 的部分分)
那就来两维,设 表示以 开始的且序列里 的最大值为 的序列最大长度
转移方程式:
好的,这样60分轻松到手
再考虑优化,这个转移方程式比较特殊,是由 引起的,那么想要把这个拆掉,可以分类讨论 与 的大小关系,这样要么转化成后面多个值的最大值更新到 这一个值上,要么相当于把自己的值 ,这需要去加加,区间最大值,单点更新,可以用一棵线段树来维护
代码实现
#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 巨佬惊呼原题,我一看题目想着又完了,做过是做过,但是连题是啥都不记得了,直接的是个什么阴间数据结构,岂不是又要完~
吸取上次超级树的教训,还是先看别的题吧……
万万没想到,考完试一看才发现这套题套的是背景,和以前的题没啥关系……
这道题可以每个点开个 把这个点的所有询问记录下来,开一个树状数组,以深度为下标,记录当前深度的颜色个数
那么对于每个询问相当于询问一个深度范围内的颜色个数,树状数组上前缀和即可
然鹅会有两个问题,一是不在当前子树内的其他点在相同深度的贡献,这个需要dfs到当前点时先减去深度范围内的颜色个数(因为此时树状数组内保存的是子树外其他点的信息);
二是一个子树内相同颜色会算重
这是可以采用线段树合并的思想,每个节点以颜色为下标开一棵线段树,每次回溯将儿子信息合并到父亲上
对于线段树每个节点保存这种颜色最浅深度的多少,更新时去min
合并的时候,如果父亲和儿子都拥有这种颜色,那么可以把儿子记录的深度更深的地方的个数-1,相当于一起合并到深度较浅的位置上
update: 这道题其实有同一复杂度的在线做法
一个颜色算重会在它们的 处体现,因此可以 序维护前驱后继的方式动态求出当前子树区间的答案
由于有深度的限制,那么按照深度插入每个点,建立一棵主席树保存所有版本的信息,由于此时子树内更深的点还没有加入,维护出的信息是符合要求的
代码实现
#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. 抛硬币
万万没想到 居然是最简单的题……
一开始也想的是 ,但是不知道为什么想容斥一下于是开始统计本质相同的串的个数,然后设计状态的时候还设计成了以 结尾的序列,弄了一个小时也没打好补丁
正解是状态直接设计成 表示结尾在 及之前长度为 的子序列的个数
转移:
先解释一下:首先之前位置结束的序列直接统计进来,然后之前位置长度 的再拼上 位置的字符构成新序列
但是拼上后可能会和原来 位置上的序列相同
那么就统计一下以上一个当前字符结尾的子序列个数减去即可
代码实现
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】