P11390 [COCI 2024/2025 #1] 教师 / Učiteljica 题解
P11390 [COCI 2024/2025 #1] 教师 / Učiteljica 题解
知识点
线段树,扫描线,容斥,状压,线段树分治。
分析
首先可以很容易的看出这题和扫描线有关,把 \([l,r]\) 转换成图形上的点即可。我们直接看到子任务 3,满足特殊性质 B:\(k = 1\)。
那么这个就是把每个数字单独拎出来,然后把合法的矩形并在一起,最后求矩形面积并,扫描线解决很简单。
法 1:容斥
这个解法的题解是网上最多的,对于擅长组合数学的同学们很友好。
我们从刚刚的部分分进一步推理:发现 \(k>1\) 时,就是把 \(k\) 不同时的矩形并再取交集,然后再算面积。那么这个“并”“交”让人想到了容斥,所以容斥把「取交集」变成多次的「求并集」解决即可。
由于其他题解讨论的都较为详细,这里不再赘述。
时间复杂度 \(O(k2^kn\log_2{n})\),空间复杂度 \(O(nk)\)。
法 2:线段树分治+状压
对于不擅长组合数学的同学们,这个做法可能会友好一点。
我们考虑不用容斥直接「取交集」。仍然是从部分分衍生出来,发现标记下传的线段树在矩形减操作时不太方便,于是可以想到线段树分治,避免矩形减操作。
再考虑状压来存储线段树上每个节点和其子节点已经满足的 \(k\),那么很容易就可以解出来:线段树上每个节点存覆盖到自己的区间矩形加的状态,开 \(2^k\) 大小的数组来存子树代表的区间中每种状态的数量。
时间复杂度 \(O(k2^kn\log^2_2{n})\),空间复杂度 \(O(n2^k)\)。可能需要卡常。
法 3:永久化标记+状压
起始思路与法 2 类似。考虑部分分的线段树实现方法,我们可以标记下传,也可以标记永久化,而标记永久化明显十分简洁,所以我们考虑在正解也用标记永久化。那么就直接正常的扫描线扫过去即可。
时间复杂度 \(O(k2^kn\log_2{n})\),空间复杂度 \(O(n2^k)\)。
大概是因为矩形只用加一次,导致这个做法是常数最小的,翻 Luogu 的提交记录,排名在前面的我有看到的都是这么写的。
代码
这里实现了永久化标记线段树配状压的做法。
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ll long long
#define RCL(a,b,c,d) memset(a,b,sizeof(c)*(d))
#define FOR(i,a,b) for(int i(a);i<=(int)(b);++i)
#define DOR(i,a,b) for(int i(a);i>=(int)(b);--i)
#define tomax(a,...) ((a)=max({(a),__VA_ARGS__}))
#define tomin(a,...) ((a)=min({(a),__VA_ARGS__}))
#define EDGE(g,i,x,y) for(int i=(g).h[(x)],y=(g)[(i)].v;~i;y=(g)[(i=(g)[i].nxt)>0?i:0].v)
#define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);return Main();}signed Main
using namespace std;
constexpr int N(1e5+10),K(4),St(1<<4);
int n,m,U,tot;
int a[N];
ll ans;
vector<int> vec[N];
struct Line {
int x,l,r,v,k;
friend bool operator <(Line a,Line b) { return a.x<b.x; }
} lin[N<<3];
struct SEG {
struct node {
int sta;
int cnt[K],sum[St];
node(int sta=0):sta(sta) { RCL(cnt,0,int,m),RCL(sum,0,int,U+1); }
void down(int k,int d) {
if(cnt[k])sta^=1<<k;
cnt[k]+=d;
if(cnt[k])sta^=1<<k;
}
} tr[N<<2];
#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((l+r)>>1)
void Up(int p) {
RCL(tr[p].sum,0,int,U+1);
FOR(S,0,U)tr[p].sum[tr[p].sta|S]+=tr[ls].sum[S]+tr[rs].sum[S];
}
void Build(int p=1,int l=1,int r=n) {
tr[p]=node(0);
if(l==r)return tr[p].sum[0]=1,void();
Build(ls,l,mid),Build(rs,mid+1,r),Up(p);
}
void Plus(int L,int R,int k,int d,int p=1,int l=1,int r=n) {
if(L<=l&&r<=R) {
if(l==r)tr[p].sum[tr[p].sta]=0;
tr[p].down(k,d),(l<r?Up(p),0:tr[p].sum[tr[p].sta]=1);
return;
}
if(L<=mid)Plus(L,R,k,d,ls,l,mid);
if(mid<R)Plus(L,R,k,d,rs,mid+1,r);
Up(p);
}
#undef ls
#undef rs
#undef mid
} seg;
void Plus(const int k,int xa,int xb,int ya,int yb) {
lin[++tot]= {xa,ya,yb,1,k},lin[++tot]= {xb+1,ya,yb,-1,k};
}
void Solve(vector<int> &vec,const int k) {
FOR(i,0,(int)vec.size()-k)
Plus(k-1,!i?1:vec[i-1]+1,vec[i],vec[i+k-1],i+k>=(int)vec.size()?n:vec[i+k]-1);
}
signed main() {
#ifdef Plus_Cat
freopen(Plus_Cat ".in","r",stdin),freopen(Plus_Cat ".out","w",stdout);
#endif
cin>>n>>m;
FOR(i,1,n)cin>>a[i],vec[a[i]].push_back(i);
U=(1<<m)-1;
FOR(i,1,n)if(!vec[i].empty())FOR(k,1,m)Solve(vec[i],k);
sort(lin+1,lin+tot+1),seg.Build();
int it(1);
FOR(i,1,n) {
while(it<=tot&&lin[it].x<=i)seg.Plus(lin[it].l,lin[it].r,lin[it].k,lin[it].v),++it;
ans+=seg.tr[1].sum[U];
}
cout<<ans<<endl;
return 0;
}

浙公网安备 33010602011771号