考试过程:
刚看完题,发现T1是个类lis 问题,但要求$O(nlogn)$,应该是个数据结构优化dp,T2应该是个数据结构,T3是个字符串?没有匹配,不会是后缀数组吧,这是NOIP模拟啊,可能是个dp。
开始分析T1,一开始想错了,以为只要一个树状数组就可以,是个全场切的sb题,后来发现过不了样例,后来发现是假的,然后默默的开始打$O(n^3)$暴力dp,半个小时后过了两个样例,60应该是稳了,怕后面题时间不够,就先看了后两题,第二题除了$O(n^2)$暴力啥也不会,T3觉得dp好像很可做,然后开始推,但一直想不到怎么去重,浪费了好多时间,最后T2打了暴力,T3根本没动。
觉得100+还是挺稳的,点开成绩,woc T1 CE!!! T2 WA0 艹,为什么啊。
我为什么要手贱手打max阿,还不编译。T2测试点分治 没return 0; 我@%$^*#&%^@#......
感觉考试的时候不太认真,不然也不会犯这么sb的错误,本来拿分就拿不过别人,还犯这种低级错误,感觉那天白跟教练谈了,真tm是不长记性。
题解:
T1 队长快跑
线段树优化dp,考场上打的是$O(n^3)$ 的,然后觉得优化空间不大,就没有往细里想,其实暴力完全可以做到$O(n^2)$。
然后线段树优化就比较显然了然而我蒟蒻就是想不到啊。
设$dp_{i,j}$表示处理完$i$个水晶并且当$a[i]$最小值为$j$的最优解
那么我们进行分类讨论
如果$a[i]<b[i]$ 那么$dp_{i,a[i]}=max{dp_{i,j}}+1,j>=b[i]+1,j<=MAX$
否则$dp_{i,a[i]}=max{dp_{i,a[i]}},j>=a[i]+1,j<=MAX $
$dp_{i,j}=dp_{i,j-1}+1,j>b[i],j<=a[i]$
那么我们只要把第二维放到线段树上维护一下就好了
这样就只需要一颗支持区间修改,单点修改,区间查询就好了。
1 #include<bits/stdc++.h> 2 #define lowbit(x) x&(-x) 3 using namespace std; 4 const int N=1e5+10; 5 int a[N],b[N]; 6 int c[N<<1]; 7 int n; 8 int t=0,len,q_mx; 9 struct SegmentTree{ 10 int l,r,mx,lazy; 11 }tr[N<<3]; 12 int max(int a,int b){ 13 return a>b?a:b; 14 } 15 void down(int p){ 16 if(!tr[p].lazy) return ; 17 tr[p<<1].lazy+=tr[p].lazy; 18 tr[p<<1|1].lazy+=tr[p].lazy; 19 tr[p<<1].mx+=tr[p].lazy; 20 tr[p<<1|1].mx+=tr[p].lazy; 21 tr[p].lazy=0; 22 } 23 24 void build(int p,int l,int r){ 25 tr[p].l=l,tr[p].r=r; 26 if(l==r){ 27 tr[p].mx=0; 28 return ; 29 } 30 int mid=(l+r)>>1; 31 build(p<<1,l,mid); 32 build(p<<1|1,mid+1,r); 33 tr[p].mx=max(tr[p<<1].mx,tr[p<<1|1].mx); 34 } 35 void update(int p,int ll,int rr,int l,int r,int val){ 36 if(ll<=l&&r<=rr){ 37 tr[p].lazy+=val; 38 tr[p].mx+=val; 39 return ; 40 } 41 down(p); 42 int mid=(tr[p].l+tr[p].r)>>1; 43 if(ll<=mid) update(p<<1,ll,rr,l,mid,val); 44 if(rr>mid) update(p<<1|1,ll,rr,mid+1,r,val); 45 tr[p].mx=max(tr[p<<1].mx,tr[p<<1|1].mx); 46 } 47 void modify(int p,int l,int r,int pos,int val){ 48 if(l==r){//cout<<val<<endl; 49 tr[p].mx=max(tr[p].mx,val); 50 // cout<<tr[p].mx<<endl; 51 return ; 52 } 53 down(p); 54 int mid=(tr[p].l+tr[p].r)>>1; 55 if(pos<=mid) modify(p<<1,l,mid,pos,val); 56 else modify(p<<1|1,mid+1,r,pos,val); 57 tr[p].mx=max(tr[p<<1].mx,tr[p<<1|1].mx); 58 } 59 void query(int p,int ll,int rr,int l,int r){ 60 if(ll<=l&&r<=rr){//cout<<"tr=="<<tr[p].mx<<" mx=="<<q_mx<<endl; 61 q_mx=max(tr[p].mx,q_mx); 62 return ; 63 } 64 down(p); 65 int mid=(tr[p].l+tr[p].r)>>1; 66 if(ll<=mid) query(p<<1,ll,rr,l,mid); 67 if(rr>mid) query(p<<1|1,ll,rr,mid+1,r); 68 } 69 int main(){ 70 // freopen("leader.in","r",stdin); 71 // freopen("leader.out","w",stdout); 72 scanf("%d",&n); 73 int Max=0; 74 for(int i=1;i<=n;++i){ 75 scanf("%d%d",&a[i],&b[i]); 76 c[++t]=a[i]; 77 c[++t]=b[i]; 78 Max=max(Max,a[i]); 79 } 80 sort(c+1,c+t+1); 81 len=unique(c+1,c+t+1)-c-1; 82 for(int i=1;i<=n;++i) { 83 a[i]=lower_bound(c+1,c+len+1,a[i])-c; 84 b[i]=lower_bound(c+1,c+len+1,b[i])-c; 85 } 86 // for(int i=1;i<=n;++i) cout<<a[i]<<" "; 87 // cout<<endl; 88 // for(int i=1;i<=n;++i) cout<<b[i]<<" "; 89 // for(int i=1;i<=n;++i) cout<<a[i]<<" "<<b[i]<<endl; 90 build(1,1,len); 91 for(int i=1;i<=n;++i){ 92 q_mx=0; 93 if(a[i]<=b[i]){ 94 query(1,b[i]+1,len,1,len); 95 modify(1,1,len,a[i],q_mx+1); 96 } 97 else{ 98 query(1,a[i]+1,len,1,len); 99 update(1,b[i]+1,a[i],1,len,1); 100 modify(1,1,len,a[i],q_mx+1); 101 } 102 // cout<<"q_mx=="<<q_mx<<endl; 103 } 104 printf("%d",tr[1].mx); 105 }
T2 影魔
大神数据结构题
不难想到的是对于每一个节点开线段树但我就是想不到。
但是要维护什么,就看它要干什么,我们考虑把颜色和深度联系到一起,考虑维护在每颗子树内,每种颜色出现的最小深度,但是这样我们处理不了询问,所以我们再维护在每个子树内以每个深度为最小深度的颜色种数,这样我们把第二棵线段树可持久化就能做到询问深度为的以内的询问了。
但是这样空间会爆,所以两棵线段树都要动态开点。
显然这两个信息都满足可加性,只要在dfs的过程中向上合并就好了。
还有要注意的一点就是在合并第一棵线段树是会影响到第二棵线段树的信息,即在合并第一棵线段树时把深度较大的信息在相应颜色的第二棵线段树里把贡献减掉,细节还是蛮多的,蒟蒻博主打了一个下午,主要还是对动态开点线段树,可持久化线段树理解的不透彻。
复杂度$O(nlogn)$,但常数超级大,在线的。
强烈谴责两个log过的
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=1e5+10; 4 int tot; 5 int vis[N],d[N],c[N]; 6 int fa[N]; 7 int first[N],to[N<<1],nex[N<<1],cnt; 8 int root1[N*60],root2[N*60]; 9 struct Seg1{ 10 int l,r,val; 11 }tr[N*120]; 12 int n; 13 void build(int &x,int l,int r,int pos,int val){//root, l,r fanwei ,pos weizhi ,val quanzhi 14 x=++tot; 15 if(l==r){ 16 tr[x].val=val; 17 return ; 18 } 19 int mid=(l+r)>>1; 20 if(pos<=mid) build(tr[x].l,l,mid,pos,val); 21 else build(tr[x].r,mid+1,r,pos,val); 22 } 23 24 void update(int &x,int l,int r,int pos,int val){ 25 ++tot; 26 tr[tot]=tr[x]; 27 x=tot; 28 tr[x].val+=val; 29 if(l==r) return ; 30 int mid=(l+r)>>1; 31 if(pos<=mid) update(tr[x].l,l,mid,pos,val); 32 else update(tr[x].r,mid+1,r,pos,val); 33 } 34 35 int merge1(int x,int y,int l,int r,int p){//col xiabiao val shendu 36 if(!x||!y) return x+y;//chuan p yuanyinshi yaodui tr2 xiugai yi p weigen 37 int rt=++tot; 38 if(l==r){ 39 tr[rt].val=min(tr[x].val,tr[y].val); 40 update(root2[p],1,n,max(tr[x].val,tr[y].val),-1); 41 return rt; 42 } 43 int mid=(l+r)>>1; 44 tr[rt].l=merge1(tr[x].l,tr[y].l,l,mid,p); 45 tr[rt].r=merge1(tr[x].r,tr[y].r,mid+1,r,p); 46 return rt; 47 } 48 int merge2(int x,int y,int l,int r){//dep xiabiao val col 49 if(!x||!y) return x+y; 50 int rt=++tot; 51 /*if(l==r){ 52 tr[rt].val=tr[x].val+tr[y].val; 53 return rt; 54 }*/ 55 tr[rt].val=tr[x].val+tr[y].val; 56 int mid=(l+r)>>1; 57 tr[rt].l=merge2(tr[x].l,tr[y].l,l,mid); 58 tr[rt].r=merge2(tr[x].r,tr[y].r,mid+1,r); 59 return rt; 60 } 61 int query(int p,int l,int r,int ll,int rr){ 62 if(ll<=l&&r<=rr) return tr[p].val; 63 int ans=0,mid=(l+r)>>1; 64 if(ll<=mid) ans+=query(tr[p].l,l,mid,ll,rr); 65 if(rr>mid) ans+=query(tr[p].r,mid+1,r,ll,rr); 66 return ans; 67 } 68 69 void add(int a,int b){ 70 to[++cnt]=b,nex[cnt]=first[a],first[a]=cnt; 71 } 72 void pre_dfs(int x,int fa){//cout<<x<<endl; 73 build(root1[x],1,n,c[x],d[x]); 74 update(root2[x],1,n,d[x],1); 75 for(int i=first[x];i;i=nex[i]){ 76 int y=to[i]; 77 if(y==fa) continue; 78 d[y]=d[x]+1; 79 pre_dfs(y,x); 80 root1[x]=merge1(root1[x],root1[y],1,n,x); 81 root2[x]=merge2(root2[x],root2[y],1,n); 82 } 83 } 84 85 int main(){ 86 int m; 87 scanf("%d%d",&n,&m); 88 int Max=0; 89 for(int i=1;i<=n;++i) {scanf("%d",&c[i]);Max=max(Max,c[i]);} 90 for(int i=1;i<n;++i){ 91 scanf("%d",&fa[i]); 92 add(fa[i],i+1); 93 } 94 d[1]=1; 95 pre_dfs(1,0); 96 // cout<<endl; 97 // cout<<tot<<endl; 98 // for(int i=1;i<=n;++i) cout<<root1[i]<<" "; 99 // cout<<endl; 100 // for(int i=1;i<=n;++i) cout<<root2[i]<<" "; 101 for(int i=1;i<=m;++i){ 102 int x,dis; 103 scanf("%d%d",&x,&dis); 104 int ans=query(root2[x],1,n,d[x],d[x]+dis); 105 printf("%d ",ans); 106 } 107 }
T3 抛硬币
其实还是不太难的dp,不难想到设$dp[i][j]$表示处理完位置i且长度为j的方案数
很显然的转移:
$dp[i][j]=dp[i-1][j]+dp[i-1][j-1]$,但是有好多重的,所以想怎么去重
显然一个字符只会和他一样的字符产生重复,所以我们预处理每个字符和他相同的前驱,设其为$p[i]$,那么在减掉发$dp[p[i]-1][j-1]$就好了。
需要稍预处理,细节见代码。
1 #include<bits/stdc++.h> 2 #define cout cerr 3 #define int long long 4 using namespace std; 5 const int N=3005; 6 const int mod=998244353; 7 int f[N][N]; 8 char s[N]; 9 int p[N]; 10 int sum[N],vis[N]; 11 signed main(){ 12 int l; 13 scanf("%s",s+1); 14 scanf("%lld",&l); 15 int n=strlen(s+1); 16 for(int i=1;i<=n;++i){ 17 sum[i]=sum[i-1]; 18 if(!vis[s[i]-'a']) sum[i]++; 19 vis[s[i]-'a']=1; 20 } 21 for(int i=1;i<=n;++i) f[i][1]=sum[i]; 22 for(int i=1;i<=n;++i) p[i]=1; 23 for(int i=1;i<=n;++i){ 24 for(int j=i-1;j>=1;--j){ 25 if(s[j]==s[i]) {p[i]=j;break;} 26 } 27 } 28 // for(int i=1;i<=n;) 29 // for(int i=1;i<=n;++i) cout<<sum[i]<<" "; 30 // for(int i=1;i<=n;++i) cout<<p[i]<<" "; 31 for(int i=2;i<=n;++i){ 32 for(int j=2;j<=min(l,i);++j){ 33 f[i][j]=(f[i-1][j]+f[i-1][j-1]-f[p[i]-1][j-1]+mod)%mod; 34 } 35 } 36 /*for(int i=1;i<=n;++i){ 37 for(int j=1;j<=min(i,l);++j){ 38 cout<<"dp["<<i<<"]["<<j<<"]="<<f[i][j]<<endl; 39 // if(p[i]-1==0)cout<<f[p[i]-1][j-1]<<" "; 40 } 41 }*/ 42 printf("%lld",f[n][l]); 43 }