[笔记]SD2021省队第一轮集训Day2
Day2 数据结构
DFS序线段树|字符串hash|多源BFS
有关998244353
它的二进制表达是:111011100000000000000000000001
它是一个质数。
它可以表达为两个数的平方和:998244353 = 3943^2 + 31348^2
它是勾股数之一:998244353^2 = 247210328^2 + 967149855^2
它可以被表达为:998244353 = 7 * 17 * 2^23 + 1
但是这些和左部L=[0,998244352]
,右部R=[0,998244352]
由R=(L*2333333+[1,26])%998244353
又有什么关系呢?
T1 DFS序线段树
权值的设定
权值设置为与编号模n同余。
考场的想法
树链剖分|最小生成树|由于是集合制约所以不能2-SAT|树分治。
std真的写了一个kruskal。
正解是DP。
T2 字符串hash
•这题比较怪。
•考虑我们有个 O(n^2 ) 做法,就是求每个子串哈希值取最大。
•考虑字符串随机的条件,再结合哈希函数的随机性,基本上意味着哈希值是在 [0,m) 随机分布的。
•随机的 Θ(n^2 ) 个变量的最大值期望是 m-m/n^2 附近。
•考虑从大往小 check 这个值是否在哈希值中存在。
截止到这里我考场上想到了。但是检查一个哈希值是否能在字符串中出现就不会了。
•一个子串 (l,r] 的哈希值大概是 s_r-base^(r-l) s_l。
•那么 s_r-base^(r-l) s_l=x 即 b^(-r) s_r=x+b^(-l) s_l,扫一遍,维护个哈希表查是否存在即可。
•这样时间复杂度是 O(m/n^2 ⋅n)=O(m/n)。
•所以 min(O(n^2 ),O(m/n))=O(max(m^(2∕3),n) )。比如对于 n≤2000 跑暴力,否则跑这个做法即可。
T2 多源点BFS
•测试点 1∼3:模拟。
•测试点 4∼6:对每个 1≤t≤k 求 dis(x,y)=t 的数量,即是要求矩形并,扫描线可做。
考场上想了扫描线,但是无奈我不会写。纠结于是处理这\(n\)个点还是先连出来边再处理边。
•测试点 7∼10:看起来就可以推式子。
•测试点 11∼16:在数轴上的情况,只有相邻两个区域合并的时候会有影响,处理出这些时间点,BFS 到的面积应该是一个以这些时间点间隔的分段函数,每段内是一个二次函数,要求每段的总和之和,每段内的总和是一个三次函数,可以插值出来。
•测试点 17∼20:不知道干啥用的,就放那。
扫描线:
#include <algorithm>
#include <cstdio>
#include <cstring>
#define maxn 300
using namespace std;
int lazy[maxn << 3]; // 标记了这条线段出现的次数
double s[maxn << 3];
struct node1 {
double l, r;
double sum;
} cl[maxn << 3]; // 线段树
struct node2 {
double x, y1, y2;
int flag;
} p[maxn << 3]; // 坐标
//定义sort比较
bool cmp(node2 a, node2 b) { return a.x < b.x; }
//上传
void pushup(int rt) {
if (lazy[rt] > 0)
cl[rt].sum = cl[rt].r - cl[rt].l;
else
cl[rt].sum = cl[rt * 2].sum + cl[rt * 2 + 1].sum;
}
//建树
void build(int rt, int l, int r) {
if (r - l > 1) {
cl[rt].l = s[l];
cl[rt].r = s[r];
build(rt * 2, l, (l + r) / 2);
build(rt * 2 + 1, (l + r) / 2, r);
pushup(rt);
} else {
cl[rt].l = s[l];
cl[rt].r = s[r];
cl[rt].sum = 0;
}
return;
}
//更新
void update(int rt, double y1, double y2, int flag) {
if (cl[rt].l == y1 && cl[rt].r == y2) {
lazy[rt] += flag;
pushup(rt);
return;
} else {
if (cl[rt * 2].r > y1) update(rt * 2, y1, min(cl[rt * 2].r, y2), flag);
if (cl[rt * 2 + 1].l < y2)
update(rt * 2 + 1, max(cl[rt * 2 + 1].l, y1), y2, flag);
pushup(rt);
}
}
int main() {
int temp = 1, n;
double x1, y1, x2, y2, ans;
while (scanf("%d", &n) && n) {
ans = 0;
for (int i = 0; i < n; i++) {
scanf("%lf %lf %lf %lf", &x1, &y1, &x2, &y2);
p[i].x = x1;
p[i].y1 = y1;
p[i].y2 = y2;
p[i].flag = 1;
p[i + n].x = x2;
p[i + n].y1 = y1;
p[i + n].y2 = y2;
p[i + n].flag = -1;
s[i + 1] = y1;
s[i + n + 1] = y2;
}
sort(s + 1, s + (2 * n + 1)); // 离散化
sort(p, p + 2 * n, cmp); // 把矩形的边的纵坐标从小到大排序
build(1, 1, 2 * n); // 建树
memset(lazy, 0, sizeof(lazy));
update(1, p[0].y1, p[0].y2, p[0].flag);
for (int i = 1; i < 2 * n; i++) {
ans += (p[i].x - p[i - 1].x) * cl[1].sum;
update(1, p[i].y1, p[i].y2, p[i].flag);
}
printf("Test case #%d\nTotal explored area: %.2lf\n\n", temp++, ans);
}
return 0;
}
例1 Room
给定一个由小写字母组成的 n×m 的矩阵,定义一个房间是同字母的极大四连通块。
q 次询问,每次给出一个矩形,问有多少房间与矩形有交。
数据范围:n,m≤2000,q≤5000。
处理联通块。联通的充要条件是一个矩形既在另一个矩形里面又在外面。
例2 Xor-matic Number of the Graph
给定一张 n 个点 m 条边的无向图,边有非负整数边权。
请求出所有满足 u 和 v 之间存在一条异或和为 w 的路径且 u<v 的三元组 (u,v,w) 的 w 之和。
数据范围:n≤〖10〗5,m≤2×〖10〗5,边权 ≤〖10〗^18。
知识点:线性基
例3 Bajtocja
u给定 d 张无向图,每张图都有 n 个点,初始时没有边。
u接下来 m 次操作,每次操作给定 (k,a,b),在第 k 张图中连边 (a,b)。
u每次操作后输出点对 (u,v) 的数量,满足 u,v 在每张图中都是连通的。
u
u数据范围:d≤2000,n≤5000,m≤〖10〗^6。
哈希??
我们需要知道的只是每对点之间是否连通,即在同一张图所属的连通块是否一样 于是我们对每个点在d张图中所属的连通块标号进行哈希,这个哈希要能快速删除一个标号 插入一个标号 如果有两个点哈希后的值相同,那么这两个点在d张图中都连通。于是我们再对这个哈希值做一遍哈希,来计算相同哈希值的个数 连边时用启发式合并,每次将size小的连通块全部修改fa,总复杂度O(dn\log n)O(dnlogn)
//75500kb 4628ms
#include <cstdio>
#include <cctype>
#include <algorithm>
#define gc() getchar()
typedef unsigned long long ull;
const int N=5005,D=205,M=1e6+5,mod=2e6;
const ull seed=769;
int n,m,d,H[D][N],Enum,to[M<<1],nxt[M<<1],fa[D][N],sz[D][N],Ans;
ull hs_id[N],Pow[D];
struct Hash_Table
{
int top,h_H[mod+5],sk[mod],h_nxt[mod],cnt[mod];
ull val[mod];
void Init()
{
top=mod-5;
for(int i=1; i<=top; ++i) sk[i]=i;
}
void Insert(ull x)
{
int p=x%mod;
for(int i=h_H[p]; i; i=h_nxt[i])
if(val[i]==x) {Ans+=2*cnt[i]+1,++cnt[i]; return;}
++Ans;//(a,a)也算一对
int pos=sk[top--];
val[pos]=x, cnt[pos]=1, h_nxt[pos]=h_H[p], h_H[p]=pos;
}
void Delete(ull x)
{
int p=x%mod,pre=h_H[p];
if(val[pre]==x)
{
Ans-=2*cnt[pre]-1;
if(!--cnt[pre]) sk[++top]=pre, h_H[p]=h_nxt[pre];
}
else
for(int i=h_nxt[pre]; i; pre=i,i=h_nxt[i])
if(val[i]==x)
{
Ans-=2*cnt[i]-1;
if(!--cnt[i]) sk[++top]=i, h_nxt[pre]=h_nxt[i];
break;
}
}
}hs2;
inline int read()
{
int now=0;register char c=gc();
for(;!isdigit(c);c=gc());
for(;isdigit(c);now=now*10+c-'0',c=gc());
return now;
}
inline void AddEdge(int u,int v,int k){
to[++Enum]=v, nxt[Enum]=H[k][u], H[k][u]=Enum;
}
void DFS(int x,int f,int k,int anc)
{
hs2.Delete(hs_id[x]);
hs_id[x]-=Pow[k]*fa[k][x];//fa就是belong了
fa[k][x]=anc;
hs_id[x]+=Pow[k]*anc;
hs2.Insert(hs_id[x]);
for(int i=H[k][x]; i; i=nxt[i])
if(to[i]!=f) DFS(to[i],x,k,anc);
}
void Union(int u,int v,int k)
{
if(fa[k][u]==fa[k][v]) return;
if(sz[k][fa[k][u]]<sz[k][fa[k][v]]) std::swap(u,v);
sz[k][fa[k][u]]+=sz[k][fa[k][v]];
DFS(v,u,k,fa[k][u]);
AddEdge(u,v,k),AddEdge(v,u,k);
}
int main()
{
d=read(),n=read(),m=read();
Pow[0]=1;
for(int i=1; i<D; ++i) Pow[i]=Pow[i-1]*seed;
hs2.Init();
for(int i=1; i<=n; hs2.Insert(hs_id[i++]))
for(int j=1; j<=d; ++j)
fa[j][i]=i, sz[j][i]=1, hs_id[i]+=Pow[j]*i;//Hash = (∑s[i]seed^i) mod 2^{31}
int a,b,k;
while(m--)
a=read(),b=read(),k=read(),Union(a,b,k),printf("%d\n",Ans);
return 0;
}
例4 Rally
u给定一张 n 个点 m 条边的 DAG,每条边长度为 1。
u你可以删除一个点,最小化删除后的最长路径长度。
u
u数据范围:2≤n≤5×〖10〗5,m≤〖10〗6。
建立超级源点超级汇点。
考虑topo序。计算每个点到超级源点和超级汇点的距离。
例5 Tournament cycle
竞赛图,缩点后变成链。
竞赛图,一定有哈密顿链。
强连通竞赛图,缩点后变成一个点。
强连通竞赛图,一定有哈密顿回路。
强连通竞赛图,每个点都经过长度为3、4、5...n的简单环。?
若存在长度为k-1的环则一定存在长度为k的环。
-----------------------------以上为昨天图论内容-------------------------------
例1 Siano
u有 n 亩土地,一开始都是空的,一个农夫要在上面种草。
u其中,第 i 亩土地的草每天会长高 a_i 厘米。
u农夫一共会进行 q 次收割,其中第 i 次收割在第 d_i 天,并把所有高度大于等于 b_i 的部分全部割去。
u输出每次收割得到的草的高度总和。
u
u数据范围:n,q≤5×〖10〗5,1≤a_i≤〖10〗6,1≤d_i,b_i≤〖10〗^12。
且 d_i<d_(i+1),任意时刻不存在一亩草高度超过 〖10〗^12。
草的高度递增排列,每次区间割下一些草仍然是单调不降的
线段树二分即可
区间修改也适用
例2 二分图
例3 直径
u给定一棵树,q 次询问,给定 a,b,c,d,求 x∈[a,b] 且 y∈[c,d] 时 dis(x,y) 的最大值。
u
u数据范围:n,q≤〖10〗^5。
例4 Painting Edges
u给定一张 n 个点 m 条边的无向图,每条边有编号在 [0,k] 内的颜色。
u初始时每条边的颜色都是 0。
u执行 q 次操作,每次给定 (e,c) 表示将边 e 的颜色改成 c(c∈[1,k])。
u如果一次操作后只考虑某个非零颜色的图不是二分图了,则忽略这次操作。
u输出每次操作是否被执行(即这次操作没被忽略)。
u
u数据范围:n,m,q≤5×〖10〗^5,k≤50。
线段树分治,内维护50个并查集。
例5 Segment
u在平面直角坐标系中维护两个操作:
1.加入一条线段,编号为 i。
2.询问与竖线 x=k 相交的线段中,交点最高的线段的编号。
u强制在线。
u
u数据范围:1≤横坐标≤40000,q≤〖10〗5,1≤纵坐标≤〖10〗9。
例6 楼房重建
给定一个长度为
n 的序列,q 次操作,每次修改一个数,然后求有多少个数满足它是前缀最大值。数据范围:n,q≤〖10〗^5。
例7
例8 线段树
u给定一个长为 n 的数组 a。
u有 m 个操作,编号为 1∼m,每个操作形如把区间 [l,r] 内的数置为这个区间的最大值。
u有 q 次操作:
1.给定 k,v,修改 a_k←v。
2.给定 x,y,k,询问如果依次执行编号为 x∼y 的操作,a_k 将会是多少。
u
u数据范围:n,m,q≤〖10〗^5。
合并操作,倒序维护。
倍增
用于合并操作。
例9 树上的路径(***) P2048 [NOI2010] 超级钢琴
u给定一棵 n 个结点的树,每条边有正整数权值。
u求出 dis(a,b)(1≤a<b≤n)从小到大排序后的前 m 个值。
u
u数据范围:n≤5×〖10〗4,m≤3×〖10〗5。
点分治!nlogn个点
考虑重心
维护前缀子树堆,堆内元素为<点,?>
考虑依次加入子树
长度logn的超级gangxing??
例10 等差数列
u给定一个长度为 n 的数列 a_1∼a_n。
u有 q 次操作,每次要么修改数列某一项,要么给出 l,r,k,询问区间 a_l∼a_r 中的数从小到大排序后能否形成公差为 k 的等差数列。
u强制在线。
u
u数据范围:n,q≤3×〖10〗5,0≤a_i,k≤〖10〗9。
性质1:所有差分的gcd为k
性质2:没有相等的数(公差不为0)
性质3:最大值与最小值之差为公差*(个数-1)
以上性质为充要条件。
扫描线:判重
主席树:在线判重
例11 Good Subsegments
u给定一个长度为 n 的排列。
u有 q 次询问,每次给定一个区间,求这个区间的所有是连续段的子区间个数。
u连续段的定义是排序后形成公差为 1 的等差数列。
u
u数据范围:n,q≤1.2×〖10〗^5。
线段树看起来可做的样子。维护一个区间的最大、小值。
正解
回滚莫队 固定右端点 算左端点合法连续段的数量
左端点线段树下标,右端点时间轴
q = 1
扫描线
线段树
区间加法 最小值
复杂度
均摊??
区间加法历史版本最小值 吉司机线段树??
例12 Puzzled Elena
u给定一棵 n 个结点的有根树,每个点有正整数权值 a_i。
u对于每个点,输出它的子树中有多少个点的权值与它的权值互质。
u
u数据范围:n≤〖10〗5,a_i≤〖10〗5。
莫比乌斯
子树=dfs序上的一个区间=前缀相减
gcd = 1,枚举因数
例13 Odwiedziny
u给定一棵 n 个结点的树,每个点有点权。
u有 q 次询问,每次给定 x,y,k,表示从 x 出发每次朝 y 方向跳 k 条边的距离,如果当前点和 y 距离不超过 k 就直接一步跳到 y,请求出经过的所有点的点权和。
u
u数据范围:n≤〖10〗^5。
根号n分治
本文来自博客园,作者:咕咕坤,转载请注明原文链接:https://www.cnblogs.com/GuguKun/p/14786491.html