10.28记
好,today is over!
梳理一下学长讲的
1.求解LCA
首先是求LCA,有倍增法,树链剖分,欧拉序都可以求解LCA(码风我看着很舒服,整齐就完事了)
倍增法:
#include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define N 500000 #define M 500000 using namespace std; const int mod = 1e9+7; const int inf = 0x3f3f3f3f; inline int read() { char c = getchar(); int x = 0, f = 1; for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1; for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48); return x * f; } int n,m,s; //====================================== int f[N][30],dep[N]; struct node { int nxt,to; }edge[M<<1];//好几次,都是70分,直到学长让我看看范围,然后问我,树是无向图,你为什么不建双向边,题目给定是一颗树,给定的数据又非节点到他的儿子; int number_edge,head[N]; void add_edge(int u,int v)//经典存图 { number_edge++; edge[number_edge].nxt=head[u]; edge[number_edge].to=v; head[u]=number_edge; } void dfs(int u,int fa)//这个倍增实现的就是求深度,和找父亲节点 { f[u][0]=fa; dep[u]=dep[fa]+1; for(int i=1;(1<<i)<=dep[u];i++) { f[u][i]=f[f[u][i-1]][i-1]; } for(int i=head[u];i;i=edge[i].nxt) { if(edge[i].to!=fa) { dfs(edge[i].to,u); } } } int lca(int u,int v)//求解LCA { if(dep[u]>dep[v]) { swap(u,v); } for(int i=15;i>=0;i--) { if(dep[u]<=dep[v]-(1<<i)) { v=f[v][i]; } } if(u==v) { return u; } for(int i=15;i>=0;i--) { if(f[u][i]==f[v][i]) { continue; } else { u=f[u][i]; v=f[v][i]; } } return f[v][0]; } int main() { n=read(),m=read(),s=read(); for(int i=1;i<=n-1;i++) { int x=read(),y=read(); add_edge(x,y); add_edge(y,x); } dfs(s,0); for(int i=1;i<=m;i++) { int u=read(),v=read(); cout<<lca(u,v)<<endl; } return 0; }
树链剖分:
#include <iostream> #include <algorithm> #include <cstring> #include <cstdio> #include <queue> #include <cmath> using namespace std; const int maxn=500010; inline int read() { char c = getchar(); int x = 0, f = 1; for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1; for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48); return x * f; } //============================================== struct node { int nxt,to; }; node edge[maxn<<1]; int n,m,root,number_edge; int size[maxn],son[maxn],f[maxn],dep[maxn],head[maxn],top[maxn]; //============================================== void add_edge(int from,int to) { number_edge++; edge[number_edge].nxt=head[from]; edge[number_edge].to=to; head[from]=number_edge; } void dfs1(int now,int fath,int depth)//电风扇1(dfs1) 用来计算重儿子,父亲节点,和深度 { dep[now]=depth; f[now]=fath; size[now]=1; for(int i=head[now];i;i=edge[i].nxt) { if(edge[i].to==fath) { continue; } dfs1(edge[i].to,now,depth+1); size[now]+=size[edge[i].to]; if(size[edge[i].to]>size[son[now]]) { son[now]=edge[i].to; } } } void dfs2(int now,int top_)//求顶端,划分链为重链还是轻链 { top[now]=top_; if(son[now]==0) { return ; } dfs2(son[now],top_); for(int i=head[now];i;i=edge[i].nxt) { if(edge[i].to==f[now]||edge[i].to==son[now]) { continue; } dfs2(edge[i].to,edge[i].to); } } int lca(int a,int b)//求解LCA { while(top[a]!=top[b]) { if(dep[top[a]]<dep[top[b]]) { swap(a,b); } a=f[top[a]]; } return dep[a]<=dep[b]? a:b; } int main() { n=read(),m=read(),root=read(); for(int i=1;i<n;i++) { int x,y; cin>>x>>y; add_edge(x,y); add_edge(y,x); } dfs1(root,0,1); dfs2(root,root); while(m--) { int a=read(),b=read(); cout<<lca(a,b)<<endl; } return 0; }
3.欧拉序求LCA
想法就是
先求出LCA,然后找到节点 x,y的区间,即[x,y]就这个,找里面深度最浅的点就是LCA(以前没写过,很快,但是很鸡肋,除了求解LCA,貌似没有别的什么用处)
(这很明显,不是我的码风,对,就是学长的)
#include <cstdio> #include <cctype> #include <algorithm> #include <cmath> #define ll long long #define max std::max const int MARX = 1e5 + 10; //=========================================================== int N, M, Log2[MARX], MAX[MARX][25]; int node[MARX][25]; //=========================================================== inline int read() { int w = 0, f = 1; char ch = getchar(); for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1; for(; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); return f * w; } void Prepare() { //MAX[i][j]: [i,i+2^j] max_num N = read(), M = read(); for(int i = 1; i <= N; i ++) { MAX[i][0] = dep[i]; node[i][0] = i; } for(int i = 2; i <= N; i ++) { Log2[i] = Log2[i >> 1] + 1; } for(int i = 1; i <= 20; i ++) for(int j = 1; j + (1 << i) - 1 <= N; j ++) { if (MAX[j][i] < MAX[j + (1 << (i - 1))][i - 1]) { MAX[j][i] = MAX[j][i - 1]; node[j][i] = node[j][i - 1]; } else { MAX[j][i] = MAX[j + (1 << (i - 1))][i - 1]; node[j][i] = node[j + (1 << (i - 1))][i - 1]; } } } int Query(int l, int r) { int Log = Log2[r - l + 1]; if (MAX[l][Log] < MAX[r - (1 << Log) + 1][Log]) return node[l][Log]; return node[r - (1 << Log) + 1][Log]; } //=========================================================== int main() { printf("%lf", log(500000)); Prepare(); while(M --) { int l = read(), r =read(); printf("%d\n", Query(l, r)); } return 0; }
2. 然后口胡某类DP
3.二进制
nice,我想了一下,还是就在这个博客里说了吧
计算机的存储应用的就是二进制,所以二进制这类基础知识就让人不得不去学习,学完之后,你发现,你可以手玩状态压缩DP了,毕竟(我是傻逼)
计算机的储存,由大到小 GB,MB,KB,bit ,进位就是1024,所以,1GB就是1024^1024^1024 bit
嗯,然后洛谷(SP2916 时间限制为132ms,然后空间限制 1.46GB,啊这)
变量是内存中开辟的,一段用于储存数据的空间。
一个 int,32 个比特,32 个 2 进制位。
一个 char,8 个比特,8 个 2 进制位。
补码
没法表示负数怎么办啊?
一个变量的第一个 2 进制位设为符号位,第一个 2 进制位为 0 表示正数,为 1 则表示负数。这样的表示法称为补码。
为保证两个互为相反数的变量相加后为 0,正数的补码即其二进制表示,负数的补码为对应正数各 2 进制位取反后 + 1
(https://netdisk-pan.baidupcs.com/disk/docview?bdstoken=ff7e264e3f2b4a69321eb08779ac6caa&uk=3848857727&path=%2F%E8%AF%BE%E4%BB%B6%2F%E4%BA%8C%E8%BF%9B%E5%88%B6.pptx&share=0)
然后就是C++中的一些符号
每一位独立,除左右移运算,在二进制表示下不进位。
异或可以看做二进制不进位加法。
优先级
还有就是位运算的优先级最低,
如果是 left+right>>1,表示的就是(left+right)/2,而不是 left+right/2;
典例:
1.快速幂:
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; long long quickpow(int x,int y) { long long ans=1; while(y) { if(y & 1) { ans*=x; y>>=1; } x=x*x; } return ans; } int main() { int x,y; cin>>x>>y;//x^y cout<<quickpow(x,y); return 0; }
2.64 位整数乘法
求 x*y%mod ,x,y,mod<=10^18
直接相乘直接炸掉long long ,因为 x(a+b)=x*a+x*b(小学数学,乘法分配率),讲x,y拆分成二进制,从而过掉 10^18,复杂度从O(1)变成了 O (log) (by the way,C++自带 log函数)
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; long long quickpow(long long x,long long y,long long mod) { long long ans=0; for(;y;y>>=1) { if(y & 1) { ans=( ans + x ) % mod; } x = (x + x) %mod; } return ans; } int main() { long long x,y,mod; cin>>x>>y>>mod; cout<<quickpow(x,y,mod); return 0; }
4.数据结构
1.树状数组
2.线段树
3.分块:
本质是暴力分治。
通过对原数据的适当划分,并在划分后的每一个块上预处理部分信息,从而较一般的暴力算法取得更优的时间复杂度。
可以处理并维护许多前两者无法维护的复杂信息,如区间众数。 空间换时间,可看做只有两层的线段树。
4.一些其他题目
题单:
1.ACL Beginner Contest D
2.SP2713 GSS4
3.P1438 无聊的数列
4.POJ 2777 Count Color
5.P4054 [JSOI2009]计数问题
6.P2574 XOR的艺术
7.ACL Beginner Contest E
8.SP2916 GSS5
9.Loj2211. 「SCOI2014」方伯伯的玉米田
10.P2572 [SCOI2010]序列操作
选做:
P3797 妖梦斩木棒:线段树模拟。
AcWing246. 区间最大公约数:区间加,区间 gcd,更相减损。
P4198 楼房重建 :动态维护单调栈,不寻常的 pushup。
CF703D:区间出现过偶数次的权值的异或和,扫描线。
「联合省选 2020 A | B」冰火战士:树状数组上二分。
P4137 Rmq Problem / mex:区间 mex,扫描线。
CF407E:线段树+单调栈。
「TJOI / HEOI2016」排序:强加单调性 + 01序列排序。
应学长要求(众所周知,学长是很欠揍的)
1.P2574 XOR的艺术
WA掉的的地方对于求解一个区间内1的数目,所求的公式就是 区间长度减去自身的值,忘记+1了, QwQ ;
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <queue> using namespace std; const int maxn=2e5+10; inline int read() { int x=0,f=1;char ch=getchar(); while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while (isdigit(ch)){x=x*10+ch-48;ch=getchar();} return x*f; } int n,m; int num[maxn<<1]; struct Tree { int sum,left,right,over; }; Tree tree[maxn<<2]; void build(int now,int left,int right) { tree[now].left=left; tree[now].right=right; if(left==right) { tree[now].sum=num[left]; return; } else { int mid=(left+right)>>1; build(now*2,left,mid); build(now*2+1,mid+1,right); } tree[now].sum=tree[now<<1].sum+tree[now<<1|1].sum; } void pushdown(int now) { if(tree[now].over) { tree[now<<1].sum=tree[now<<1].right-tree[now<<1].left+1-tree[now<<1].sum; tree[now<<1|1].sum=tree[now<<1|1].right-tree[now<<1|1].left+1-tree[now<<1|1].sum; tree[now<<1].over^=1; tree[now<<1|1].over^=1; tree[now].over=0; } return ; } int query(int now,int left,int right) { if(tree[now].right<=right && tree[now].left>=left) { return tree[now].sum; } else { pushdown(now); int mid=(tree[now].right+tree[now].left)>>1; int ans=0; if(left<=mid) { ans+=query(now<<1,left,right); } if(mid<right) { ans+=query(now<<1|1,left,right); } tree[now].sum=tree[now<<1].sum+tree[now<<1|1].sum; return ans; } } void change(int now,int left,int right) { if(tree[now].left>=left &&tree[now].right<=right) { tree[now].sum=tree[now].right-tree[now].left+1-tree[now].sum; tree[now].over^=1; return ; } else { pushdown(now); int mid=(tree[now].left+tree[now].right)>>1; if(left<=mid) { change(now<<1,left,right); } if(mid<right) { change((now<<1)|1,left,right); } tree[now].sum=tree[now<<1].sum+tree[now<<1|1].sum; } } int main() { cin>>n>>m; for(int i=1;i<=n;i++) { scanf("%1d",&num[i]); } build(1,1,n); for(int i=1;i<=m;i++) { int op; cin>>op; if(op==0) { int l,r; cin>>l>>r; change(1,l,r); } else if(op==1) { int l,r; cin>>l>>r; printf("%d\n",query(1,l,r)); } } return 0; }