11/27周总结

这周按照上周的安排学习线段树,线段树区别于CDQ分治,CDQ 分治的核心思想就是将修改和询问丢一块,然后左右区间合并上来的时候考虑左边区间对右边区间的贡献。而 CDQ 分治处理的修改一般都是永久性的,不可撤销

有时,进行的一种操作可以快速完成,但这种操作的逆操作难以实现。比如,维护一些连通性,或直径等问题。这类问题加边很好做,但删边很难实现。但是若题目中需要加边,删边,查询,且支持离线,可以采用如下做法:线段树分治,将每个添加操作的有效区间按在线段树上,然后遍历这颗线段树同时处理标记即可。

而一般来说,需要利用线段树解题都有其特征,一般都是给你若干操作,每一个操作的作用时间范围为 [l,r],然后让你求每一个时刻下 (题目要求) 的结果,或是没有说明操作时间,我们以操作个数为时间,当某些操作完全相同,则可以合并。这样的话如果进行询问,只需要DFS就可以。而且关于线段树,我看的博客里也基本都是题目的集合,以题为主,学习线段树也要基于题目,关于优化建树方面,这个dalao的博客写的很详细另外就是板子,跟着博客复现:

#include<bits/stdc++.h>
#define INF 0x7fffffff
const int N=200010;
using namespace std;
 
inline int read(){
	char ch=getchar();int x=0,f=1;
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
} 
//180W点 520W条边 n+m 20W 
struct edge{
	int v,nxt,w;
}e[N*40];
int head[N<<4],cnt;
long long inf;
int n,m,s;
 
void add(int u,int v,int w){
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
 
int tin[N<<4],tout[N<<4],temp;
long long dis[N<<4];
 
void build(int k,int l,int r){
	if(l==r){
		tin[k]=tout[k]=l;
		return ;
	}
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	tin[k]=++temp;tout[k]=++temp;
	add(tout[k<<1],tout[k],0);
	add(tout[k<<1|1],tout[k],0);
	add(tin[k],tin[k<<1],0);
	add(tin[k],tin[k<<1|1],0);
}
 
void update1(int k,int l,int r,int L,int R,int u,int w){
	if(L<=l&&r<=R){
		add(u,tin[k],w);
		return ;
	}
	int mid=(l+r)>>1;
	if(mid>=L){
		update1(k<<1,l,mid,L,R,u,w);
	}
	if(mid<R){
		update1(k<<1|1,mid+1,r,L,R,u,w);
	}
}
 
void update2(int k,int l,int r,int L,int R,int u,int w){
		if(L<=l&&r<=R){
		add(tout[k],u,w);
		return ;
	}
	int mid=(l+r)>>1;
	if(mid>=L){
		update2(k<<1,l,mid,L,R,u,w);
	}
	if(mid<R){
		update2(k<<1|1,mid+1,r,L,R,u,w);
	}
}
 
struct node{
	int u;
	long long d;
	bool operator<(const node&h)const{return d>h.d;};
};
 
void dij(){
	memset(dis,0x3f,sizeof(dis));
	inf=dis[1];
	priority_queue<node> q;
	dis[s]=0;
	q.push((node){s,0});
	while(!q.empty()){
		node now=q.top();
		int u=now.u;
		long long d=now.d;
		q.pop();
		if(d!=dis[u])continue;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v,w=e[i].w;
			if(dis[v]>dis[u]+w){
				dis[v]=dis[u]+w;
				q.push((node){v,dis[v]});
			}
		}
	}
}
 
int main(){
	
	n=read(),m=read(),s=1;
	temp=n+m; 
	
	build(1,1,n+m);
	for(int i=1;i<=m;i++){
		int l=read(),r=read(),w=read();
		update1(1,1,n+m,l,r,n+i,0);
		update2(1,1,n+m,l,r,n+i,w);
	}
	dij(); 
	//cout<<inf<<endl;
	if(dis[n]!=inf){
		printf("%lld ",dis[n]);
	}
	else printf("-1 ");
	return 0;
}

这里有一个紫题的模版题,题目可以看作,给你 M个操作,每次在时间 [l,r]内加上一条边,并且这里每一条边都是不同的。符合使用线段树的条件,这里贴部分关键操作的代码。

const int  MAX = 1e5+7;
typedef pair<int, int> pii;
struct UnionFind {
private:
    int rk[MAX << 1], pre[MAX << 1], totNode;
    stack<pii> st;//记录操作
public:
    void init(int tot) {
        totNode = tot;
        for (int i = 1; i <= totNode * 2; i++)//将种类并查集分成两类
            pre[i] = i, rk[i] = 1;
    }
    int find(int x) 
    { 
        while (x ^ pre[x]) //^运算可加快代码速度
        x = pre[x]; 
        return x; 
    }
    void merge(int x, int y) //开始按秩合并
    {
        x = find(x), y = find(y);
        if (x == y) 
        return;
        if (rk[x] < rk[y]) 
        swap(x, y);

        st.push(make_pair(y, rk[x]));//将操作存在栈中
        pre[y] = x, rk[x] += rk[x] == rk[y];
    }
    int start() 
    { return st.size(); }//当前点开始时栈中操作数
    void end(int last) //撤回merge操作
    {
        while (st.size() > last) 
        {//一直到最开始为止
            pii tp = st.top();
            //这里按之前合并的时候反过来写就行了
            rk[pre[tp.first]] -= tp.second;
            pre[tp.first] = tp.first;
            st.pop();
        }
    }
} uf;

void insert(int u, int l, int r, int ql, int qr, pii k) {//将加入的边插入[ql, qr]时间段即可
    if (ql <= l && r <= qr) {
        t[u].push_back(k);
        return;
    }
    if (ql <= m) 
    insert(lc, l, m, ql, qr, k);
    if (qr > m) 
    insert(rc, m + 1, r, ql, qr, k);
}

void dfs(int u, int l, int r) {
    int now = uf.start(), flag = 0;//now记录最开始栈中元素个数
    for (auto &i: t[u]) 
    {
        int x = i.first, y = i.second;
        //将x和y分成两种不同的类
        if (uf.find(x) == uf.find(y)) //如果x和y已经是同类了, 那么就有冲突了
        {
            flag = 1;
            break;
        }
        //将x和y分成两种不同的类
        uf.merge(x, y + N); uf.merge(y, x + N);
    }
    if (flag) 
    for (int i = l; i <= r; i++) 
    cout<<"no"<<endl;//有冲突,那么显然他的子树都不用在看了,肯定有冲突了
    else if (l == r) 
    cout<<"yes"<<endl;//如果到达子树都没有冲突,说明可以
    else 
    dfs(lc, l, m), dfs(rc, m + 1, r);
    uf.end(now);//每次结束都撤回操作
}

转眼11月竟然快过去了,总结一下这个月都码了什么、学了什么。新的算法知识主要是莫队算法,CQD分治、主席树和线段树,另外就是写了一些最短路之类的题,还有复习基本的算法知识用Java实现,进行一个知识迁移。下周继续补充线段树的题库,线段树、主席树之类的题目还真得多看题,才能在遇到的时候能想到是用这钟方式来解决。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值