NFLS 241014 比赛总结
T1 JZOI5246 Trip
Problem
有一串长为 \(n\) 的序列 \(a\),有 \(m\) 组询问,每组询问给出一个区间,求区间内有多少个数满足以下条件之一:
- 在区间内,它的左侧不存在大于它的数。
- 在区间内,它的右侧不存在大于它的数。
Solution
离散化,用权值线段树求出序列上每个数左边和右边第一个比它大的数的位置,记为 \(L,R\)。
因此对于每组询问 \([l,r]\),我们要求 \([l,r]\) 中所有 \(L<l\) 或 \(r<R\) 的点,可以大减小来求:我们先求出序列中所有 \(L<l\) 或 \(r<R\) 的点,减去这之中不在 \([l,r]\) 中的点。而我们发现不在区间中的点一定都是满足 \(L<l\) 或 \(r<R\) 的,因此直接减去 \(n-(r-l+1)\) 。
现在问题转化为一个二维数点:\(n\) 个点 \((L,R)\),\(m\) 组询问 \(l,r\),求 \(L<l\) 或 \(r<R\) 的点,树状数组做即可。代码中是先求的 1 号部分,然后统一求的 2 3 号部分,避免为了 3 号部分又重新倒着做一遍。
代码
#include<bits/stdc++.h>
#define lch (pos<<1)
#define rch (pos<<1|1)
#define getmid int mid=(l+r)/2
using namespace std;
#ifdef ONLINE_JUDGE
#define getchar __getchar
inline char __getchar(){
static char ch[1<<20],*l,*r;
return (l==r&&(r=(l=ch)+fread(ch,1,1<<20,stdin),l==r))?EOF:*l++;
}
#endif
template<class T>inline void rd(T &x){
T res=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9'){if(ch=='-')f=-1; ch=getchar();}
while('0'<=ch && ch<='9'){res=res*10+ch-'0';ch=getchar();}
x=res*f;
}
template<class T>inline void wt(T x,char endch='\0'){
static char wtbuff[20];
static int wtptr;
if(x==0){
putchar('0');
}
else{
if(x<0){x=-x;putchar('-');}
wtptr=0;
while(x){wtbuff[wtptr++]=x%10+'0';x/=10;}
while(wtptr--) putchar(wtbuff[wtptr]);
}
if(endch!='\0') putchar(endch);
}
typedef pair<int,int> pii;
const int MAXN=1e6+5;
int n,m,a[MAXN],na[MAXN],buck[MAXN],len,ans[MAXN];
pii rg[MAXN];
struct NQRY{
int first,second,id;
bool operator < (const NQRY y){
return first<y.first;
}
}qry[MAXN];
class SegTreeMax{
private:
int t[MAXN<<2];
inline void pushup(int pos){
t[pos]=max(t[lch],t[rch]);
}
public:
void update(int pos,int l,int r,int aim,int val){
if(l==r){
t[pos]=val;
return;
}
getmid;
if(aim<=mid) update(lch,l,mid,aim,val);
else update(rch,mid+1,r,aim,val);
pushup(pos);
}
int query(int pos,int l,int r,int ll,int rr){
if(ll>rr) return 0;
if(ll<=l && r<=rr){
return t[pos];
}
getmid,res=0;
if(ll<=mid) res=max(res,query(lch,l,mid,ll,rr));
if(mid<rr) res=max(res,query(rch,mid+1,r,ll,rr));
return res;
}
}stmax;
class SegTreeMin{
private:
int t[MAXN<<2];
inline void pushup(int pos){
t[pos]=min(t[lch],t[rch]);
}
public:
SegTreeMin(){
memset(t,0x3f,sizeof(t));
}
void update(int pos,int l,int r,int aim,int val){
if(l==r){
t[pos]=val;
return;
}
getmid;
if(aim<=mid) update(lch,l,mid,aim,val);
else update(rch,mid+1,r,aim,val);
pushup(pos);
}
int query(int pos,int l,int r,int ll,int rr){
if(ll>rr) return 0x3f3f3f3f;
if(ll<=l && r<=rr){
return t[pos];
}
getmid,res=0x3f3f3f3f;
if(ll<=mid) res=min(res,query(lch,l,mid,ll,rr));
if(mid<rr) res=min(res,query(rch,mid+1,r,ll,rr));
return res;
}
}stmin;
class Fenwick{
private:
int t[MAXN];
inline int lowbit(int x){
return x&(-x);
}
public:
inline void reset(){
memset(t,0,sizeof(t));
}
inline void add(int pos,int v){
while(pos<=n){
t[pos]+=v;
pos+=lowbit(pos);
}
}
inline int ask(int pos){
int res=0;
while(pos>0){
res+=t[pos];
pos-=lowbit(pos);
}
return res;
}
inline int ask_range(int l,int r){
if(l>r) return 0;
if(l<=1) return ask(r);
else return ask(r)-ask(l-1);
}
}fw;
int main(){
rd(n);rd(m);
for(int i=1;i<=n;i++){
rd(a[i]);
buck[i]=a[i];
}
for(int i=1;i<=m;i++){
rd(qry[i].first);rd(qry[i].second);
qry[i].id=i;
}
sort(buck+1,buck+1+n);
len=unique(buck+1,buck+1+n)-buck-1;
for(int i=1;i<=n;i++)
na[i]=lower_bound(buck+1,buck+1+len,a[i])-buck;
for(int i=1,res;i<=n;i++){
res=stmax.query(1,1,len,na[i]+1,len);
if(res==0) rg[i].first=1;
else rg[i].first=res+1;
stmax.update(1,1,len,na[i],i);
}
for(int i=n,res;i>=1;i--){
res=stmin.query(1,1,len,na[i]+1,len);
if(res==0x3f3f3f3f) rg[i].second=n;
else rg[i].second=res-1;
stmin.update(1,1,len,na[i],i);
}
sort(qry+1,qry+1+m);
sort(rg+1,rg+1+n);
for(int i=1,ptrqry=0,ptrrg=0;i<=n;i++){
while(ptrrg<n && rg[ptrrg+1].first<=i){
ptrrg++;
fw.add(rg[ptrrg].second,1);
}
while(ptrqry<m && qry[ptrqry+1].first<=i){
ptrqry++;
ans[qry[ptrqry].id]+=fw.ask(qry[ptrqry].second-1);
}
if(ptrrg>=n && ptrqry>=m) break;
}
for(int i=1;i<=m;i++){
ans[qry[i].id]+=fw.ask_range(qry[i].second,n);
ans[qry[i].id]-=(qry[i].first-1)+(n-qry[i].second);
}
for(int i=1;i<=m;i++){
wt(ans[i],'\n');
}
return 0;
}
T2 QOJ1173 Knowledge Is...
Problem
有 \(n\) 个区间,求最多能选出多少对区间,使得每对中的两个区间严格不交,一个区间至多属于一对区间,也可以不被选。
Solution
贪心。按左端点顺序遍历所有区间,开两个以右端点为关键字的小根堆,一个维护之前没有被匹配的区间,一个维护被匹配的区间对中右侧的那个区间,记当前区间为 \([l,r]\),堆顶的区间为 \([l',r']\)。
- 如果没被匹配的区间堆顶 \(r'<l\),那么就可以匹配,否则一定不存在未匹配的区间可以与之配。
- 如果被匹配的区间堆顶 \(r'<r\),那么可以让当前区间去匹配堆顶原先匹配的区间,而将堆顶抽出来变为未匹配的,这样一定是更优的,并且由于我们是按左端点顺序遍历,\(l'<l\),因此替换一定是合法的。
- 如果仍无法匹配,那就塞入未匹配的堆中。
代码
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int MAXN=5e5+5;
int n,ans=0;
pii a[MAXN];
struct CMP{
bool operator ()(const pii& a,const pii& b){
return a.second>b.second;
}
};
priority_queue<pii,vector<pii>,CMP>match,unmatch;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i].first>>a[i].second;
}
sort(a+1,a+1+n);
for(int i=1;i<=n;i++){
bool flag=0;
if(!unmatch.empty()){
auto it=unmatch.top();
if(it.second<a[i].first){
flag=1;
ans++;
unmatch.pop();
match.push(a[i]);
}
}
if(!flag && !match.empty()){
auto it=match.top();
if(it.second<a[i].second){
flag=1;
match.pop();
match.push(a[i]);
unmatch.push(it);
}
}
if(!flag){
unmatch.push(a[i]);
}
}
cout<<ans<<endl;
return 0;
}
T3 Codeforces Gym 102538H Horrible Cycles
Problem
有 \(2n\) 个点的二分图,左右各 \(n\) 个点,左侧第 \(i\) 个点与右侧第 \(j\in[1,a[i]]\) 个点有边,求图中有多少简单环(简单环:每个点只经过一次)。
Solution
将所有点重新排序,使得所有的左部点与它前面的所有右部点连边。
考虑到如果我们只保留 \(1\sim n\) 的点,环就会被拆成很多条链。
令 f[i][j]
为只从前 \(i\) 个点中选择点保留,形成了 \(j\) 条链的方案数,注意此时的链是钦定了顺序的链。
考虑转移:
- 如果点 \(i\) 是右部点,那么加入后不会产生任何边,如果选择它,就会形成一个新的链。
f[i][j]=f[i-1][j]+f[i-1][j-1]
。 - 如果点 \(i\) 是左部点,那么加入后它会和前面所有右部点连边(这些边有些会被选入链中有些被弃用),如果选择它,就会将之前的链合并为一条,由于我们计数的是简单环,我们只能将当前点置于一条链中,相当于在 \(j\) 条链中分别选择一条与它的链尾相连,再选择一条与它的链头相连。
f[i][j]=f[i-1][j]+j*(j-1)*f[i-1][j+1]
。
那么答案就是所有 \(i\) 的 f[i-1][1]
之和,表示 \(i\) 将这条链的两端连起来了,形成了一个环,并且要减去二元环数量 \(\sum a_i\),最后还需要除以 \(2\),因为链是有序的,每个环被算了两次。
由于每次状态转移只与上一次状态有关,可以压维。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN=5005;
const LL P=1e9+7;
inline LL qpow(LL a,LL b){
a%=P;
LL res=1;
while(b){
if(b&1) res=res*a%P;
a=a*a%P;
b/=2;
}
return res;
}
int n,a[MAXN];
LL ans=0,sum=0,f[MAXN];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
sum=(sum+(LL)a[i])%P;
}
sort(a+1,a+1+n);
f[0]=1;
for(int i=1;i<=n;i++){
for(int j=a[i-1]+1;j<=a[i];j++){
for(int k=j;k>=1;k--){
f[k]=(f[k]+f[k-1])%P;
}
}
ans=(ans+f[1])%P;
for(int j=1;j<=a[i];++j){
f[j-1]=(f[j-1]+1LL*j*((j-1+P)%P)%P*f[j]%P)%P;
}
}
ans=(ans-sum+P)%P;
cout<<ans*qpow(2,P-2)%P<<endl;
return 0;
}
T4 SDOI2017 树点涂色
Problem
有一棵以 \(1\) 为根节点的有根树,最开始树上每个点的颜色都不同,有如下操作或询问:
- 将 \(i\) 到根的路径染上一种新颜色。
- 求 \(u\) 到 \(v\) 的路径有多少种不同的颜色。
- 求 \(i\) 的子树中的点到根的路径不同颜色数的最大值。
Solution
本题最关键的性质是,一种颜色的点在任意时刻都是以一条连续的链的形式出现的。
因此每次修改不断向上跳同色链,用一棵线段树记录颜色,一棵线段树记录每个点到根的路径不同颜色数。
最开始跳同色链的复杂度是 \(O(n)\) 的,但随着染色同色链总数量会越来越少,复杂度可能需要用势能来证。
考虑对于一个同色链,将其更新为一个新的颜色会有什么影响,如下图,设当前我们从上一条同色链(链头为 \(lst\))跳过来,目前在 \(x\),同色链的头尾分别为 \(coltop\) 和 \(colbtm\),记当前 \(coltop\) 为 \(y\),如果我把 \(x\) 到 \(y\) 染成一个新的颜色,称 \(y\) 在染色前到根路径的不同颜色数为 \(tmp\),则染色后 \(y\) 子树中所有点到根路径不同颜色数就会减少 \(tmp-1\),但此时需要注意,由于上一条链我们已经更新过答案了,因此实际上我们只需要对 \(y\) 子树中除了 \(lst\) 子树的其他点更新答案即可。
另外更新颜色时需注意,染色后当前颜色不一定会完全被覆盖,\(x\) 到 \(colbtm\) 的部分(不包括 \(x\))仍会保留原来的颜色,这一步需仔细实现。
代码
#include<bits/stdc++.h>
using namespace std;
#ifdef ONLINE_JUDGE
#define getchar __getchar
inline char __getchar(){
static char ch[1<<20],*l,*r;
return (l==r&&(r=(l=ch)+fread(ch,1,1<<20,stdin),l==r))?EOF:*l++;
}
#endif
template<class T>inline void rd(T &x){
T res=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9'){if(ch=='-')f=-1; ch=getchar();}
while('0'<=ch && ch<='9'){res=res*10+ch-'0';ch=getchar();}
x=res*f;
}
template<class T>inline void wt(T x,char endch='\0'){
static char wtbuff[20];
static int wtptr;
if(x==0){
putchar('0');
}
else{
if(x<0){x=-x;putchar('-');}
wtptr=0;
while(x){wtbuff[wtptr++]=x%10+'0';x/=10;}
while(wtptr--) putchar(wtbuff[wtptr]);
}
if(endch!='\0') putchar(endch);
}
const int MAXN=2e5+5,MAXB=20;
int n,m,lg[MAXN];
vector<int>g[MAXN];
int dep[MAXN],sz[MAXN],hson[MAXN],fa[MAXN][MAXB],dfn[MAXN],dfncnt=0,rk[MAXN],top[MAXN];
int coltop[MAXN],colbtm[MAXN],colcnt;
void dfs1(int x,int fat){
dep[x]=dep[fat]+1;
sz[x]=1;
hson[x]=-1;
fa[x][0]=fat;
for(int k=1;k<=lg[n];k++)
fa[x][k]=fa[fa[x][k-1]][k-1];
for(auto it:g[x]){
if(it==fat) continue;
dfs1(it,x);
sz[x]+=sz[it];
if(hson[x]==-1 || sz[it]>sz[hson[x]]) hson[x]=it;
}
}
void dfs2(int x,int topid){
top[x]=topid;
dfn[x]=++dfncnt;
rk[dfn[x]]=x;
if(hson[x]==-1) return;
dfs2(hson[x],topid);
for(auto it:g[x]){
if(it==fa[x][0] || it==hson[x]) continue;
dfs2(it,it);
}
}
inline int getlca(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
u=fa[top[u]][0];
}
return (dep[u]<dep[v]?u:v);
}
inline int getkthfa(int x,int k){
for(int i=lg[n];i>=0;i--)
if(k&(1<<i)) x=fa[x][i];
return x;
}
#define lch (pos<<1)
#define rch (pos<<1|1)
#define getmid int mid=(l+r)/2
class SegTreeMax{
private:
int t[MAXN<<2],tag[MAXN<<2];
inline void pushup(int pos){
t[pos]=max(t[lch],t[rch]);
}
inline void pushdown(int pos){
if(tag[pos]){
t[lch]+=tag[pos];
t[rch]+=tag[pos];
tag[lch]+=tag[pos];
tag[rch]+=tag[pos];
tag[pos]=0;
}
}
public:
// void print(int pos,int l,int r){
// if(l==r){
// cout<<t[pos]<<' ';
// return;
// }
// pushdown(pos);
// getmid;
// print(lch,l,mid);
// print(rch,mid+1,r);
// }
void build(int pos,int l,int r){
tag[pos]=0;
if(l==r){
t[pos]=dep[rk[l]];
return;
}
getmid;
build(lch,l,mid);
build(rch,mid+1,r);
pushup(pos);
}
void update(int pos,int l,int r,int ll,int rr,int val){
if(ll<=l && r<=rr){
t[pos]+=val;
tag[pos]+=val;
return;
}
getmid;
pushdown(pos);
if(ll<=mid) update(lch,l,mid,ll,rr,val);
if(mid<rr) update(rch,mid+1,r,ll,rr,val);
pushup(pos);
}
int query(int pos,int l,int r,int ll,int rr){
if(ll<=l && r<=rr){
return t[pos];
}
getmid,res=0;
pushdown(pos);
if(ll<=mid) res=max(res,query(lch,l,mid,ll,rr));
if(mid<rr) res=max(res,query(rch,mid+1,r,ll,rr));
return res;
}
}stmx;
class SegTreeCol{
private:
int t[MAXN<<2],tag[MAXN<<2];
inline void pushup(int pos){
if(t[lch]!=-1 && t[rch]!=-1 && t[lch]==t[rch])
t[pos]=t[lch];
else
t[pos]=-1;
}
inline void pushdown(int pos){
if(tag[pos]!=-1){
t[lch]=t[rch]=tag[lch]=tag[rch]=tag[pos];
tag[pos]=-1;
}
}
public:
// void print(int pos,int l,int r){
// if(l==r){
// cout<<t[pos]<<' ';
// return;
// }
// pushdown(pos);
// getmid;
// print(lch,l,mid);
// print(rch,mid+1,r);
// }
void build(int pos,int l,int r){
tag[pos]=-1;
if(l==r){
t[pos]=rk[l];
return;
}
getmid;
build(lch,l,mid);
build(rch,mid+1,r);
pushup(pos);
}
void update(int pos,int l,int r,int ll,int rr,int val){
if(ll<=l && r<=rr){
t[pos]=val;
tag[pos]=val;
return;
}
getmid;
pushdown(pos);
if(ll<=mid) update(lch,l,mid,ll,rr,val);
if(mid<rr) update(rch,mid+1,r,ll,rr,val);
pushup(pos);
}
int query(int pos,int l,int r,int aim){
if(l==r){
return t[pos];
}
getmid;
pushdown(pos);
if(aim<=mid) return query(lch,l,mid,aim);
else return query(rch,mid+1,r,aim);
}
}stcol;
#undef lch
#undef rch
#undef getmid
int main(){
rd(n);rd(m);//int _;rd(_);
for(int i=1,u,v;i<n;i++){
rd(u);rd(v);
g[u].push_back(v);
g[v].push_back(u);
}
lg[1]=0;
for(int i=2;i<=n;i++)
lg[i]=lg[i/2]+1;
dep[0]=0;
dfs1(1,0);
dfs2(1,1);
stmx.build(1,1,n);
stcol.build(1,1,n);
for(int i=1;i<=n;i++)
coltop[i]=colbtm[i]=i;
colcnt=n;
int opt,x,y;
while(m--){
rd(opt);rd(x);
switch(opt){
case 1:{
colcnt++;
coltop[colcnt]=1;colbtm[colcnt]=x;
int lst=-1,precol,tmp;
while(x!=0){
precol=stcol.query(1,1,n,dfn[x]);
assert(coltop[precol]!=-1 && colbtm[precol]!=-1);
y=coltop[precol];
tmp=stmx.query(1,1,n,dfn[y],dfn[y]);
stmx.update(1,1,n,dfn[y],dfn[y]+sz[y]-1,-tmp+1);
if(lst!=-1) stmx.update(1,1,n,dfn[lst],dfn[lst]+sz[lst]-1,tmp-1);
if(colbtm[precol]!=x){
coltop[precol]=getkthfa(colbtm[precol],dep[colbtm[precol]]-dep[x]-1);
stmx.update(1,1,n,dfn[coltop[precol]],dfn[coltop[precol]]+sz[coltop[precol]]-1,1);
}
else coltop[precol]=colbtm[precol]=-1;
while(top[x]!=top[y]){
stcol.update(1,1,n,dfn[top[x]],dfn[x],colcnt);
x=fa[top[x]][0];
}
stcol.update(1,1,n,dfn[y],dfn[x],colcnt);
lst=y;
x=fa[y][0];
}
break;
}
case 2:{
rd(y);
int lca=getlca(x,y);
wt(stmx.query(1,1,n,dfn[x],dfn[x])+stmx.query(1,1,n,dfn[y],dfn[y])-2*stmx.query(1,1,n,dfn[lca],dfn[lca])+1,'\n');
break;
}
case 3:{
wt(stmx.query(1,1,n,dfn[x],dfn[x]+sz[x]-1),'\n');
break;
}
}
}
return 0;
}