Loading

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\) 可以解出来。

posted @ 2021-06-03 16:33  Alan_Zhao_2007  阅读(62)  评论(0编辑  收藏  举报