zzh的NOIP2021膜你赛
zzh的NOIP2021模拟赛
没zky的那么水
所以写挂情有可原
A Sequence
题意:给定两个1~n的排列,求最大前缀满足两排列在该前缀中任意区间的最小值位置相同
std维护了两个单减单调栈,\(O(n)\)扫了一遍,若栈顶元素位置不一样则输出\(i-1\)
想了想为什么这样是对的
单调栈常用来维护某个前缀的后缀的性质(然后扫一遍就是维护任意区间
比如说这个题维护的就是最小值
若该前缀合法,两个单调栈的栈顶无论在何时都应该是相同位置
码:
#include<bits/stdc++.h>
using namespace std;
const int N=500010;
int ans,st1[N],st2[N],n,a[N],b[N],top1=0,top2=0;
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) cin>>b[i];
for(int i=1;i<=n;i++){
while(top1&&st1[top1]>=a[i]) --top1;
while(top2&&st2[top2]>=b[i]) --top2;
st1[++top1]=a[i],st2[++top2]=b[i];
if(top1!=top2){ans=i-1;break;}
}
cout<<ans;
return 0;
}
B Cave
题意:给定一棵完全二叉树,对其进行m次删边/加边操作,求每次操作后节点\(i,j\)连通的\((i,j)\)数对对数
删边意味着本来\(s\)个连通块变为\(s+1\)个
加边意味着本来\(s\)个连通块变为\(s-1\)个
不同连通块中的节点不连通
若一个连通块有\(d\)个节点,则\(\, ans+=\frac{d\times (d-1)}{2}\)
用siz[i]来维护以i为根节点的子树大小
用tag[i]来标记该点与父节点之间是否有边(1为有边 0为无边)
码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=100010;
int tag[N],n,m;
ll siz[N];
int main(){
cin>>n>>m;
for(int i=n;i>=1;--i){
siz[i]++;
siz[i/2]+=siz[i];
tag[i]=1;
}//完全二叉树建法
tag[1]=0;
ll ans=1ll*n*(n-1)/2;
int x;
for(int i=1;i<=m;++i){
cin>>x;
if(tag[x]){
int root=x,si=siz[x];
while(tag[root]) siz[root/2]-=si,root/=2;
ans-=1ll*siz[root]*siz[x];
tag[x]=0;
}else{
tag[x]=1;
int root=x,si=siz[x];
while(tag[root]) siz[root/2]+=si,root/=2;
ans+=1ll*(siz[root]-si)*si;
}
cout<<ans<<endl;
}
return 0;
}
C Coin
题意:长度为\(L\)数轴上有\(N\)个关键点,第\(i\)个位置为\(x[i]\),数轴上任意一个点到\(s\)个关键点的距离之和不超过\(T\),求\(s\)的最大值
正解双指针,维护右区间定时左区间的最值
不断右移右指针并维护左区间最值,枚举答案
为什么标程那么麻烦
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=200009;
ll L,T,x[N];
int n;
int main(){
cin>>n>>L>>T;
for(int i=1;i<=n;i++){
cin>>x[i];
}
int p=1,md=1;//p表示左指针,md表示区间中点
ll nwans=0;//该区间的比较距离
int ans=1;//最终答案
for(int i=2;i<=n;i++){
int nw=(i+p)>>1;//修改区间后的中点
if(nw>md) md=nw;//判断中点是否变化,如果变了那就改
nwans+=x[i]-x[md];//更新区间比较距离
while(p<i&&nwans>T){
int nw=(i+p+1)>>1;
if(nw>md) md=nw;
nwans-=x[md]-x[p];p++;
}//左节点右移至满足条件
ans=max(ans,i-p+1);
}
cout<<ans;
return 0;
}
D Tree
题意:给出\(n\)个节点的连边方案,求构成不同的树中的安全点的总个数。(定义安全点为根节点或比自己父亲节点点权大的点)
做法是线段树/树状数组优化DP
首先我们把这道题变成一道计数题,计某个节点在能构成的所有树中是安全点的概率
设\(f[i]\)为节点\(i\)是安全点的概率,则有:
发现这个转移是由一个区间转移到一个点,需要区间查询和单点修改,所以想到线段树和树状数组
发现转移方程里有判断,不爽,用数据结构处理不了,考虑优化DP顺序,按高度从小到大排序,高度相同按序号排
除法要用逆元
我们还需要所有情况的总数:
最终答案是:
码:
#include<bits/stdc++.h>
#define N 100002
using namespace std;
typedef long long ll;
const int mod=998244353;
int n;
ll tr[N<<2],ni[N],ans;
struct node{
int h,id,l,r;
inline bool operator <(const node &b)const{
if(h!=b.h)return h<b.h;
return id<b.id;
}
}a[N];
inline ll rd(){
ll x=0;char c=getchar();bool f=0;
while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return f?-x:x;
}
inline void MOD(ll &x){x=x>=mod?x-mod:x;}//奇奇怪怪的卡常技巧
inline ll power(ll x,ll y){//快速幂
ll ans=1;
while(y){
if(y&1)ans=ans*x%mod;
x=x*x%mod;
y>>=1;
}
return ans;
}
void ins(int cnt,int l,int r,int x,ll y){//线段树单点插入
if(l==r){
MOD(tr[cnt]+=y);
return;
}
int mid=(l+r)>>1;
if(mid>=x)ins(cnt<<1,l,mid,x,y);
else ins(cnt<<1|1,mid+1,r,x,y);
MOD(tr[cnt]=tr[cnt<<1]+tr[cnt<<1|1]);
}
ll query(int cnt,int l,int r,int L,int R){//线段树区间查询
if(l>=L&&r<=R)return tr[cnt];
int mid=(l+r)>>1;
ll ans=0;
if(mid>=L)MOD(ans+=query(cnt<<1,l,mid,L,R));
if(mid<R)MOD(ans+=query(cnt<<1|1,mid+1,r,L,R));
return ans;
}
int main(){
n=rd();
for(int i=1;i<=n;++i)a[i].h=rd(),a[i].id=i;
ll num=1;
for(int i=1;i<=n;++i)ni[i]=power(i,mod-2);//求逆元
for(int i=2;i<=n;++i){
a[i].l=rd();a[i].r=rd();
num=num*(a[i].r-a[i].l+1)%mod;//预处理所有区间的乘积
}
a[1].l=a[1].r=1;
sort(a+1,a+n+1);//处理DP顺序
for(int i=1;i<=n;++i){
ll x=query(1,1,n,a[i].l,a[i].r)*ni[a[i].r-a[i].l+1]%mod;//算f[i]
if(a[i].id==1)x=1;
MOD(ans+=x);
ins(1,1,n,a[i].id,x);//把f[i]插入线段树
}
printf("%lld\n",ans*num%mod);
return 0;
}