感觉是鸽了很久了,觉得还是总结以下提升会比较大。虽然原因其实是临近国庆,平时常用的几个oj都出了点不同程度上的问题,国外节点的CF卡爆,VJ也是很慢,然后bzoj暂时关闭,HDU晚上不营业(就是现在我写博客的时间),突然又不想做题又不想闲着,那就写点东西算了。
2018年杭电多校第一场的G题,怎么说呢,这题其实打表可以明显看到有规律可循,因为每种数字x的出现个数就是lowbit(x)次,于是我一直往树状数组的方向想但是最终无果,没办法把规律变得能够处理就很让人难受。其实这题就是根据给出的n求出n对应的数字x,然后在x的范围内其实每种数字的的累加都可以化为多组等差数列求和处理,每组的首项是一个2k ,公差是2k ,然后范围小于等于最大的x。
褚大爷推荐的一题,二分匹配 + 数学规律,题意就是从s+1到s+n,每一个数从1到n中挑一个数是它的因数,不能重复选择同一个数,然后求能不能全匹配,就让你裸的跑二分匹配,但是点太多如何处理。答案其实还是好想的,素数间隔!1e18内的素数间隔不会超过300(大概230左右??)所以一旦超过600的话就有两个待匹配的素数,而他们一定得和1匹配,于是得出结论大于600的范围一定匹配不上直接输出答案,剩余再跑一遍匹配就行。
难受,写博客想从题库里找题,vj也能给你卡半天,我死了
一道莫比乌斯反演的入门题,最近在研究数论方面,不管怎么说这种基础性的东西还是得掌握。然后先存一手别人家的博客,https://blog.csdn.net/codeswarrior/article/details/81541972,感觉最近才算是弄懂了点东西。其实最核心的就是一个约束公式一个倍数公式
约数公式:
F ( n ) = ∑ d ∣ n f ( d ) ⟶ f ( n ) = ∑ d ∣ n μ ( d ) F ( n d ) ( d 是 n 的 约 数 ) F(n)=∑_{d|n}f(d)⟶f(n)=∑_{d|n}μ(d)F(\frac{n}{d}) ~~~~~~~~~~(d是n的约数) F(n)=∑d∣nf(d)⟶f(n)=∑d∣nμ(d)F(dn) (d是n的约数)
倍数公式:
F ( n ) = ∑ n ∣ d f ( d ) ⟶ f ( n ) = ∑ n ∣ d μ ( d n ) F ( d ) ( d 是 n 的 倍 数 ) F(n)=∑_{n|d}f(d)⟶f(n)=∑_{n|d}μ(\frac{d}{n})F(d)~~~~~~~~~~~ (d是n的倍数) F(n)=∑n∣df(d)⟶f(n)=∑n∣dμ(nd)F(d) (d是n的倍数)
然后其中的μ函数可以用线性筛法求,具体意义和证明博客和《具体数学》上都讲的蛮好
(贴张图)
int mu[maxn], prime[maxn], prime_cnt;
bool is_prime[maxn];
void Leaner_Shaker_Mu (int n)
{
mu[1] = 1;
for (int i = 2; i <= n; i++) is_prime[i] = true;
for (int i = 2; i <= n; i++)
{
if (is_prime[i])
{
prime[++prime_cnt] = i;
mu[i] = -1;
}
for (int j = 1; j <= prime_cnt && i * prime[j] <= n; j++)
{
is_prime[i * prime[j]] = false;
if (i % prime[j]) mu[i * prime[j]] = -mu[i];
else
{
mu[i * prime[j]] = 0;
break;
}
}
}
}
然后就是要看怎么用了,就是一个推公式,其实关于这块感觉还得多练,本题的话,要求1到b和1到d内各选一个数使得他们的GCD为k,请问有多少种选择这样的。于是数论整除分块,转换到1到b/k和1到d/k内选一个数使得GCD为1的选择数量。
下面我们设 f ( d ) f(d) f(d)表示满足 g c d ( x , y ) = d gcd(x,y)=d gcd(x,y)=d的个数
F ( d ) F(d) F(d)表示满足 g c d ( x , y ) = d gcd(x,y)=d gcd(x,y)=d的倍数的个数
当 f ( d ) f(d) f(d)难求的时候我们可以考虑转换思路求其倍数or因数的个数,因为我们可以用莫比乌斯反演解决转化问题,那么就是先求 F ( d ) F(d) F(d)咯。
因为对于区间[1,n]来说,能够被x或者x的倍数整除的数(即含有x或者x的倍数作为因子)的个数为
n/x,同理对于区间[1,m]来说能被x或者x的倍数整除的数的个数为m/x,那么n和m都可选择的情况下,即有 F ( x ) = n x ⋅ m x F(x) =\frac{n}{x}\cdot\frac{m}{x} F(x)=xn⋅xm。我们就可以处理出所有 F ( x ) F(x) F(x),然后转求 f ( 1 ) f(1) f(1)。最后注意最后去重一道就行。
这确实是个好题,或者说提供了一种思考方式,读懂题意后知道我们需要求的是树上所有点对的距离dis,然后 d i s 1 = ⌊ d i s + 1 2 ⌋ dis1=\lfloor\frac{dis+1}{2}\rfloor dis1=⌊2dis+1⌋,求全部dis1的和。
暴力跑不过来怎么办?那是因为我们的眼光聚焦在了点与点上,不妨这么想,求所有边被经过的总次数就能求出全部的点对间的dis总和,于是我们可以对一个点统计其子树大小 c n t 1 cnt1 cnt1和非子树点的数量 c n t 2 cnt2 cnt2,这两部分一定由一条边连接,于是经过次数就是 c n t 1 ⋅ c n t 2 cnt1\cdot cnt2 cnt1⋅cnt2,dfs的同时对每一个点都计算并统计即可。
那么dis1如何转化呢?我们发现与让dis直接除以2的不同在于当二者间距为奇数时统计答案应该加一,这又这么处理?就是让树分层,奇数层与偶数层的距离和,就是会影响除以2的部分。
题意简单粗暴,求 ∑ i = 1 n − 1 ∑ j = i + 1 n g c d ( i , j ) ~~∑_{i=1}^{n−1}∑_{j=i+1}^ngcd(i,j) ∑i=1n−1∑j=i+1ngcd(i,j)
设 f ( x ) = ∑ i = 1 n g c d ( i , x ) f(x)=∑_{i=1}^ngcd(i,x) f(x)=∑i=1ngcd(i,x)
那么 a n s ( n ) = ∑ i = 2 n f ( i ) ~~ans(n)=\sum_{i=2}^nf(i)~~ ans(n)=∑i=2nf(i) 或者说 a n s ( n ) = a n s ( n − 1 ) + f ( n ) ~~ans(n)=ans(n-1)+f(n) ans(n)=ans(n−1)+f(n)
n不大,可先循环处理 f ( x ) f(x) f(x),再递推得到范围内全部 a n s ( x ) ans(x) ans(x),最后每次对于询问输出即可
至于 f ( n ) f(n) f(n)的处理,这类问题看起来应该都得先考虑互质, g c d ( x , y ) = 1 gcd(x,y)=1 gcd(x,y)=1那么就有 g c d ( k x , k y ) = k ~~gcd(kx,ky)=k gcd(kx,ky)=k,我们固定 y y y值,于是小于 y y y且与其互质的个数就是欧拉函数,然后对于每一个 k y ky ky享受到的贡献救要加上每一个与 y y y互质的数 x 1 x1 x1的贡献 k k k,这一部分的贡献加到 f ( k y ) f(ky) f(ky)中,于是可递推。
写出来是这样的:
void init (int n)
{
Linear_Shaker_phi(n);
for (int i = 1; i <= n; i++)
for (int j = i + i; j <= n; j += i)
f[j] += i * phi[j / i];
for (int i = 1; i <= n; i++)
ans[i] = ans[i - 1] + f[i];
}
思维题,我直接上别人博客了,我感觉我说的一定没有他说的好
https://www.cnblogs.com/yzxqq/p/11307918.html
求串S与自己的每一个后缀的匹配长度,真是好久不用就完了有EX-KMP这事了,傻了
给定序列a和序列b和数字C,求所有满足 ∑ i = 1 N ∣ a i ⋅ x + b i ∣ = C ∑_{i=1}^{N}|ai⋅x+bi|=C ∑i=1N∣ai⋅x+bi∣=C的 x ~x~ x 值,本来觉得不可做,因为之前很少接触过这种题,现在想来也满单纯,想象一下求和出来的函数图嘛,不就是由一堆带绝对值的一次函数(关于 x = − b i / a i ~x=-bi/ai~ x=−bi/ai 对称)所累加出来的折线图嘛。然后每一次转折都是在一对 ( a i , b i ) (ai,~bi) (ai, bi)的对称轴处。那么就是对于两个转折处间检查有没有可能出现 C C C值,具体检查方法就是根据在那个区间内直线 l i : k i x + b i = 0 ~~l_i:k_ix+b_i=0 li:kix+bi=0,根据斜率还有C与纵截距的距离求出 x ~x x,若 x ~x~ x 在那两个对称轴之间,那么就是可以记录的。
给定p和x,求最小的b满足 a ≡ b x ( m o d p ) a≡bx(modp) a≡bx(modp)
https://blog.csdn.net/baiyifeifei/article/details/98606715
一个经典问题,求满足值在所给的两分数之间时,最小的分子分母是多少
推导和原理见上博客,这里给一下解决的代码
void go (long long p1, long long x1, long long p2, long long x2, long long & b, long long & c)
{
long long d = (p1 + x1 - 1) / x1 - 1;
if (p1 / x1 < p2 / x2)
{
b = d + 1;
c = 1;
return;
}
go (x2, p2 - x2 * d, x1, p1 - x1 * d, c, b);
b += d * c;
}
上面连续几题都是今年杭电多校第五场的题目,不得不吹一下题目质量。
题意是在可修改边权的操作的条件下,询问两点间路径上的最大边权值。
经典的树链剖分+线段树维护,也就是这个题我总算搞懂了树链剖分的意义和一点点用法,好东西。
点u和v都是在同一条重链上分出的不同(或相同)轻链及其分支上,所以我们要做的就是让他们跳回那条大重链上。如果说O(n)的查找方式必T的话那么就用O(logn)的吧,因为树链剖分的关系,在重新命名树节点的时候保证了在同一条链(两个相邻的分支点之间)上的节点的id序号是相邻的,既然相邻的话,区间最大值就可以用线段树维护,线段树直接套版。
贴一下树链剖分部分的代码
int father[maxn], hson[maxn], siz[maxn], depth[maxn];
int cnt, id[maxn], top[maxn];
void get_hson (int now, int fa)
{
father[now] = fa;
siz[now] = 1;
depth[now] = depth[fa] + 1;
int it = 0;
for (int i = head[now]; i; i = edge[i].nex)
{
int to = edge[i].to;
if (to == fa) {_val[now] = edge[i].val; continue;}
get_hson (to, now);
siz[now] += siz[to];
if (siz[to] > siz[it]) {hson[now] = to; it = to;}
}
}
void dfs (int now, int thetop)
{
top[now] = thetop;
id[now] = ++cnt;
val[cnt] = _val[now];
if (siz[now] == 1) return;
dfs (hson[now], thetop);
for (int i = head[now]; i; i = edge[i].nex)
{
int to = edge[i].to;
if (to == father[now] || to == hson[now]) continue;
dfs (to, to);
}
}
long long path_max (int u, int v)
{
long long ret = -llinf;
while (top[u] != top[v])
{
if (depth[top[u]] < depth[top[v]]) swap (u, v);
ret = max (ret, seg.query(id[top[u]], id[u], 1, n, 1));
u = father[top[u]];
}
if (depth[u] > depth[v]) swap (u, v);
ret = max (ret, seg.query (id[u] + 1, id[v], 1, n, 1));
return ret;
}
RUAAAAAAA
好像已经写了很久了,那今天到这了吧,虽然还有一堆题没谈到,CF都没来得及打开。
摸了摸了