第一场-1003与1007题解-高维前缀和(个人学习笔记)

1 前言:

个人学习用途,无法用作题解参考

2 题解

1007 树上LCM

题目描述:
给你一棵由 n个节点的树和一个数 x,其中每个节点都有一个值。有多少条简单路径的值的 lcm 为 x?

首先要知道LCM本质为两个数的质因数数量取max, x 的质因数最多有 7 种。将顶点上的值 ai 状态压缩成二进制数 bi 如果 ai 的某位质因数的数量与 x 相同,令 bi 的这一位置为 1,小于定义为0,大于直接令bi为 -1,此时只要路径上存在 ai 则LCM一定不为 x 。之后进行树上DP, 树的合并状态转移为 f[nownode][st1|st2] = f[lnode][st1] + f[rnode][st2]; 如果lnode或rnode对应 ai 为 -1 则跳过。这样暴力枚举的时间复杂度为O(n*(2k)*(2k))无法满足要求。可以用高维前缀和处理出rnode所有状态的父状态数量和,st1 与 (2^k -1)^st1的所有父状态构成答案,结果加上 f[lnode][st1] * sumfa[(2^k -1)^st1] 。树的合并用f[nownode][nowval | j] += f[nextnode][j]; 进行转移,遍历子树前要加上nownode单独一个点的情况。遇到任意一个节点为-1时跳过即可。

#include <iostream>
#include <algorithm>
#include <map>
#include <vector>
#include <cstring>
#include <cmath>
#include <set>
#include <queue>

typedef long double ld;
#define int long long
#define INF 1e18
#define endl '\n'
#define MOD 1000000000000000003

using namespace std;

int n, x;
vector<vector<int>> tree;

vector<int> val;
map<int, int> prinum;

vector<vector<int>>f;

int stnum;
int ans = 0;
void dfs(int nownode, int fa)
{
    int nowval = val[nownode];
    if(nowval!=-1)
    {
    f[nownode][nowval]++;
    ans += f[nownode][stnum - 1];
    }
    for (int i = 0; i < tree[nownode].size(); i++)
    {
        int nextnode = tree[nownode][i];
        if (nextnode == fa )
        {
            continue;
        }
        dfs(nextnode, nownode);
        if(val[nextnode] == -1||val[nownode]==-1)
        {
            continue;
        }
        vector<int> nextf(stnum + 1);
        for (int j = 0; j < stnum; j++)
        {
            nextf[j] = f[nextnode][j];
        }

        for (int j = 0; j < prinum.size(); j++)
        {
            for (int k = 0; k < stnum; k++)
            {
                if (!((k >> j) & 1))
                {
                    nextf[k] += nextf[k ^ (1 << j)];
                }
            }
        }
        //
        for (int j = 0; j < stnum; j++)
        {
            ans += f[nownode][j] * nextf[(stnum - 1) ^ j];
        }
        //
        for (int j = 0; j < stnum; j++)
        {
            f[nownode][nowval | j] += f[nextnode][j];
        }
    }
}
void solve()
{

    cin >> n >> x;
    f=vector<vector<int>>(n+5, vector<int>(150, 0));
    ans = 0;
    tree = vector<vector<int>>(n + 5);
    val = vector<int>(n + 5);
    prinum = map<int, int>();
    int cpx = x;
    for (int i = 2; i <= x; i++)
    {
        while (cpx % i == 0)
        {
            prinum[i]++;
            cpx /= i;
        }
    }

    stnum = (1 << prinum.size());
    for (int i = 1; i <= n - 1; i++)
    {
        int a, b;
        cin >> a >> b;
        tree[a].push_back(b);
        tree[b].push_back(a);
    }

    for (int i = 1; i <= n; i++)
    {
        int tmp;
        cin >> tmp;
        int cnt = 0;
        int nowval = 0;
        for (auto it = prinum.begin(); it != prinum.end(); it++)
        {
            int nownum = 0;

            while (tmp % it->first == 0)
            {
                tmp /= it->first;
                nownum++;
            }
            if (nownum == it->second)
            {
                nowval |= (1 << cnt);
            }
            else if (nownum > it->second)
            {
                nowval = -1;
                break;
            }
            cnt++;
        }
        if (tmp != 1)
        {
            nowval = -1;
        }
        val[i] = nowval;
    }

    dfs(1, 0);
    cout << ans << endl;
}

signed main()
{
    std::ios::sync_with_stdio(0);
    std::cin.tie(0);
    int T = 1;

    cin >> T;
    for (int i = 1; i <= T; i++)
    {
        solve();
    }
    return 0;
}

1003 奸商

题目描述:
给你长度为 n 只有前 17 个字母的字符串,定义一个字符串优秀为这个字符串的所有长度 leni 大于 len 的子串 s 至少有一个位置 i 满足 si == s(leni+1-i)。你可以花费 wi 使得第 i 个字母能够与所有大于他的字母相同。问你使这个字符串优秀的最小花费为多少。

长度为奇数的字符串一定优秀,长度为偶数的字符串 s 如果优秀,则与 s 的对称中心相同的 s 的父串 fas 都优秀。那么使整个字符串优秀的条件就只需要所有长度为 len 的字串优秀即可,如果len为奇数则len++ 。

暴力做法:定义((st>>i)&1) ==1 为取第 i 个字母 。设 si 为长度为 len 的第 i 个子串,f[st] = 1定义为st是一种可能的解,设fa(st)为st的所有父集 ,f[fa(st)]都为可能解。遍历si与s(i+1)的所有状态,f[st1|st2] = f[st1] & f[st2]。但是时间显然超时。

高位前缀和优化:定义((st>>i)&1) ==1 为"不取"第 i 个字母 。若 st 不为解则fa(st)不为解。遍历长度为 len 的 si,找出 si 所有能够使 si 优秀的字母letter ,令st为不取所有letter,显然st不为答案且fa(st)都不为答案,fa(st)可以用高维前缀和快速得出,对所有 si 进行这样的操作取或后得出所有不是答案的状态,再遍历所有合法状态取花费最小的为结果。
注意:当si本身为优秀,不应该使f[0]=1 及 f[fa(0)]=1 ,而是直接跳过。

#include <iostream>
#include <algorithm>
#include <map>
#include <vector>
#include <cstring>
#include <cmath>
#include <set>
#include <queue>

typedef long double ld;
#define int long long
#define INF 1e18
#define endl '\n'
#define MOD 1000000000000000003

using namespace std;

const int N = (1 << 17);
int n;
vector<int> arr;
int val[17];

vector<int> f;
void solve()
{
    cin >> n;
    arr = vector<int>(n + 5);
    f = vector<int>(N + 10);
    for (int i = 1; i <= n; i++)
    {
        char ch;
        cin >> ch;
        arr[i] = ch - 'a';
    }

    for (int i = 0; i < 17; i++)
    {
        cin >> val[i];
    }
    int len;
    cin >> len;
    if (len & 1)
    {
        len++;
    }

    for (int i = 1; i <= n; i++)
    {
        if (i + len - 1 > n)
        {
            break;
        }
        int nowst = 0;
        for (int j = 0; j < len; j++)
        {
            if (arr[i + j] < arr[i + len - 1 - j])
            {
                nowst |= (1 << arr[i + j]);
            }
            else if (arr[i + j] > arr[i + len - 1 - j])
            {
                nowst |= (1 << arr[i + len - 1 - j]);
            }
            else
            {
                nowst = 0;
                break;
            }
        }
        if (nowst == 0)
        {
            continue;
        }
        f[nowst] = 1;
    }

    for (int j = 0; j < 17; j++)
    {
        for (int i = 0; i < N; i++)
        {
            if ((i >> j) & 1)
            {
                f[i] |= f[i ^ (1 << j)];
            }
        }
    }
    int ans = INF;
    for (int i = 0; i < N; i++)
    {
        if (f[i] > 0)
        {
            continue;
        }
        int sum = 0;
        for (int j = 0; j < 17; j++)
        {
            if (((i >> j) & 1) == 0)
            {
                sum += val[j];
            }
        }
        ans = min(ans, sum);
    }
    cout << ans << endl;
}

signed main()
{
    // std::ios::sync_with_stdio(0);
    // std::cin.tie(0);
    int T = 1;

    cin >> T;
    for (int i = 1; i <= T; i++)
    {
        solve();
    }
    return 0;
}

3 总结

对高维前缀和的理解:能够快速取得每一个状态的所有父状态或子状态。
for (int j = 0; j < 17; j++)
{
for (int i = 0; i < stnum; i++)
{
if ((i >> j) & 1)
{
f[i] |= f[i ^ (1 << j)];
}
}
}
使每一个状态仅有一条路径到达其父状态或子状态。

posted @ 2025-07-21 08:45  青一凡  阅读(60)  评论(2)    收藏  举报