第一场-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)];
}
}
}
使每一个状态仅有一条路径到达其父状态或子状态。

浙公网安备 33010602011771号