zoukankan      html  css  js  c++  java
  • 7.14复习笔记

    一、线段树优化建图

    线段树优化建图可以用来优化区间向单点,单点向区间,区间向区间连边的问题,可以将边数从(qn)级别降至(qlogn)级别

    具体的引入两道题完全包含上述所说的问题:

    1. CF786B Legacy

    先建出一颗出树一颗入树(不同题下不一定两颗树都要建)

    void build(int &x,int l,int r,int op){
    	if (l == r){x = l;return;}
    	x = ++tot;
    	int mid = (l+r >> 1);
    	build(ls[x],l,mid,op),build(rs[x],mid+1,r,op);
    	if (op == 1) ed.add(x,ls[x],0),ed.add(x,rs[x],0);
    	if (op == 2) ed.add(ls[x],x,0),ed.add(rs[x],x,0);
    }
    

    然后是区间向单点和单点向区间连边,这两个操作可以看成是互逆的

    void modify(int x,int l,int r,int nl,int nr,int p,int w,int op){
    	if (nl <= l&&r <= nr){
    		if (op == 2) ed.add(p,x,w);
    		if (op == 3) ed.add(x,p,w);
    		return;
    	}
    	int mid = (l+r >> 1);
    	if (nl <= mid) modify(ls[x],l,mid,nl,nr,p,w,op);
    	if (nr > mid) modify(rs[x],mid+1,r,nl,nr,p,w,op);
    }
    

    2.[PA2011]Journeys

    这道题就是剩下的那个区间向区间连边了

    我们可以每一条边都建一个虚点,从区间向虚点连边,从虚点向区间连边,双向边连两次

    注意:一定要看好出树和入树的连边方向,从出树连出来连向入树

    void modify(int x,int l,int r,int nl,int nr,int op){
    	if (nl <= l&&r <= nr){
    		if (op == 2) ed.add(x,tot,1);
    		if (op == 1) ed.add(tot,x,1);
    		return;
    	}
    	int mid = (l+r >> 1);
    	if (nl <= mid) modify(ls[x],l,mid,nl,nr,op);
    	if (nr > mid) modify(rs[x],mid+1,r,nl,nr,op);
    }
    

    二、最短路

    既然上面写线段树优化建图的时候都把Dij给写了,那我就直接再复习一下最短路吧

    三种最短路的板子:

    void Dij(int s){
    	priority_queue<pair<int,int> > q;q.push(make_pair(0,s));
    	for (int i = 1;i <= tot;i++) dis[i] = inf,vis[i] = 0;
    	dis[s] = 0;
    	while (!q.empty()){
    		int x = q.top().second;q.pop();
    		if (vis[x]) continue;
    		vis[x] = 1;
    		for (int i = ed.head[x];i;i = ed.nxt[i]){
    			int to = ed.to[i];
    			if (dis[to] > dis[x]+ed.w[i]){
    				dis[to] = dis[x]+ed.w[i];
    				q.push(make_pair(-dis[to],to));
    			}
    		}
    	}
    }
    void SPFA(){
    	queue<int> q;q.push(s);
    	for (int i = 1;i <= n;i++) dis[i] = inf,vis[i] = 0;
    	vis[1] = 1,dis[1] = 0;
    	while (!q.empty()){
    		int x = q.front();q.pop();
    		vis[x] = 0;
    		for (int i = head[x];i;i = ed[i].nxt){
    			int to = ed[i].to;
    			if (dis[to] > dis[x]+ed[i].w){
    				dis[to] = dis[x]+ed[i].w;
    				if (!vis[to]) q.push(to);
    			}
    		}
    	}
    }
    void Floyd(){
    	for (int k = 1;k <= n;k++){
    		for (int i = 1;i <= n;i++){
    			for (int j = 1;j <= n;j++){
    				dis[i][j] = min(dis[i][k]+dis[k][j],dis[i][j]);
    			}
    		}
    	}
    }
    

    一些问题:

    1.从1号点出发,到每个点的最短路有多少条?

    跟dp一样,在转移的时候统计方案就好了

    if (dis[to] == dis[x]+ed.w[i]) dp[to] += dp[x];
    else if (dis[to] > dis[x]+ed.w[i]){
    	dis[to] = dis[x]+ed.w[i];
    	dp[to] = dp[x];
    	q.push(make_pair(-dis[to],to));
    }
    

    2.给定一条边e,求有多少条经过边e的从1到n的最短路?

    经过这条边的方式有两种:从u走到v和从v走到u

    解决这个问题首先我们需要知道边是否在最短路上,看dis的差值等不等于边权就好了

    如果这条边在最短路上,结合问题1,先走的点有多少条最短路,这条边就有多少条最短路

    3.给定一条边e,请问这条边是否一定在从1到n的最短路上?

    结合问题2,看最短路条数是否为1

    特殊最短路

    BFS(边权全一样),01 BFS(边权只有0和1),多源Dijkstra

    1.01BFS

    考虑正常的BFS,我们可以想象到,其实就是在维护一个距离单调的队列,01BFS也是一样,只是队列中只有x和x+1

    因为我们每次边权只会加上0或者1,加上0不变,他还是最小的直接塞到头,加上1变大塞到末尾

    2.多源Dijkstra

    可以建一个虚点,虚点向每一个起点连一条边权为0的边,然后跑最短路

    但是我们发现这样没什么用,我们可以直接在入队的时候把所有源点加进队列里然后跑最短路

    三、exgcd

    我都有点忘了怎么求的了,虽然我一直都不会推导。

    扩展欧几里得算法(其实过程和gcd很类似),可以求解类似(ax+by = c)的二元方程的一组解,其中(gcd(a,b)|c)

    我们在具体做题的时候可以分成以下几个流程:

    1. 写出同余方程,找到(a,b,c)(一般b是模数)

    2. 然后(a)(b)同时除以(gcd(a,b)),套模板求exgcd

    3. 最后乘上(c)/(gcd(a,b)),并对(b)取模

    void exgcd(int a,int b,int &x,int &y){
    	if (!b){x = 1,y = 0;return;}
    	exgcd(b,a%b,y,x);
    	y -= (a/b)*x;
    }
    

    四、可持久化数据结构

    这里主要是讲一下可持久化线段树,也叫主席树

    主席树对比与线段树的一个优点是他可以查询历时版本,而且还运用了前缀和的思想,注意这里提醒我们需要前缀和可以参考主席树

    看一道例题,这是我一次模拟题的部分分出成的一道简化版:

    Rmq Problem / mex

    找区间内最小的没有出现过的自然数

    每一次新加入一个数,我们就可以值域线段树维护每个数最晚的位置,然后区间取min

    每次查询对于一个区间的右端点,找到在这个历史版本下,最大的最晚出现位置<l的那个数

    其实转化问题比较重要吧,对于一次查询我们只需要他以及他之前的数的信息,也可以看成一个前缀和?

    #include <iostream>
    #include <algorithm>
    #include <cstring>
    #include <cstdio>
    #define B cout<<"Breakpoint"<<endl;
    #define O(x) cout<<#x<<" "<<x<<endl;
    #define o(x) cout<<#x<<" "<<x<<" ";
    using namespace std;
    int read(){
    	int x = 1,a = 0;char ch = getchar();
    	while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();}
    	while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
    	return x*a;
    }
    const int maxn = 1e7+10,inf = 1e9+7;
    int T,n,q;
    int tot;
    struct node{
    	int l,r,val;
    }tree[maxn];
    int b[maxn];
    int root[maxn];
    void pushup(int x){
    	tree[x].val = min(tree[tree[x].l].val,tree[tree[x].r].val);
    }
    void modify(int &x,int lst,int l,int r,int p,int k){
    	if (!x) x = ++tot;
    	if (l == r) {tree[x].val = k;return;}
    	int mid = (l+r >> 1);
    	if (p <= mid) tree[x].r = tree[lst].r,modify(tree[x].l,tree[lst].l,l,mid,p,k);
    	else tree[x].l = tree[lst].l,modify(tree[x].r,tree[lst].r,mid+1,r,p,k);
    	pushup(x);
    }
    int query(int x,int l,int r,int k){
    	if (l == r) return l;
    	int mid = (l+r >> 1);
    	if (tree[tree[x].l].val < k) return query(tree[x].l,l,mid,k);
    	else return query(tree[x].r,mid+1,r,k);
    }
    int a[maxn];
    int main(){
    //	freopen("mexe2-1.in","r",stdin);
    //	freopen("out.out","w",stdout);
    	T = read(),n = read(),q = read();
    	int lst = 0;
    	for (int i = 1;i <= n;i++) a[i] = read();
      	for (int i = 1;i <= n;i++) modify(root[i],root[i-1],0,n,a[i],i);
    //	debug(1,1,n);
    	for (int i = 1;i <= q;i++){
    		int op = read(),x = read(),y = read();
    		x = (x+T*lst) % n+1,y = (y+T*lst) % n+op;
    //		cout<<x<<" "<<y<<endl;
    		if (x > y) swap(x,y);
    		lst = query(root[y],0,n,x);
    		printf("%d
    ",lst);
    	}
    	return 0;
    } 
    /*
    0
    3 2
    0 1 2
    1 0 2
    1 1 2
    */
    

    五、斜率优化

    斜率优化的条件:

    1.只在一维转移

    2.出现了(i imes j)这一项

    3.满足决策单调性

    斜率优化的流程:

    1.写出dp转移方程

    2.找出对应的x((i imes j)项里的j),y(只含j的项),k((i imes j)项里的i)

    是不是看起来还挺简单的?找一道例题:

    「SDOI2016」征途

    通过一系列的推导,我们可以知道求方差最小,也就是求平方和最小

    定义状态:(dp[i][j])表示在前j个物品里分成i组的最小方差

    状态转移:(dp[i][j] = min(dp[i-1][k]+w(k,j),dp[i][j]))

    枚举上一组的最后一个物品,其中(w(i,j) = (sum[j]-sum[i])^2)

    发现第一维没有转移可以暂时去掉他,然后我们把dp方程展开:

    (dp[i] = dp[j]+sum[i]^2+2 imes sum[i] imes sum[j]+sum[j]^2)

    此时我对应的(x = sum[j],y = dp[j]+sum[j]^2,k = 2 imes sum[i]),就可以斜率优化了

    double X(int x){
    	return (double)sum[x];
    }
    double Y(int id,int x){
    	return (double)(dp[id][x]+sum[x]*sum[x]);
    }
    double slope(int id,int a,int b){
    	return (Y(id,b)-Y(id,a))/(X(b)-X(a));
    }
    int main(){
    	n = read(),m = read();
    	for (int i = 1;i <= n;i++) a[i] = read();
    	for (int i = 1;i <= n;i++) sum[i] = sum[i-1]+a[i];
    	for (int i = 1;i <= n;i++) dp[1][i] = sum[i]*sum[i];
    	for (int j = 2;j <= m;j++){
    		int head = 1,tail = 1;
    		for (int i = 1;i <= n;i++){
    			while (head < tail&&slope(j-1,q[head],q[head+1]) <= 2*sum[i]) head++;
    			dp[j][i] = dp[j-1][q[head]]+(sum[i]-sum[q[head]])*(sum[i]-sum[q[head]]);
    			while (head < tail&&slope(j-1,q[tail],i) <= slope(j-1,q[tail-1],q[tail])) tail--;
    			q[++tail] = i;
    		}
    	}
    	printf("%d
    ",m*dp[m][n]-sum[n]*sum[n]);
    	return 0;
    } 
    

    六、决策单调性优化

    今天上课学到的:一般大于1次的方程转移都是满足决策单调性的

    决策单调性优化分为两种

    1.每个阶段的被决策点不会成为决策点

    这类问题的dp状态通常是二维的,就比如上面的问题

    假设被决策点的范围在[l,r]之间,决策点的范围在[nl,nr]之间

    对于每一个选取的区间的mid,我们需要找到他的决策点然后继续向下分治

    void solve(int x,int l,int r,int nl,int nr){
    	if (l > r) return;
    	int mid = (l+r >> 1),pos;
    	for (int i = nl;i <= min(mid,nr);i++){
    		int w = dp[x-1][i]+(sum[mid]-sum[i])*(sum[mid]-sum[i]);
    		if (w < dp[x][mid]) dp[x][mid] = w,pos = i;
    	} 
    	solve(x,l,mid-1,nl,pos),solve(x,mid+1,r,pos,nr);
    }
    

    2.每个阶段的被决策点可能成为决策点

    很简单,我们分治套分治就好了,因为cdq分治的时候我们就是在考虑[l,mid]对[mid+1,r]的贡献,思路很相符

    因为我自己对分治的理解就不是特别深入,所以讲的就比较粗略

    void solve(int l,int r,int nl,int nr){
    	if (l > r||nl > nr) return;
    	int mid = (l+r >> 1),pos,lst = inf;
    	for (int i = nl;i <= min(mid,nr);i++){
    		int w = calc(i,mid);
    		if (w < lst) lst = w,pos = i;
    	}
    	dp[mid] = min(dp[mid],lst);
    	solve(l,mid-1,nl,pos),solve(mid+1,r,pos,nr); 
    }
    void cdq(int l,int r){
    	if (l == r) return;
    	int mid = (l+r >> 1);
    	cdq(l,mid),solve(mid+1,r,l,mid);
    	cdq(mid+1,r);
    }
    

    七、单调队列优化dp

    本章的最后一个小结了!其实我本来都删掉了,但是良心不安啊,还是补上来了

    单调队列解决问题的标志: 规定长度内的最值

    单调队列优化的流程:

    1.一样先写出状态转移方程

    2.单调队列优化(太简陋了,但我也想不出来说啥好)

    int head = 1,tail = 1;
    for (int i = 1;i <= n;i++){
    	while (q[head]+k < i) head++;
    	dp[i] = dp[q[head]]+a[i];
    	while (head < tail&&dp[i] <= dp[q[tail]]) tail--;
    	q[++tail] = i; 
    }
    
  • 相关阅读:
    js扩展Date对象的方法,格式化日期格式(prototype)
    JSP中的普通路径写法
    工作方向与目标
    Cookie工具类(获取cookie,设置cookie)
    读取普通java web项目下的WEB-INF目录下的配置文件(application.xml,xx.properties等配置文件)
    js单选和全选
    exits 和no exits
    Linux配置nginux
    Java读取利用java.util类Properties读取resource下的properties属性文件
    vue-devtools vue调试工具
  • 原文地址:https://www.cnblogs.com/little-uu/p/15016246.html
Copyright © 2011-2022 走看看