训练笔记 9月26日

感觉是鸽了很久了,觉得还是总结以下提升会比较大。虽然原因其实是临近国庆,平时常用的几个oj都出了点不同程度上的问题,国外节点的CF卡爆,VJ也是很慢,然后bzoj暂时关闭,HDU晚上不营业(就是现在我写博客的时间),突然又不想做题又不想闲着,那就写点东西算了。


HDU - 6304

2018年杭电多校第一场的G题,怎么说呢,这题其实打表可以明显看到有规律可循,因为每种数字x的出现个数就是lowbit(x)次,于是我一直往树状数组的方向想但是最终无果,没办法把规律变得能够处理就很让人难受。其实这题就是根据给出的n求出n对应的数字x,然后在x的范围内其实每种数字的的累加都可以化为多组等差数列求和处理,每组的首项是一个2k ,公差是2k ,然后范围小于等于最大的x。


HDU - 5943

褚大爷推荐的一题,二分匹配 + 数学规律,题意就是从s+1到s+n,每一个数从1到n中挑一个数是它的因数,不能重复选择同一个数,然后求能不能全匹配,就让你裸的跑二分匹配,但是点太多如何处理。答案其实还是好想的,素数间隔!1e18内的素数间隔不会超过300(大概230左右??)所以一旦超过600的话就有两个待匹配的素数,而他们一定得和1匹配,于是得出结论大于600的范围一定匹配不上直接输出答案,剩余再跑一遍匹配就行。

难受,写博客想从题库里找题,vj也能给你卡半天,我死了


HDU - 1695

一道莫比乌斯反演的入门题,最近在研究数论方面,不管怎么说这种基础性的东西还是得掌握。然后先存一手别人家的博客,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)=dnf(d)f(n)=dnμ(d)F(dn)          (dn)

倍数公式:

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)=ndf(d)f(n)=ndμ(nd)F(d)           (dn)

然后其中的μ函数可以用线性筛法求,具体意义和证明博客和《具体数学》上都讲的蛮好
(贴张图)
在这里插入图片描述

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)=xnxm。我们就可以处理出所有 F ( x ) F(x) F(x),然后转求 f ( 1 ) f(1) f(1)。最后注意最后去重一道就行。


Codeforces - 1060E

这确实是个好题,或者说提供了一种思考方式,读懂题意后知道我们需要求的是树上所有点对的距离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 cnt1cnt2,dfs的同时对每一个点都计算并统计即可。
那么dis1如何转化呢?我们发现与让dis直接除以2的不同在于当二者间距为奇数时统计答案应该加一,这又这么处理?就是让树分层,奇数层与偶数层的距离和,就是会影响除以2的部分。


uva - 11426

题意简单粗暴,求    ∑ i = 1 n − 1 ∑ j = i + 1 n g c d ( i , j ) ~~∑_{i=1}^{n−1}∑_{j=i+1}^ngcd(i,j)   i=1n1j=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(n1)+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];
}

HDU - 6630

思维题,我直接上别人博客了,我感觉我说的一定没有他说的好
https://www.cnblogs.com/yzxqq/p/11307918.html


HDU - 6629

求串S与自己的每一个后缀的匹配长度,真是好久不用就完了有EX-KMP这事了,傻了


HDU - 6627

给定序列a和序列b和数字C,求所有满足 ∑ i = 1 N ∣ a i ⋅ x + b i ∣ = C ∑_{i=1}^{N}|ai⋅x+bi|=C i=1Naix+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 在那两个对称轴之间,那么就是可以记录的。


HDU - 6624

给定p和x,求最小的b满足 a ≡ b x ( m o d p ) a≡bx(modp) abx(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;
}

上面连续几题都是今年杭电多校第五场的题目,不得不吹一下题目质量。


SPOJ - Query on a tree

题意是在可修改边权的操作的条件下,询问两点间路径上的最大边权值。
经典的树链剖分+线段树维护,也就是这个题我总算搞懂了树链剖分的意义和一点点用法,好东西。
点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都没来得及打开。

?

摸了摸了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值