做题随笔1

前缀后缀的预处理

接雨水
对于某一个柱子 \(i\),它左右两边柱子的较小值决定这个柱子能盛多少水,预处理前缀后缀的最小值即可

自然数前缀异或与二次前缀异或

对于自然数 \(1-n\) 的前缀异或,规律如下

\[S(n)=\left\{\begin{array}{ll} n, & n \bmod 4=0 \\ 1, & n \bmod 4=1 \\ n+1, & n \bmod 4=2 \\ 0, & n \bmod 4=3 \end{array}\right. \]

对于自然数 \(1-n\) 前缀异或的前缀异或(即二次前缀异或),规律如下

\(r=x\) \(mod\) \(8\)

\[f(x)=Q_{x}=\left\{\begin{array}{ll} x, & r \in\{0,1\} \\ 2, & r \in\{2,3\} \\ x+2, & r \in\{4,5\} \\ 0, & r \in\{6,7\} \end{array}\right. \]

二分边界的选取

https://codeforces.com/problemset/problem/1538/C

可以把 \(l<=a_i+a_j<=r\) 转化为 \(l-a_j<=a_i<=r-a_j\),对 \(a\) 数组进行排序,然后遍历 \(a\) 数组,对于每一个 \(a_i\) 来说,计算他下一个到最后一个的合法对数,通过 \(lowerbound\)\(upperbound\)实现

计算x进制下式子末尾0的个数

通俗的,先对十进制进行分析,只需要计算式子中每一项含有 \(2\)\(5\) 的个数就可以了,同样的,对x进行质因数分解,只需要计算式子中每一项含各个质因数的最少个数就可以了。

注意1:\(x=p_1^{c1}\cdotp_2^{c2}\cdots p_m^{cm}\) ,找出式子中对于某个质因数方的最小倍数,即为答案
注意2:对于 \(n!\) 数字太大难以计算,通过勒让定理计算 \(n!\) 中质因数 \(p\) 的个数 \(V\),
\(V_p(n!) \equiv \left\lfloor \frac{n}{p} \right\rfloor + \left\lfloor \frac{n}{p^2} \right\rfloor + \left\lfloor \frac{n}{p^3} \right\rfloor + \cdots\)
我们在代码实现不需要计算 \(p^2\) 等等质因数,累加 \(n\) 除以 \(p\) 就可以了

注意3:如果 \(x\) 最后不为 \(1\),那么说明经计算后的 \(x\) 也是一个质因数

Code
点击查看代码
void solve()
{
    cin >> n >> m;
    int ans = -1;
    for (int i = 2; i * i <= m; i++)
    {

        if (m % i == 0)
        {
            int cnt = 0;
            while (m % i == 0)
            {
                cnt++;
                m /= i;
            }
            int c2 = 0;
            int t = n;
            while (t > 0)
            {
                c2 += t / i;
                t /= i;
            }
            int res = c2 / cnt;
            if (ans == -1)
            {
                ans = res;
            }
            else
                ans = min(ans, res);
        }
    }
    if (m > 1)
    {
        int t = n;
        int c2 = 0;
        while (t > 0)
        {
            c2 += t / m;
            t /= m;
        }
        int res = c2 / 1;
        if (ans == -1)
        {
            ans = res;
        }
        else
            ans = min(ans, res);
    }
    if (ans == -1)
    {
        cout << 0 << endl;
    }
    else
    {
        cout << ans << endl;
    }
}

贡献法

https://codeforces.com/problemset/problem/1648/A

很容易发现,横坐标和纵坐标的计算互不干扰,所以可以分别计算,分别按升序排序后,就可以去除绝对值,对于一个坐标点\((x_i,y_i)\) 来说,比他小的坐标和他组成的组合对答案的贡献是正的 \((i-1)*x_i\),比他大的坐标和他组成的组合对答案的贡献是负的 \((n-i)*x_i\),通过这种方法可以分别计算每一个点横纵坐标对于答案的贡献

Code
点击查看代码
void solve()
{
    cin >> n >> m;
    vvi a(n + 1, vi(m + 1, 0));
    map<int, vi> p, q;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            cin >> a[i][j];
            p[a[i][j]].pb(i);
            q[a[i][j]].pb(j);
        }
    }
    int ans = 0;
    for (auto [i, c] : p)
    {
        sort(all(c));
        for (int j = 0; j < c.size(); j++)
        {
            ans += j * c[j] - (c.size() - j - 1) * c[j];
        }
    }
    for (auto [i, c] : q)
    {
        sort(all(c));
        for (int j = 0; j < c.size(); j++)
        {
            ans += j * c[j] - (c.size() - j - 1) * c[j];
        }
    }
    cout << ans << endl;
}

字符串的处理技巧

小美的01串翻转

思路

对于这种相邻字符不同的01串,一个很经典的思想,就是这种串只有两种形式 \(01010101……\) 或者 \(10101010…………\), 所以,只需要求得原字符串转换到他们的较小值就行了 ,发现枚举是 \(O(n^3)\) 的,我们采取前缀和进行优化,提前算出 \(pre[i]\) 来代表前 \(i\) 个字符与标准串的不同个数,优化到 \(O(n^2)\)

Code

点击查看代码
void solve()
{
    cin>>s;
    n=s.size();
    string s1="",s2="";
    vi p1(n+5),p2(n+5);
    for(int i=0;i<n;i++)
    {
        if(i&1)
        {
            s1+='1';
            s2+='0';
        }
        else {
            s1+='0';
            s2+='1';
        }
        p1[i+1]=p1[i]+(s1[i]!=s[i]);
        p2[i+1]=p2[i]+(s2[i]!=s[i]);
    }
    int ans=0;
    for(int len=2;len<=n;len++)
    {
        for(int l=0;l+len-1<n;l++)
        {
            int c1=0,c2=0;
            r=len+l-1;
            c1=p1[r+1]-p1[l];
            c2=p2[r+1]-p2[l];
            ans+=min(c1,c2);
        }
    }
    cout<<ans<<endl;
}

借助因子解决问题

https://codeforces.com/problemset/problem/1850/F

思路:对于每一个 大于 \(1\) 小于 \(n\) 的跳跃距离,都是有效的。遍历每一个点,处理每一个点的因子,如果一个跳跃距离是这个点的因子,那么就可以到达这个点,因为 如果 \(x%a_i==0\),那么 \(x%(x/a_i)==0\),所以只需要遍历到 \(\sqrt n\) 即可,时间复杂度是 \(O(n\sqrt n)\)

经典dfs搜索

https://atcoder.jp/contests/abc448/tasks/abc448_d

思路:从根开始 \(dfs\) ,用 \(set\) 维护经过的值,及时回溯

Code
点击查看代码
vi adj[M];
vi ans;
int cnt = 0;
set<int> p;
vi a;
void dfs(int cur, int fa)
{
    int now = 0;
    if (p.find(a[cur]) != p.end())
    {
        now++;
    }
    else
    {
        p.insert(a[cur]);
    }
    cnt += now;
    if (cnt > 0)
    {
        ans[cur] = 1;
    }
    for (int i : adj[cur])
    {
        if (i == fa)
            continue;
        dfs(i, cur);
    }
    cnt -= now;
    if (now == 0)
        p.erase(a[cur]);
}
void solve()
{
    cin >> n;
    a.resize(n + 1);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    for (int i = 1; i < n; i++)
    {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    ans.resize(n + 1, 0);
    dfs(1, -1);
    for (int i = 1; i <= n; i++)
    {
        if (ans[i] > 0)
            cout << "Yes" << endl;
        else
            cout << "No" << endl;
    }
}
posted @ 2026-04-15 18:07  Lambda_L  阅读(4)  评论(0)    收藏  举报