2021 第二轮省队集训 Day5
A
先考虑 \(k=0\) 怎么做:用 set 维护每个点的前驱和后继,用线段树维护区间内后继编号的最小值以及区间 \(v\) 的和。
- 对于修改操作,它只会修改三个位置的后继:原数、原来的前驱、新前驱。直接在线段树里单点修改就行了。
- 对于查询操作,由于 \(k=0\),所以只能选最长的一段颜色互不相同的一段。在线段树内二分这个段的长度即可。
若 \(k>0\),那么修改的做法不变,查询的做法就是上述做法重复 \(k\) 次:每次找到第一个有重复颜色的位置,并更新这个颜色对应的最大值,然后继续找下一个位置。
考场上写了个 \(O(nk \log^2 n)\) 的做法,但实际上上述二分可以做到 \(1\log\)。
代码($O(nk\log n)$)
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <set>
#include <tuple>
#include <vector>
using namespace std;
#define For(Ti,Ta,Tb) for(int Ti=(Ta);Ti<=(Tb);++Ti)
#define Dec(Ti,Ta,Tb) for(int Ti=(Ta);Ti>=(Tb);--Ti)
typedef long long ll;
template<typename T> void Read(T &x){
x=0;int f=1,ch;
for(ch=getchar();!isdigit(ch);ch=getchar()) f=(ch=='-'?-1:1);
for(;isdigit(ch);ch=getchar()) x=x*10+(ch-48);
x*=f;
}
template<typename T,typename... Args> void Read(T &x,Args&... args){
Read(x);Read(args...);
}
const int N=2e5+5,Inf=0x3f3f3f3f;
int n,m,c[N],v[N],nxt[N];
set<int> st[N];
#define ls(xx) ((xx)<<1)
#define rs(xx) ((xx)<<1|1)
struct Node{
int l,r,nxt,minp;ll vsum;
}t[N<<2];
void Pushup(int p){
if(t[ls(p)].nxt<t[rs(p)].nxt){
t[p].nxt=t[ls(p)].nxt,t[p].minp=t[ls(p)].minp;
}else{
t[p].nxt=t[rs(p)].nxt,t[p].minp=t[rs(p)].minp;
}
t[p].vsum=t[ls(p)].vsum+t[rs(p)].vsum;
}
void Build(int p,int l,int r){
t[p].l=l,t[p].r=r;
if(l==r){
t[p].nxt=nxt[l],t[p].vsum=v[l];
t[p].minp=l;
return;
}
int mid=(l+r)>>1;
Build(ls(p),l,mid);Build(rs(p),mid+1,r);
Pushup(p);
}
void Modify(int p,int pos,int nxt_,int val){
if(t[p].l==t[p].r){
t[p].nxt=nxt_;
if(~val) t[p].vsum=val;
return;
}
int mid=(t[p].l+t[p].r)>>1;
if(pos<=mid) Modify(ls(p),pos,nxt_,val);
else Modify(rs(p),pos,nxt_,val);
Pushup(p);
}
int QMinp(int p,int l,int r){
if(l<=t[p].l&&t[p].r<=r) return t[p].minp;
int mid=(t[p].l+t[p].r)>>1,minp=0;
if(l<=mid) minp=QMinp(ls(p),l,r);
if(r>mid){
int p_=QMinp(rs(p),l,r);
if(!minp||nxt[minp]>nxt[p_]) minp=p_;
}
return minp;
}
ll QSum(int p,int l,int r){
if(l<=t[p].l&&t[p].r<=r) return t[p].vsum;
int mid=(t[p].l+t[p].r)>>1;ll res=0;
if(l<=mid) res+=QSum(ls(p),l,r);
if(r>mid) res+=QSum(rs(p),l,r);
return res;
}
void Getp(int p,int l,int r,int *pos,int &cnt){
if(l<=t[p].l&&t[p].r<=r){
pos[++cnt]=p;return;
}
int mid=(t[p].l+t[p].r)>>1;
if(l<=mid) Getp(ls(p),l,r,pos,cnt);
if(r>mid) Getp(rs(p),l,r,pos,cnt);
}
int Search(int p,int mn){
if(t[p].l==t[p].r) return t[p].l;
if(min(mn,t[ls(p)].nxt)<=t[ls(p)].r) return Search(ls(p),mn);
else return Search(rs(p),min(mn,t[ls(p)].nxt));
}
int QMaxp(int s){
static int seg[105];
int len=0,rseg=1,mn=n+1,pre=n+1;Getp(1,s,n,seg,len);
for(;rseg<=len;++rseg){
mn=min(mn,t[seg[rseg]].nxt);
if(mn<=t[seg[rseg]].r) break;
pre=mn;
}
if(rseg<=len) return Search(seg[rseg],pre);
else return n+1;
}
ll mxans[N],tag[N];int tcnt=0;
ll Query(int s,int k){
++tcnt;
int l=QMaxp(s);
ll res=QSum(1,s,l-1);
if(!k) return res;
vector<int> chg;
For(i,1,k){
if(l==n+1) break;
int p=QMinp(1,s,l);
if(tag[c[p]]!=tcnt){
tag[c[p]]=tcnt;
if(v[p]<v[l]) res+=v[l]-v[p],mxans[c[p]]=v[l];
else mxans[c[p]]=v[p];
}else{
if(mxans[c[p]]<v[l]) res+=v[l]-mxans[c[p]],mxans[c[p]]=v[l];
}
chg.push_back(p);
Modify(1,p,Inf,-1);
nxt[p]=Inf;
int oldl=l;
l=QMaxp(s);
if(oldl+1<=l-1) res+=QSum(1,oldl+1,l-1);
}
for(int i:chg){
int p=*++st[c[i]].find(i);
Modify(1,i,p,-1);nxt[i]=p;
}
return res;
}
int main(){
freopen("gp.in","r",stdin);
freopen("gp.out","w",stdout);
Read(n,m);
For(i,1,n) st[i].insert(n+1);
For(i,1,n){
Read(c[i],v[i]);
st[c[i]].insert(i);
}
For(i,1,n) nxt[i]=*++st[c[i]].find(i);
Build(1,1,n);
int op,x,col,val;
while(m--){
Read(op,x,col);
if(op==1){
Read(val);
auto it=st[c[x]].find(x);
it=st[c[x]].erase(st[c[x]].find(x));
if(it!=st[c[x]].begin()){
int nxt_=*it;int pre=*--it;
nxt[pre]=nxt_;
Modify(1,pre,nxt_,-1);
}
c[x]=col,v[x]=val;
it=st[c[x]].insert(x).first;
nxt[x]=*++it;
Modify(1,x,*it,val);
--it;
if(it!=st[c[x]].begin()){
int pre=*--it;nxt[pre]=x;
Modify(1,pre,x,-1);
}
}else{
printf("%lld\n",Query(x,col));
}
}
return 0;
}
如何在某个区间内进行线段树二分
首先,在线段树上找到那些可以拼凑出查询区间的那些区间,递归时可以指定递归顺序,来让这些区间有序。然后从左往右遍历这些区间,找到第一个不满足条件的区间,并在其中进行正常的线段树二分。时间复杂度 \(1\log\)。
B
对所有 \(T_i\) 建立 AC 自动机,于是它形成了一个有向图。在这张图上进行概率 dp,设 \(f_{i}\) 为从 \(i\) 到任意一个终末位置的期望步数。那么 \(f_{i}=\begin{cases}1+\sum\limits_{(i,v)\in E} p_{i\to v}f_v & i\ \text{不是终末位置} \\ 0 & i\ \text{是终末位置}\end{cases}\)。
最暴力的做法就是对这 \(nm\) 个未知数进行高斯消元。时间复杂度 \(O(n^3m^3)\)。
对它的优化可以用主元法。即,我们用一些未知数来表示其他所有的未知数,最后通过一些相等关系来列方程。
主元法
在一个 \(n\times n\) 的网格图中随机游走,从第 \(1\) 列中的任意一点开始,问走到第 \(n\) 列的期望步数。
直接做需要 \(n^2\) 个未知数。考虑用未知数 \(x_i\) 表示从第 \(x\) 行第 \(1\) 列开始走的期望步数,\(y_i\) 表示从第 \(y\) 行第 \(2\) 列开始走的期望步数,那么 \(x_i=\dfrac{1}{3}(x_{i-1}+x_{i+1}+y_i)+1\)。于是 \(y_i\) 可以用 \(x_i,x_{i-1},x_{i+1}\) 表示。后面的列同理。
递推到最后一行的时候,它形成了 \(n\) 个关于 \(x_i\) 的方程,于是高斯消元即可。时间复杂度 \(O(n^3)\)。
杂题 1
首先,\(n\) 个 \([1,m]\) 之间的随机整数的期望最大值是 \(m-\dfrac{m}{n}\)。因为 \(m\) 一定大于 \(\max\{b_i\}\),所以从这个最大值开始枚举。对于每个枚举的 \(m\),有方程 \(\sum a_i + nk\equiv \sum b_i \pmod m\),所以 \(k\) 可以解出来。