集訓D1-3

wertyuio1發表於2024-08-18

集訓

DAY1 搜尋進階

因此在學習的時候主要以程式碼實踐為主(多做題)

深度優先搜尋(dfs)基礎

1.子集列舉
複雜度\(O(2^n)\)

2.排列列舉

複雜度\(O(n!)\)

Dfs的剪枝

1.最佳化搜尋順序

sort

列舉順序(正/倒)

2.排除等效冗餘

inline void dfs(int i,int len,int st){//P1120
	if(i==cnt+1)...
	if(len==tar)...
	for(int j=st/*限制單調*/;j<=n;j++){
		if(!vis[j]&&len+a[j]<=tar){
			...
			if(len==0||len+a[j]==tar)return;//排除等效冗餘
			j=nxt[j];
		}

	}
}
int main(){
    ...
	nxt[n]=n;
	for(int i=n-1;i>=1;i--){//避免重複
		if(a[i]!=a[i+1])nxt[i]=i;
		else nxt[i]=nxt[i+1];
	}
	...
}

3.可行性剪枝(上下界剪枝) 當前分支必定失敗

void dfs(int i,int lh,int lr,int sums,int sumv){//P1731 //P1025
	...
	if(sumv>=n)return;//可行性
	if(i*lr*lr*lh+sumv<n)return;
	if(i*2+sums>=ans)return;
	for(int j=lr-1;j>=i;j--)//上下界
		for(int k=lh-1;k>=i;k--)
			...
}

4.最優性剪枝 答案不會更優

if(...)return;//P10483 P1731

5.記憶化

迭代加深

搜尋規模隨搜尋層數深入增長很快,且能夠確保答案在較淺節點

思想:限制深度

bool dfs(int i){
	if(i==dep){
		return ...;
	}
	for...{if(...){return 1;}}
	return 0;
}
void solve(){
	dep=1;
	while(!dfs(1)){
		dep++;
	}
}//uva529

P1763

hack

雙向搜尋

具有明確的“初始狀態”和“終止狀態”

從初態和終態出發各搜一半狀態,產生兩個深度減半的狀態空間,在中間交會、組合成最終答案。\(O(2^\frac{n}{2})\)

//CF888E U311043
void dfs1(int i)...
void dfs2(int i)...
void solve()...
int main(){
    dfs1();dfs2();solve();
}

廣度優先搜尋(BFS)基礎

U311287

大模擬

struct傳參太多會TLE

3維狀態

U311289

多個源點同時入隊

BFS變形

1.雙端佇列BFS(0/1bfs)

deque

0費入隊首,1費入隊尾

2.優先佇列BFS

uva11367

定義狀態\((city,cost,left)\)

dijkstra轉移狀態

優先佇列使得\(cost\)最小

二維狀態判重

void solve(){
	int c=read(),s=read()+1,e=read()+1;
	memset(vis,0,sizeof vis);
	memset(f,0x3f,sizeof(f));
	f[s][0]=0;
	priority_queue<node>q;
	q.push(node{s,0,0});
	while(!q.empty()){
		node now=q.top();
		q.pop();
		if(now.u==e){
			cout<<now.cot<<"\n";
			return;
		}
		if(vis[now.u][now.res])continue;
		else vis[now.u][now.res]=1;
		if(now.res<c&&f[now.u][now.res+1]>now.cot+val[now.u]){
			f[now.u][now.res+1]=now.cot+val[now.u];
			q.push(node{now.u,f[now.u][now.res+1],now.res+1});
		}
		for(int j=0;j<(int)a[now.u].size();j++){
			int v=a[now.u][j].v,w=a[now.u][j].w;
			if(now.res>=w&&f[v][now.res-w]>now.cot){
				f[v][now.res-w]=now.cot;
				q.push(node{v,f[v][now.res-w],now.res-w});
			}
		}
	}
	puts("impossible");
}

3.雙向BFS

acwing177

大量細節

開兩個佇列q1,q2

每單位時間q1擴充三次,q2一次

若兩者有相同可到達點即輸出當前時間

擴充時打上第幾次標記

擴充時打vis標記

多測清空

判段是否在鬼區內

void bfs(){
	int tim=0,pre=1,pre2=1;
	memset(vis,0,sizeof(vis));
	memset(vis2,0,sizeof(vis2));
	queue<node>q1;
	queue<node>q2;
	q1.push(node{bx,by,1});
	q2.push(node{gx,gy,1});
	while((!q1.empty())&&(!q2.empty())){
		tim++;
		for(int i=1;i<=3;i++){
			while(!q1.empty()&&q1.front().t==pre){
				node now=q1.front();
				q1.pop();
				if(!check(now.x,now.y,tim))continue;
				for(int j=0;j<4;j++){
					int tx=now.x+dx[j],ty=now.y+dy[j];
					if(tx>0&&tx<=n&&ty>0&&ty<=m&&check(tx,ty,tim)&&a[tx][ty]!='X'){
						if(vis2[tx][ty]){
							cout<<tim<<"\n";
							return;
						}
						if(vis[tx][ty])continue;
						else vis[tx][ty]=1;
						q1.push(node{tx,ty,pre+1});
					}
				}
			}
			pre++;
		}	
		while(!q2.empty()&&q2.front().t==pre2){
			node now=q2.front();
			q2.pop();
			if(!check(now.x,now.y,tim))continue;
			for(int j=0;j<4;j++){
				int tx=now.x+dx[j],ty=now.y+dy[j];
				if(tx>0&&tx<=n&&ty>0&&ty<=m&&check(tx,ty,tim)&&a[tx][ty]!='X'){
					if(vis[tx][ty]){
						cout<<tim<<"\n";
						return;
					}
					if(vis2[tx][ty])continue;
					else vis2[tx][ty]=1;
					q2.push(node{tx,ty,pre2+1});
				}
			}	
		}
		pre2++;
	}
	puts("-1");
}

DAY2-3 資料結構提高

單調棧

//P5788
stack<int>st;
for(int i=1;i<=n;i++){
    while(!st.empty()&&h[i]>h[st.top()])st.pop();
    st.push(i);
}
while(!st.empty())st.pop();

分治+單調棧

某計蒜客題

P1823

在單調棧內二分

SP1805

經典

單調佇列

deque<int>q;
for(int i=1;i<=n;i++){
    while(!q.empty()&&q.front()<i-m)q.pop_front();
	...
    while(!q.empty()&&a[q.back()]>=a[i])q.pop_back();
    q.push_back(i);
}//P1440

P2216

二維

void push(int x){//P3378
	heap[++len]=x;
	int i=len;
	while(i>1&&heap[i/2]>heap[i]){
		swap(heap[i/2],heap[i]);
		i=i/2;
	}
}
void pop(){
	heap[1]=heap[len--];
	int i=1;
	while(i*2<=len){
		int son=i*2;
		if(son+1<=len&&heap[son]>heap[son+1])son++;
		if(heap[son]<heap[i]){
			swap(heap[son],heap[i]);
			i=son;
		}else break;
	}
}
int ask(){return heap[1];}

左偏樹

\(dist\)定義:一個節點到子樹中最近的外節點所經過的邊的數量,\(dist[0]=-1\)

維護\(dist\)以維護左偏性質

左偏樹的深度沒有保證

int find(int i){return f[i]==i?i:f[i]=find(f[i]);}//P3377
int merge(int i,int j){
	if(!i||!j)return i|j;//節點為0
	if(a[j].val<a[i].val)swap(i,j);//維護堆
	a[i].rs=merge(a[i].rs,j);//遞迴合併右子和另一個堆
	if(a[a[i].rs].dist>a[a[i].ls].dist)swap(a[i].ls,a[i].rs);//維護左偏
	a[i].dist=a[a[i].rs].dist+1;//維護dist
	return i;//返回節點
}
void init(){
    a[0].dist=-1;
		scanf("%d",&a[i].val);
		a[i].id=f[i]=i;
		a[i].ls=a[i].rs=a[i].dist=a[i].dead=0;
}
int main(){
	//合併 插入
        if(a[x].dead||a[y].dead)continue;
        int fx=find(x),fy=find(y);
        if(fx!=fy)f[fx]=f[fy]=merge(fx,fy);
	//刪除根
        if(a[x].dead)continue;
        int fx=find(x);a[fx].dead=1;
        f[fx]=f[a[fx].ls]=f[a[fx].rs]=merge(a[fx].ls,a[fx].rs);
}

P4331

基礎 STL

注意:

1.區間左閉右開 [,)

2.deque->MLE

pair

預設排序先first後second

vector

t.emplace_back()//新插入方式
t.resize()//重置大小
//定點插入刪除元素
v.insert(a.begin()+i,j)//在v的第i個元素位置插入j(從0開始)
v.erase(a.begin(),a.begin()+i)//刪除 v 的 第0~第i-1個元素

set (multiset)

去重(不去重)

s.erase(pos)//刪除pos迭代器所指向的元素,返回下一個迭代器的位置
s.erase(x)//為刪除set中值為x的元素
//注意:multiset執行該操作會刪除所有=x的元素,如果只想刪除一個則應用s.erase(s.find(x))
s.find(x)//查詢 x 元素是否存在,如果存在返回該元素的迭代器,若不存在,返回s.end(),用法 auto pos=s.find(x)
s.count(x)//查詢 x 元素的個數,用於 multiset,複雜度很高,為O(ans+logn)
s.lower_bound(x)//返回第一個 >= x 的元素的迭代器
s.upper_bound(x)//返回第一個 > x 的元素的迭代器

nth_element()

平均O(n)的時間複雜度,將陣列中第k小的元素放到第k個位置上,同時保證左邊元素全部小於它,右邊元素全部大於它。

//用法 (a陣列下標從1開始,將第i小的元素放到第i個位置)
nth_element(a+1,a+i,a+n+1,cmp);

lower_bound()

返回第一個>=x位置下標

upper_bound()

返回第一個>x位置下標

使用前保證單調性!sort

//用法
int p=lower_bound(a+1,a+n+1,x,cmp);

map

map<T,T>mp;//T必須已定義大小比較

bitset

最佳化傳遞閉包,揹包 \(O(\frac{n}{w})\)

bitset<size>b;

樹狀陣列

1.單點修改,區間查詢

int lowbit(int i){return i&(-i);}//P3374
int sum(int i){
	int ret=0;
	while(i){
		ret+=c[i];
		i-=lowbit(i);
	}
	return ans;
}
void add(int i,int val){
	while(i<=n){
		c[i]+=val;
		i+=lowbit(i);
	}
}

2.區間修改,單點查詢

維護原序列的差分序列

線段樹

注意:四倍空間

void pushup(int p){
	...
}
void update(int p,int pl,int pr,...){
	if(pl==pr){
		...
        return;
	}
	if(...)update(ls,pl,mid,...);
	if(...)update(rs,mid+1,pr,...);
	pushup(p);
}
int query(int l,int r,int p,int pl,int pr){
	if(l<=pl&&pr<=r){
		return ...;
	}
	int ret=0;
	if(l<=mid)...
	if(r>mid)...
	return ret;
}

P4513

經典

LOJ6029

除法轉化為減法,暴力更新

void updatediv(int l,int r,int p,int pl,int pr,int k){
	if(l<=pl&&pr<=r){
		ll n1=floor(1.0*t[p].maxn/k)-t[p].maxn;
		ll n2=floor(1.0*t[p].minn/k)-t[p].minn;
		if(pl==pr){
			t[p].sum=t[p].maxn=t[p].minn=floor(1.0*t[p].minn/k);
		}else if(n1==n2){
			t[p].tag+=n1;
			t[p].maxn+=n1;
			t[p].minn+=n1;
			t[p].sum+=n1*(pr-pl+1);
		}else{
			pushdown(p,pl,pr);
			updatediv(l,r,ls,pl,mid,k);
			updatediv(l,r,rs,mid+1,pr,k);
			pushup(p);
		}
		return;
	}
	pushdown(p,pl,pr);
	if(l<=mid)updatediv(l,r,ls,pl,mid,k);
	if(r>mid)updatediv(l,r,rs,mid+1,pr,k);
	pushup(p);
}

不開long long見祖宗

P3369

值域線段樹&動態開點線段樹

void pushup(int p){
	val[p]=val[ls]+val[rs];
}
void add(int &p,int pl,int pr,int pos,int k){//數pos的個數增加k
	if(!p)p=++cnt;
	if(pl==pr){
		val[p]+=k;
		return;
	}
	if(pos<=mid)add(ls,pl,mid,pos,k);
	else if(pos>mid)add(rs,mid+1,pr,pos,k);
	pushup(p);
}
int query(int l,int r,int p,int pl,int pr){//返回[l,r]數的個數
	if(!p)return 0;
	if(l<=pl&&pr<=r)return val[p];
	int ans=0;
	if(l<=mid)ans+=query(l,r,ls,pl,mid);
	if(r>mid)ans+=query(l,r,rs,mid+1,pr);
	return ans;
}
int getrank(int k,int p,int pl,int pr){//查詢第k大的數
	if(pl==pr)return pl;
	if(val[ls]>=k)return getrank(k,ls,pl,mid);
	else return getrank(k-val[ls],rs,mid+1,pr);
}
//插入一個數x
add(rt,-lim,lim,x,1);
//刪除一個數x(若有多個相同的數,應只刪除一個)
add(rt,-lim,lim,x,-1);
//定義排名為比當前數小的數的個數+1,查詢x的排名
query(-lim,x-1,rt,-lim,lim)+1
//查詢資料結構中排名為x的數
getrank(x,rt,-lim,lim)
//求x的前驅(前驅定義為小於x,且最大的數)
int rk=query(-lim,x-1,rt,-lim,lim);
getrank(rk,rt,-lim,lim)
//求x的後繼(後繼定義為大於x,且最小的數)
int rk=query(-lim,x,rt,-lim,lim)+1;
getrank(rk,rt,-lim,lim)

P4198

序列\(a\),可修改,查詢滿足\({\forall}j{\in}[1,i-1],a[i]>a[j]\)\(i\)個數

複雜度\(O(nlog^2n)\)

int query(int p,int pl,int pr,double K){//返回p節點內>k的個數
	if(pl==pr){
		return t[p].maxn>K;
	}
	if(t[ls].maxn>K)return query(ls,pl,mid,K)+t[p].len-t[ls].len;//遞迴左子+右子貢獻
	else return query(rs,mid+1,pr,K);//左子無貢獻,遞迴右子
}
void pushup(int p,int pl,int pr){
	t[p].maxn=max(t[ls].maxn,t[rs].maxn);
	t[p].len=t[ls].len+query(rs,mid+1,pr,t[ls].maxn);//左子不受影響+右子>左子最大值
}
void update(int pos,int p,int pl,int pr,double K){
	if(pl==pr){
		t[p].len=1;
		t[p].maxn=K;
		return;
	}
	if(pos<=mid)update(pos,ls,pl,mid,K);
	else update(pos,rs,mid+1,pr,K);
	pushup(p,pl,pr);
}
update(x,1,1,n,tmp);
t[1].len

P4556

值域線段樹+樹上差分+線段樹合併

int merge(int p,int i,int pl,int pr){//合併
	if(!p||!i)return p|i;
	if(pl==pr){
		...
		return p;
	}
    //合併左右子樹
	ls=ch[i][0]=merge(ls,ch[i][0],pl,mid);
	rs=ch[i][1]=merge(rs,ch[i][1],mid+1,pr);
	pushup(p);
	return p;
}
void getans(int i,int fa){//自下而上合併
	for(int j=0;j<a[i].size();j++){
		int v=a[i][j];
		if(v!=fa){
			getans(v,i);
			rt[i]=rt[v]=merge(rt[i],rt[v],1,1e5);//更改根節點
		}
	}
    //統計答案
	ans[i]=maxn[rt[i]];
	if(!sum[rt[i]])ans[i]=0;//特判
}

P4197

線段樹合併

在每個點建一顆線段樹,加入離散化後的點權

離線詢問,按\(x\)排序

將邊按邊權排序,列舉每個詢問,加邊,合併線段樹

注意陣列大小

輸出回車

CF786B

線段樹最佳化建圖

一棵入樹,一棵出樹,節點標號偏移\(D\)

\(D\)由資料範圍確定

void build(int p,int pl,int pr){
	if(pl==pr){
		a[pl]=p;//獲取節點i對應的樹上節點a[i]
		return;
	}
    //出樹向父節點連邊
	e[ls+D].push_back(edge{p+D,0});
	e[rs+D].push_back(edge{p+D,0});
	//入樹向子節點連邊
    e[p].push_back(edge{ls,0});
	e[p].push_back(edge{rs,0});
	build(ls,pl,mid);
	build(rs,mid+1,pr);
}
void connect(int l,int r,int v,ll w,int p,int pl,int pr,int op){
	if(l<=pl&&pr<=r){
        //出樹向入樹連邊
		if(op)e[p+D].push_back(edge{a[v],w});
		else e[a[v]/*注意轉換*/+D].push_back(edge{p,w});
		return;
	}
	if(l<=mid)connect(l,r,v,w,ls,pl,mid,op);
	if(r>mid)connect(l,r,v,w,rs,mid+1,pr,op);
}
void init(){
    build(1,1,n);
	for(int i=1;i<=n;i++){
        //出樹和入樹的葉子節點是同一節點
		e[a[i]/*注意轉換*/].push_back(edge{a[i]/*注意轉換*/+D,0});
		e[a[i]/*注意轉換*/+D].push_back(edge{a[i]/*注意轉換*/,0});
	}
}
//單點
e[a[v]+D].push_back(edge{a[u],w});
//單點->區間&區間->單點
connect(l,r,v,w,1,1,n,op%2);
//注意圖上節點向樹上節點編號轉換 i->a[i] 在dijkstra時以及答案統計和輸出時

trie

P5283 擺

P4551

void insert(int s){
	int p=0;
	for(int i=(1<<30);i>0;i>>=1){
		bool t=s&i;//注意bool
		if(!ch[p][t])ch[p][t]=++id;
		p=ch[p][t];
	}
	cnt[p]++;
}
int ask(int s){
	int p=0,ret=0;
	for(int i=(1<<30);i>0;i>>=1){
		bool t=s&i;
		if(ch[p][!t]){
			ret+=i;
			p=ch[p][!t];
		}else p=ch[p][t];
	}
	return ret;
}
//d[i]為樹上異或字首
insert(d[i]);
ans=max(ans,ask(d[i]));