(来源:XJ高质量原创题)
弱化版
题意:(n imes n) 的棋盘上有 (n) 颗棋子,每行每列都有且仅有一颗棋子,求出有多少个 (k imes k) 的子棋盘也满足每行每列只有一颗棋子。
将棋盘的 (x) 轴看作上一题中每个点的下标,(y) 轴看作这个点的权值。
其实就是树退化为了链,而 (m=1) 的情况。
题意
给定一棵树,每一个点有一个权值 (a_i) ,({a}) 构成一个排列。
询问有多少个点集 (S={u_1,u_2,dots,u_k}(1le jle n)) 满足这些点构成联通块不超过 (m) 个,且 (max_{1le ile k}{a_{u_{i}}}-min_{1le ile k}{a_{u_{i}}}+1=k) 。
(nle 10^5,mle 7)
题解
考虑枚举值域的右端点 (r),考虑加入这个右端点时,对与 ([1,r-1]) 中的最短点的联通块个数的变化。
先假设假设这个点与其他无边相连,那么联通块数加 (1) 。
对于每一条与点集中的点相连的边 ((num(r),ver)) ,都会使左端点在 ([1,val(ver)]) 中的联通块数量 (-1) ((num(x)) 表示权值为 (x) 的点的编号)。
之后我们就将题意简化为:
-
区间加上一个数
-
求出整个数列中 (le m) 的数有多少个
-
保证 (a[1]=1)
考虑到 (m) 比较小,猜测答案复杂度为 (O(nmlog n)) 。
我们将加减操作同普通的线段树操作,主要改变 ( ext{pushup}) 操作。
在线段树的每一个节点 ([l,r]) 上维护:
-
(minn) : 表示在 ([l,r]) 区间上数的最小值。
-
(cnt[7]) : 表示这段区间内值域在 ([minn,minn+6]) 之间的数分别有多少。
之后根据两个子区间的最小值就可以愉快地转移啦!!
// Author:A weak man named EricQian
// expect : 100 pts
#include<bits/stdc++.h>
using namespace std;
#define infll 0x7f7f7f7f7f7f7f7f
#define inf 0x3f3f3f3f
#define Maxn 500005
#define Maxm 7
typedef long long ll;
inline int rd()
{
int x=0;
char ch,t=0;
while(!isdigit(ch = getchar())) t|=ch=='-';
while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
return x=t?-x:x;
}
int n,m,tot;
int val[Maxn],num[Maxn];
int hea[Maxn],nex[Maxn<<1],ver[Maxn<<1];
struct TREE { ll minn,laz,cnt[Maxm]; }tree[Maxn<<2];
ll ans;
inline void add(int x,int y){ ver[++tot]=y,nex[tot]=hea[x],hea[x]=tot; }
inline void pushup(int p)
{
int x=p<<1,y=p<<1|1,tmp;
if(tree[x].minn>tree[y].minn) swap(x,y);
tree[p].minn=tree[x].minn;
for(int i=0;i<m;i++) tree[p].cnt[i]=tree[x].cnt[i];
tmp=tree[y].minn-tree[x].minn;
for(int i=tmp;i<m;i++) tree[p].cnt[i]+=tree[y].cnt[i-tmp];
}
inline void pushdown(int p)
{
tree[p<<1].laz+=tree[p].laz,tree[p<<1].minn+=tree[p].laz;
tree[p<<1|1].laz+=tree[p].laz,tree[p<<1|1].minn+=tree[p].laz;
tree[p].laz=0;
}
void build(int p,int nl,int nr)
{
if(nl==nr) { tree[p].minn=tree[p].cnt[0]=1; return; }
int mid=(nl+nr)>>1;
build(p<<1,nl,mid),build(p<<1|1,mid+1,nr);
pushup(p);
}
void add(int p,int nl,int nr,int l,int r,int k)
{
if(nl>=l && nr<=r) { tree[p].minn+=k,tree[p].laz+=k; return; }
pushdown(p);
int mid=(nl+nr)>>1;
if(mid>=l) add(p<<1,nl,mid,l,r,k);
if(mid<r) add(p<<1|1,mid+1,nr,l,r,k);
pushup(p);
}
int main()
{
n=rd(),m=rd();
for(int i=1;i<=n;i++) val[i]=rd(),num[val[i]]=i;
for(int i=1,x,y;i<n;i++) x=rd(),y=rd(),add(x,y),add(y,x);
build(1,1,n);
for(int r=1;r<=n;r++)
{
if(r!=1) add(1,1,n,1,r-1,1);
for(int i=hea[num[r]];i;i=nex[i])
if(val[ver[i]]<val[num[r]])
add(1,1,n,1,val[ver[i]],-1);
for(int i=0;i<m;i++) ans+=tree[1].cnt[i];
ans-=(n-r);
}
printf("%lld
",ans);
return 0;
}