noip模拟14[队长快跑·影魔·抛硬币]
noip模拟14 solutions
太傻啦,太傻啦
也就我自己傻到忘记写暴搜程序,直接丢掉40分啊啊啊气死啦
这次考试算是数据结构专题考试了吧,虽然我也用到了好多,但是只拿到了120pts,最高250pts
第一题算是比较成功的,自己搞出来一个与众不同的转移方程
第二题也还行,用数据结构优化了一下
第三题就比较惨,只有10pts,太着急了,忘记写暴力了
所以呢,以后要是考试,一定一定要先把所有的题看完,找到最好的开题顺序,先把暴力打一遍
·
这些题好像是\(HMOI_2017\)的原题目背景,所以
T1 队长快跑
我在考场上想出来了一个\(O(nklogn)\)的算法,其实还是挺快的,就是中间那个\(k\)在\(1\)到\(n\)之间不等
就直接退化到\(O(n^2logn)\),就直接拿到70pts,大体思路是这样:
·
先对啊\(a[i],b[i]\)进行离散化,因为树状数组只能维护前缀,所以我就吧\(a[i],b[i]\)变换了一下
a[i]=n+1-a[i],b[i]=n+1-b[i];
为啥要这样?因为原题要求\(a[i]>b[i]\)而我们要维护前缀,所以我们就让\(a[i]<b[i]\)
我就开了一颗树状数组,然后维护两个值,\(tr[i],maxn[i]\),\(tr\)表示长度,\(maxn\)表示长度为\(tr[i]\)时的最小的\(a[i]\),这样我们就可以记录,
在\([1,i]\)这个范围内,最大的长度为多少,以及这个长度所对应的最小的\(a[i]\);
然后有一个小问题,我们可能要用到长度较小但是最小的a很大的序列,所以我们就根据此时的\(maxn\)向前跳
找到\([1,maxn[i]-1]\)这样的正确性显然,比maxn大的地方还没有他长,一定不是最优解
所以我那个k就是从这里来的,注意跳的时候,小于此时\(a[i]\)的时候就不要跳了,没啥意义
70pts_树状数组
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=1e5+5;
int n,a[N],b[N];
int lsh[N*2],lh;
struct seg_tree{
int tr[N*2],maxn[N*2];
int ret,rma;
inline int lb(int x){return (x&(-x));}
inline void ins(int x,int v){
for(re i=x;i<=lh+5;i+=lb(i)){
if(v>tr[i]){
tr[i]=v;maxn[i]=x;
}
}
}
inline void query(int x){
ret=0;
for(re i=x;i>0;i-=lb(i)){
if(ret<tr[i]){
ret=tr[i];rma=maxn[i];
}
}
}
}t;
int ans;
signed main(){
scanf("%d",&n);
for(re i=1;i<=n;i++){
scanf("%d%d",&a[i],&b[i]);
lsh[2*i-1]=a[i];lsh[2*i]=b[i];
}
sort(lsh+1,lsh+2*n+1);
lh=unique(lsh+1,lsh+2*n+1)-lsh-1;
for(re i=1;i<=n;i++){
a[i]=lower_bound(lsh+1,lsh+lh+1,a[i])-lsh;
b[i]=lower_bound(lsh+1,lsh+lh+1,b[i])-lsh;
a[i]=lh+2-a[i];
b[i]=lh+2-b[i];
}
for(re i=1;i<=n;i++){
for(re j=b[i]-1;j>0;j=t.rma-1){
t.query(j);
ans=max(ans,t.ret+1);
t.ins(max(t.rma,a[i]),t.ret+1);
if(t.rma<a[i]||t.ret==0)break;
}
t.ins(a[i],1);
}
printf("%d",ans);
}
·
所以下面是不是该讲讲正解啦??
对正解是线段树,对万能的线段树,所以以后我要是再写树状数组,我就把我面前这个电脑吃掉
刚才的那个向前跳的过程,在线段树中就直接一个区间修改+1,完美\(logn\)解决
AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=1e5+5;
int n,a[N],b[N];
int lsh[N*2],lh;
struct XDS{
#define ls x<<1
#define rs x<<1|1
int sum[N*8],maxn[N*8];
int laz[N*8];
inline void pushup(int x){
maxn[x]=max(maxn[ls],maxn[rs]);
}
inline void pushdown(int x,int l,int r){
if(!laz[x])return ;
int mid=l+r>>1;
laz[ls]+=laz[x];maxn[ls]+=laz[x];
laz[rs]+=laz[x];maxn[rs]+=laz[x];
laz[x]=0;
}
void ins1(int x,int l,int r,int pos,int v){
if(l==r){
maxn[x]=max(maxn[x],v);
return ;
}
pushdown(x,l,r);
int mid=l+r>>1;
if(pos<=mid)ins1(ls,l,mid,pos,v);
else ins1(rs,mid+1,r,pos,v);
pushup(x);return ;
}
void ins2(int x,int l,int r,int ql,int qr,int v){
if(ql<=l&&r<=qr){
laz[x]+=v;
maxn[x]+=v;
return ;
}
pushdown(x,l,r);
int mid=l+r>>1;
if(ql<=mid)ins2(ls,l,mid,ql,qr,v);
if(qr>mid)ins2(rs,mid+1,r,ql,qr,v);
pushup(x);return ;
}
int query(int x,int l,int r,int ql,int qr){
if(ql>qr)return 0;
if(ql<=l&&r<=qr)return maxn[x];
pushdown(x,l,r);
int mid=l+r>>1,ret=0;
if(ql<=mid)ret=max(ret,query(ls,l,mid,ql,qr));
if(qr>mid)ret=max(ret,query(rs,mid+1,r,ql,qr));
pushup(x);return ret;
}
#undef ls
#undef rs
}xds;
int ans;
signed main(){
scanf("%d",&n);
for(re i=1;i<=n;i++){
scanf("%d%d",&a[i],&b[i]);
lsh[i*2-1]=a[i];
lsh[i*2]=b[i];
}
sort(lsh+1,lsh+2*n+1);
lh=unique(lsh+1,lsh+2*n+1)-lsh-1;
for(re i=1;i<=n;i++){
a[i]=lower_bound(lsh+1,lsh+lh+1,a[i])-lsh;
b[i]=lower_bound(lsh+1,lsh+lh+1,b[i])-lsh;
if(a[i]<=b[i]){
int tmp=xds.query(1,1,lh,b[i]+1,lh)+1;
ans=max(ans,tmp);
xds.ins1(1,1,lh,a[i],tmp);
}
else{
int tmp=xds.query(1,1,lh,a[i]+1,lh)+1;
ans=max(ans,tmp);
int ji=tmp;
tmp=xds.query(1,1,lh,b[i]+1,a[i])+1;
ans=max(ans,tmp);
xds.ins2(1,1,lh,b[i]+1,a[i],1);
xds.ins1(1,1,lh,a[i],ji);
}
}
printf("%d",ans);
}
这里还有一个事情,最好在线段树中每一个函数都加上\(pushdown和pushup\)我因为这个调了快半个小时
![](https://img2020.cnblogs.com/blog/1755560/202107/1755560-20210714120558461-1284503624.png)
这里是官方题解,注意这里的转移是\(O(n^2)\)的,而\(O(n^3)\)的算法,是不继承上一个i的值,然后暴力循环n
·
T2 影魔
说实话,看到这个题目之后我吓了一跳,虽然说之前我做过这个题,但是我不保证现在还记得
读完题面之后,我直接想到树套树,可是这里有点不一样,他要维护三个值,深度,dfn,种类
于是我第一时间写了dfs序,树上区间问题转为序列区间问题,
再想想,维护三个值,我靠,这要树套树套树啊,不行不行,根本套不下
我就退而求其次,我去那部分分,看到有\(d+dep[u]>最大深度\)的情况,
我就直接一个线段树合并,解决了这种问题,剩下的就直接暴力扫,
考完之后一想,发现,向下搜索一下的话,还有一些子树的深度也很小,也可以用线段树维护出来,
要是那样写一下,起码得有70pts,而我只有40pts
40pts_线段树合并
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=1e5+5;
int n,m,R,c[N];
int to[N*2],nxt[N*2],head[N],rp;
void add_edg(int x,int y){
to[++rp]=y;
nxt[rp]=head[x];
head[x]=rp;
}
int dfn[N],idf[N],dfm[N],cnt;
int rt[N];
int dep[N],des[N];
struct XDS{
int ls[N*80],rs[N*80];
int siz[N*80],sum[N*80];
int seg;
void pushup(int x){
siz[x]=0;sum[x]=0;
if(ls[x])siz[x]+=siz[ls[x]],sum[x]+=sum[ls[x]];
if(rs[x])siz[x]+=siz[rs[x]],sum[x]+=sum[rs[x]];
}
void ins(int &x,int l,int r,int pos){
if(!x)x=++seg;
if(l==r){
siz[x]++;sum[x]=1;
return ;
}
int mid=l+r>>1;
if(pos<=mid)ins(ls[x],l,mid,pos);
else ins(rs[x],mid+1,r,pos);
pushup(x);return ;
}
void merge(int &x,int y,int l,int r){
if(!y)return ;
if(!x)x=++seg;
siz[x]+=siz[y];
if(l==r){
sum[x]=1;
return ;
}
int mid=l+r>>1;
merge(ls[x],ls[y],l,mid);
merge(rs[x],rs[y],mid+1,r);
pushup(x);return ;
}
}xds;
void dfs_first(int x){
dfn[x]=++cnt;idf[cnt]=x;
des[x]=dep[x];
for(re i=head[x];i;i=nxt[i]){
int y=to[i];
dep[y]=dep[x]+1;
dfs_first(y);
des[x]=max(des[x],des[y]);
xds.merge(rt[x],rt[y],1,R);
}
xds.ins(rt[x],1,R,c[x]);
dfm[x]=cnt;
}
bool vis[N];
int ans,ji[N],tot;
signed main(){
//freopen("b.ans","w",stdout);
scanf("%d%d",&n,&m);
for(re i=1;i<=n;i++)scanf("%d",&c[i]),R=max(R,c[i]);
for(re i=2,x;i<=n;i++){
scanf("%d",&x);
add_edg(x,i);
}
dfs_first(1);
for(re i=1,u,d;i<=m;i++){
scanf("%d%d",&u,&d);
if(d>=des[u]-dep[u])printf("%d\n",xds.sum[rt[u]]);
else{
tot=0;ans=0;
for(re j=dfn[u];j<=dfm[u];j++){
int x=idf[j];
if(dep[x]<=dep[u]+d){
if(!vis[c[x]])ans++,ji[++tot]=c[x];
vis[c[x]]=true;
}
}
for(re j=1;j<=tot;j++)vis[ji[j]]=false;
printf("%d\n",ans);
}
}
}
·
于是乎我马上就要说正解啦
其实要是找到一个规律之后,就很简单了
比如说,现在我们有一颗树,每个节点都有一个种类,要求这颗树的种类数
我们可以将每种种类放到一起,按照dfn排序,所有这个种类的点在树上的权值+1,相邻点的lca-1
这样我们直接求这个树的权值和就是这个树的种类数,这样比原来就好统计多了,
当然,对于每一个节点的子树内的种类数就是这个子树内的权值和
所以我们对这个题的做法就是主席树,每个深度为一个历史版本
统计的时候,就直接在相应的深度的树内查询对应dfs序区间上的权值和就好了
首先对所有节点按照深度排序,插入时,用set来维护这个节点在这个种类内的前趋,后继
设前趋为\(pre\) ,后继为\(nxt\)
i +1
lca(pre,i) -1
lca(nxt,i) -1
lca(nxt,pre) +1
这样就维护好了上面所说的那种权值
还有一个需要注意的地方,此时的主席树,不可以每次都继承上一个版本的信息
因为在一个版本中可能会插入好多值,我的做法是,在向下搜索时,将儿子赋值为0
AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=1e5+5;
int n,m;
struct POT{
int c,dep,dfn,dfm,id;
bool operator < (POT x)const{
return x.dfn>dfn;
}
}sca[N];
set<POT> st[N];
int cnt;
int to[N*2],nxt[N*2],head[N],rp;
void add_edg(int x,int y){
to[++rp]=y;
nxt[rp]=head[x];
head[x]=rp;
}
int fa[N][25];
int dp[N],ds[N],dn[N],dm[N];
void dfs_fir(int x){
sca[x].dfn=++cnt;
ds[x]=sca[x].dep;
for(re i=head[x];i;i=nxt[i]){
int y=to[i];
sca[y].dep=sca[x].dep+1;
fa[y][0]=x;
for(re i=1;i<=20;i++)fa[y][i]=fa[fa[y][i-1]][i-1];
dfs_fir(y);
ds[x]=max(ds[x],ds[y]);
}
sca[x].dfm=cnt;
}
int LCA(int x,int y){
if(dp[x]<dp[y])swap(x,y);
for(re i=20;i>=0;i--)
if(dp[fa[x][i]]>=dp[y])
x=fa[x][i];
if(x==y)return x;
for(re i=20;i>=0;i--)
if(fa[x][i]!=fa[y][i])
x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
bool cmp(POT x,POT y){
if(x.dep!=y.dep)return x.dep<y.dep;
return x.dfn<y.dfn;
}
int rt[N];
struct pst_tree{
int ls[N*80],rs[N*80];
int siz[N*80];
int seg;
inline void pushup(int pre,int x){
siz[x]=0;
if(ls[x])siz[x]+=siz[ls[x]];
if(rs[x])siz[x]+=siz[rs[x]];
return ;
}
inline void ins(int pre,int &x,int l,int r,int pos,int v){
if(!x){x=++seg;siz[x]=siz[pre];ls[x]=ls[pre];rs[x]=rs[pre];}
if(l==r){siz[x]+=v;return ;}
int mid=l+r>>1;
if(pos<=mid){
if(ls[x]==ls[pre])ls[x]=0;
ins(ls[pre],ls[x],l,mid,pos,v);
}
else {
if(rs[x]==rs[pre])rs[x]=0;
ins(rs[pre],rs[x],mid+1,r,pos,v);
}
pushup(pre,x);return ;
}
inline int query(int x,int l,int r,int ql,int qr){
if(!x)return 0;
if(ql<=l&&r<=qr){return siz[x];}
int mid=l+r>>1,ret=0;
if(ql<=mid)ret+=query(ls[x],l,mid,ql,qr);
if(qr>mid)ret+=query(rs[x],mid+1,r,ql,qr);
return ret;
}
}pst;
signed main(){
scanf("%d%d",&n,&m);
for(re i=1;i<=n;i++)scanf("%d",&sca[i].c),sca[i].id=i;
for(re i=2,x;i<=n;i++){
scanf("%d",&x);
add_edg(x,i);
}
sca[1].dep=1;
dfs_fir(1);
for(re i=1;i<=n;i++){
dp[i]=sca[i].dep;
dn[i]=sca[i].dfn;
dm[i]=sca[i].dfm;
}
sort(sca+1,sca+n+1,cmp);
for(re i=1;i<=n;i++){
pst.ins(rt[sca[i].dep-1],rt[sca[i].dep],1,n,sca[i].dfn,1);
st[sca[i].c].insert(sca[i]);
int lca;
POT nu;nu.id=0;
POT pre=st[sca[i].c].find(sca[i])==st[sca[i].c].begin()?nu:*--st[sca[i].c].find(sca[i]);
POT nxt=((++st[sca[i].c].find(sca[i]))==st[sca[i].c].end())?nu:*++st[sca[i].c].find(sca[i]);
if(pre.id){
lca=LCA(sca[i].id,pre.id);
pst.ins(rt[sca[i].dep-1],rt[sca[i].dep],1,n,dn[lca],-1);
}
if(nxt.id){
lca=LCA(sca[i].id,nxt.id);
pst.ins(rt[sca[i].dep-1],rt[sca[i].dep],1,n,dn[lca],-1);
}
if(pre.id&&nxt.id){
lca=LCA(pre.id,nxt.id);
pst.ins(rt[sca[i].dep-1],rt[sca[i].dep],1,n,dn[lca],1);
}
}
for(re i=1,u,d;i<=m;i++){
scanf("%d%d",&u,&d);
printf("%d\n",pst.query(rt[min(dp[u]+d,ds[u])],1,n,dn[u],dm[u]));
}
}
·
T3 抛硬币
这个好像是这次考试中最最最简单的题了
主要是这数据范围非常的迷人 3000 , 我还是不敢用\(O(n^2)\)的算法
正解就是非常非常慢的\(O(n^2)\)算法
直接看官方题解吧,这次题解给的极其善良
![](https://img2020.cnblogs.com/blog/1755560/202107/1755560-20210714120802407-1752187943.png)
AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=3e3+5;
const ll mod=998244353;
char a[N];
int l,len;
ll dp[N][N],las[30];
ll ans;
signed main(){
scanf("%s",a+1);
len=strlen(a+1);
scanf("%d",&l);
dp[0][0]=1;
for(re i=1;i<=len;i++){
dp[i][0]=1;
for(re j=1;j<=l&&j<=i;j++){
dp[i][j]=(dp[i-1][j-1]+dp[i-1][j])%mod;
if(las[a[i]-'a']!=0)dp[i][j]=(dp[i][j]+mod*101-dp[las[a[i]-'a']-1][j-1])%mod;
}
las[a[i]-'a']=i;
}
printf("%lld",dp[len][l]);
}
这里还有一个毒瘤的地方,
一般来说,\(dp[len][l]\)比任何的\(dp[i][l]\)都大,所以求取max是没有问题的
但是这里取模啦啊啊啊啊啊啊啊