杭电2022 2
1001 Static Query on Tree
转化题意之后就是问一棵树有多少点满足是集合\(A\)中某个节点的祖先且是\(B\)中某个节点的祖先且是\(C\)中某个节点的后代。
\(|A|+|B|+|C|<=10^5\)
满足条件的点一定构成一条链,并且链上最浅的节点是\(C\)中节点最深的节点是\(A\)中一点与\(B\)中一点的\(LCA\)。建出虚树,虚树上一定包含这条链的最浅节点和最深节点,在虚树上\(DP\)即可统计答案。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=2e5+500;
int read(){
int sum=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
return sum*f;
}
struct edge{
int to,nxt;
}e[N];
int head[N],cnt;
void add_edge(int u,int v){
cnt++;
e[cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
int dep[N],dfn[N],root,f[N][22],tot;
void get_dfn(int u){
for(int i=1;i<=20;i++)f[u][i]=f[f[u][i-1]][i-1];
dfn[u]=++tot;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
dep[v]=dep[u]+1;
f[v][0]=u;
get_dfn(v);
}
}
int get_lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
for(int i=20;i>=0;i--)
if(dep[f[x][i]]>=dep[y])x=f[x][i];
if(x==y)return x;
for(int i=20;i>=0;i--)
if(f[x][i]!=0&&(f[x][i]!=f[y][i]))x=f[x][i],y=f[y][i];
return f[x][0];
}
bool cmp(int a,int b){
return dfn[a]<dfn[b];
}
int stack[N],num[N][4],a[N],top,Map[N],_Map[N],c[4][N],col[N][4],ans;
void build_tree(){
a[0]=1;a[1]=root;
for(int i=1;i<=3;i++)
for(int j=1;j<=c[i][0];j++)
a[++a[0]]=c[i][j];
sort(a+1,a+1+a[0],cmp);
a[0]=unique(a+1,a+1+a[0])-a-1;
//处理a[]
stack[top=1]=root;
Map[++tot]=root,_Map[root]=tot;
//Map[]是虚树到实际树的映射 _Map[]是反映射
int x,y;
for(int i=2;i<=a[0];i++){
int p=a[i];
x=stack[top],y=stack[top-1];
int lca=get_lca(x,p);
if(lca==x)stack[++top]=p,Map[++tot]=p,_Map[p]=tot;
else{
while(top>1&&dfn[y]>=dfn[lca]){
add_edge(_Map[y],_Map[x]);
top--;
x=stack[top],y=stack[top-1];
}
if(x!=lca){
Map[++tot]=lca;_Map[lca]=tot;
add_edge(_Map[lca],_Map[x]);
stack[top]=lca;
}
stack[++top]=p;
Map[++tot]=p,_Map[p]=tot;
}
}
while(top>1){
x=stack[top],y=stack[top-1];
add_edge(_Map[y],_Map[x]),top--;
}
}
void dfs(int u){
if(col[Map[u]][3])num[u][3]++;
if(col[Map[u]][1])num[u][1]++;
if(col[Map[u]][2])num[u][2]++;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
num[v][3]+=num[u][3];
dfs(v);
num[u][1]+=num[v][1];
num[u][2]+=num[v][2];
}
if(num[u][1]&&num[u][2]&&num[u][3])ans++;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(num[u][1]&&num[u][2]&&num[u][3]&&num[v][1]&&num[v][2]&&num[v][3]){
ans+=dep[Map[v]]-dep[Map[u]]-1;
}
}
}
int main(){
int T=read();
while(T--){
int n=read();
int q=read();
for(int i=1;i<n;i++){
int u=read();
add_edge(u,i+1);
}
root=1;
dep[root]=1;
get_dfn(root);
cnt=0;tot=0;
for(int i=1;i<=n;i++)head[i]=0;
while(q--){
c[1][0]=read(),c[2][0]=read(),c[3][0]=read();
for(int i=1;i<=3;i++)
for(int j=1;j<=c[i][0];j++){
c[i][j]=read();
col[c[i][j]][i]=1;
}
build_tree();
ans=0;
dfs(_Map[root]);
printf("%d\n",ans);
for(int i=1;i<=3;i++)
for(int j=1;j<=c[i][0];j++)
col[c[i][j]][i]=0;
cnt=0;
for(int i=1;i<=tot;i++)head[i]=0,num[i][1]=num[i][2]=num[i][3]=0;
tot=0;
}
}
return 0;
}
1005 Slayers Come
\(m\)个技能,每一个技能可以干掉位置\(pos_i\)上的怪兽并秽土转生它使它的攻击力下降\(l_i\)/\(r_i\)后攻击它相邻的怪兽。问有多少技能集合可以使得使用集合中所有技能后所有\(n\)个怪兽都至少死过一次。
可以预处理出每一个技能的覆盖范围,然后问题转化成了选择若干个区间覆盖整个区间的计数。
求出数组\(c[i]=a[i+1]-b[i]\)一个技能的覆盖区间的左边界就是\(c[pos[i]-1]到c[1]\)中第一个小于\(l_i\)的下标+1。这个东西可以用平衡树求出,用同样的方法也可以求出区间的右边界。
剩下的计数可以用\(DP\)解决
\(dp[i]\)表示\(1~i\)被覆盖\(i+1\)不被覆盖的方案数。
将区间按右端点排序之后枚举区间
转移就是 \(dp[r]=\sum_{i=l-1}^{r}dp[i], \ dp[i]*=2 (i<=l-2)\)
转移的过程可以用线段树优化。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<cstdlib>
using namespace std;
#define int long long
#define mid (L+R>>1)
#define ls (now<<1)
#define rs (now<<1|1)
const int p=998244353;
const int INF=1e9+7;
const int N=5e5+50;
int a[N],b[N],pos[N],l[N],r[N],root,x,y;
int read(){
int sum=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar(); }
while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
return sum*f;
}
int tot,val[N],mn[N],rad[N],ch[N][2],W[N];
int new_node(int x,int w){
int now=++tot;
val[now]=x;
mn[now]=w;
W[now]=w;
rad[now]=rand();
return now;
}
void update(int now){
mn[now]=W[now];
if(ch[now][0])mn[now]=min(mn[now],mn[ch[now][0]]);
if(ch[now][1])mn[now]=min(mn[now],mn[ch[now][1]]);
}
int merge(int x,int y){
if(x==0||y==0)return x+y;
if(rad[x]>rad[y]){
ch[x][1]=merge(ch[x][1],y);
update(x);
return x;
}
else{
ch[y][0]=merge(x,ch[y][0]);
update(y);
return y;
}
}
void split(int now,int &x,int &y,int w){
if(now==0){
x=y=0;
return;
}
if(val[now]<=w){
x=now;
split(ch[now][1],ch[x][1],y,w);
}
else{
y=now;
split(ch[now][0],x,ch[y][0],w);
}
update(now);
}
int find_1(int w){
int now=x,ans=-INF;
while(now){
if(W[now]<w)ans=max(ans,now);
if(mn[ch[now][1]]<w&&ch[now][1])now=ch[now][1];
else now=ch[now][0];
}
return val[ans];
}
int find_2(int w){
int now=y,ans=INF;
while(now){
if(W[now]<w)ans=min(ans,now);
if(mn[ch[now][0]]<w&&ch[now][0])now=ch[now][0];
else now=ch[now][1];
}
return val[ans];
}
void clear(){
for(int i=1;i<=tot;i++)ch[i][0]=ch[i][1]=mn[i]=val[i]=rad[i]=W[i]=0;
tot=0;root=0;
}
struct line{
int l,r;
}c[N];
bool cmp(line a,line b){
return a.r<b.r;
}
int sum[N*8],lazy_1[N*8],lazy_2[N*8];
void update_(int now){
sum[now]=(sum[ls]+sum[rs])%p;
}
void init(int L,int R,int now){
lazy_1[now]=1,lazy_2[now]=0;
sum[now]=0;
if(L==R){
if(L==1)sum[now]=1;
return;
}
init(L,mid,ls);
init(mid+1,R,rs);
update_(now);
}
void pushdown(int L,int R,int now){
if(lazy_1[now]!=1){
lazy_1[ls]=lazy_1[ls]*lazy_1[now]%p;
lazy_2[ls]=lazy_2[ls]*lazy_1[now]%p;
sum[ls]=sum[ls]*lazy_1[now]%p;
lazy_1[rs]=lazy_1[rs]*lazy_1[now]%p;
lazy_2[rs]=lazy_2[rs]*lazy_1[now]%p;
sum[rs]=sum[rs]*lazy_1[now]%p;
lazy_1[now]=1;
}
if(lazy_2[now]){
lazy_2[ls]=(lazy_2[ls]+lazy_2[now])%p;
sum[ls]=(sum[ls]+(mid-L+1)*lazy_2[now])%p;
lazy_2[rs]=(lazy_2[rs]+lazy_2[now])%p;
sum[rs]=(sum[rs]+(R-mid)*lazy_2[now])%p;
lazy_2[now]=0;
}
}
int get_sum(int L,int R,int l,int r,int now){
pushdown(L,R,now);
if(L==l&&R==r)return sum[now];
if(l>mid)return get_sum(mid+1,R,l,r,rs);
else if(r<=mid)return get_sum(L,mid,l,r,ls);
else return (get_sum(L,mid,l,mid,ls)+get_sum(mid+1,R,mid+1,r,rs))%p;
}
void add(int L,int R,int x,int w,int now){
pushdown(L,R,now);
if(L==R){
sum[now]=(sum[now]+w)%p;
return;
}
if(x>mid)add(mid+1,R,x,w,rs);
else add(L,mid,x,w,ls);
update_(now);
}
void mul(int L,int R,int l,int r,int w,int now){
if(l>r)return;
pushdown(L,R,now);
if(L==l&&R==r){
sum[now]=sum[now]*w%p;
lazy_1[now]=lazy_1[now]*w%p;
lazy_2[now]=lazy_2[now]*w%p;
return;
}
if(l>mid)mul(mid+1,R,l,r,w,rs);
else if(r<=mid)mul(L,mid,l,r,w,ls);
else mul(L,mid,l,mid,w,ls),mul(mid+1,R,mid+1,r,w,rs);
update_(now);
}
signed main(){
srand(time(NULL));
int T=read();
while(T--){
int n=read(),m=read();
for(int i=1;i<=n;i++)a[i]=read(),b[i]=read();
for(int i=1;i<=m;i++)pos[i]=read(),l[i]=read(),r[i]=read();
for(int i=1;i<n;i++)root=merge(root,new_node(i,a[i+1]-b[i]));
for(int i=1;i<=m;i++){
split(root,x,y,pos[i]-1);
if(mn[x]>=l[i]||x==0)c[i].l=1;
else c[i].l=find_1(l[i])+1;
root=merge(x,y);
}
clear();
for(int i=2;i<=n;i++)root=merge(root,new_node(i,a[i-1]-b[i]));
for(int i=1;i<=m;i++){
split(root,x,y,pos[i]);
if(mn[y]>=r[i]||y==0)c[i].r=n;
else c[i].r=find_2(r[i])-1;
root=merge(x,y);
}
clear();
sort(c+1,c+1+m,cmp);
init(1,n+1,1);
for(int i=1;i<=m;i++){
add(1,n+1,c[i].r+1,get_sum(1,n+1,c[i].l,c[i].r+1,1),1);
mul(1,n+1,1,c[i].l-1,2,1);
}
printf("%lld\n",get_sum(1,n+1,n+1,n+1,1));
}
return 0;
}