Friends and Pizza (Educational Codeforces Round 175 (Rated for Div. 2))
Monocarp has nnn pizzas, the iii-th pizza consists of aia_iai slices. Pizzas are denoted by uppercase Latin letters from A to the nnn-th letter of the Latin alphabet.
Monocarp also has mmm friends, and he wants to invite exactly two of them to eat pizza. For each friend, Monocarp knows which pizzas that friend likes.
After the friends arrive at Monocarp’s house, for each pizza, the following happens:
- if the pizza is not liked by any of the two invited friends, Monocarp eats it;
- if the pizza is liked by exactly one of the two invited friends, that friend eats it;
- and if the pizza is liked by both friends, they try to split it. If it consists of an even number of slices, they both eat exactly half of the slices. But if the pizza consists of an odd number of slices, they start quarrelling, trying to decide who will eat an extra slice — and Monocarp doesn’t like that.
For each kkk from 000 to ∑ai\sum a_i∑ai, calculate the number of ways to choose exactly two friends to invite so that the friends don’t quarrel, and Monocarp eats exactly kkk slices.
Input
The first line contains two integers nnn and mmm (1≤n≤201 \le n \le 201≤n≤20; 2≤m≤5⋅1052 \le m \le 5 \cdot 10^52≤m≤5⋅105) — the number of pizzas and the number of friends, respectively.
The second line contains mmm strings s1,s2,…,sms_1, s_2, \dots, s_ms1,s2,…,sm (1≤∣si∣≤n1 \le |s_i| \le n1≤∣si∣≤n), where sis_isi is a string consisting of distinct characters from A to the nnn-th letter of the Latin alphabet, denoting which pizzas the iii-th friend likes. In every string sis_isi, the characters are sorted in lexicographical (alphabetic) order.
The third line contains nnn integers a1,a2,…,ana_1, a_2, \dots, a_na1,a2,…,an (1≤ai≤2⋅1041 \le a_i \le 2 \cdot 10^41≤ai≤2⋅104) — the sizes of the pizzas.
Output
Print ∑ai+1\sum a_i + 1∑ai+1 integers, where the kkk-th integer (starting from 000) should be the number of ways to choose exactly two friends to invite so that the friends don’t quarrel, and Monocarp eats exactly kkk slices.
Example
Input
3 6A AB ABC AB BC C2 3 5
Output
4 0 0 1 0 2 0 0 0 0 0
Note
Let’s consider all pairs of friends from the first example:
- if Monocarp invites friends 111 and 222, they will eat pizzas 111 and 222, and he’ll eat the 333-rd pizza;
- if Monocarp invites friends 111 and 333, they will eat all of the pizzas;
- if Monocarp invites friends 111 and 444, they will eat pizzas 111 and 222, and he’ll eat the 333-rd pizza;
- if Monocarp invites friends 111 and 555, they will eat all of the pizzas;
- if Monocarp invites friends 111 and 666, they will eat pizzas 111 and 333, and he’ll eat the 222-nd pizza;
- if Monocarp invites friends 222 and 333, they will quarrel because of the 222-nd pizza;
- if Monocarp invites friends 222 and 444, they will quarrel because of the 222-nd pizza;
- if Monocarp invites friends 222 and 555, they will quarrel because of the 222-nd pizza;
- if Monocarp invites friends 222 and 666, they will eat all of the pizzas;
- if Monocarp invites friends 333 and 444, they will quarrel because of the 222-nd pizza;
- if Monocarp invites friends 333 and 555, they will quarrel because of the 222-nd pizza;
- if Monocarp invites friends 333 and 666, they will quarrel because of the 333-rd pizza;
- if Monocarp invites friends 444 and 555, they will quarrel because of the 222-nd pizza;
- if Monocarp invites friends 444 and 666, they will eat all of the pizzas;
- if Monocarp invites friends 555 and 666, they will quarrel because of the 333-rd pizza.
题目描述
给定一个包含 n 个元素的集合,集合中的每个元素代表一个数字。并且有 m 个字符串,其中每个字符串由大写字母组成。每个字符串的出现代表某些元素的子集。你需要根据这些字符串,计算每个可能的数字和的出现次数。
具体来说,我们有以下信息:
- 有
n个整数a1, a2, ..., an,每个元素为正整数。 - 有
m个字符串,字符串的每个字符都是大写字母(最多 20 个字母)。 - 每个字符串代表某个元素的子集。你需要根据这些字符串计算每个可能的数字和的出现次数。
题解
思路
-
问题转化:
- 将每个朋友喜欢的比萨转化为二进制掩码,使用位操作(AND 和 OR)来判断他们是否会争吵。
- 考虑所有朋友对 (i,j)(i, j)(i,j),其中 1≤i,j≤n1 \le i, j \le n1≤i,j≤n。然后,通过减去 i=ji = ji=j 的情况并除以 2,避免重复计算对称的朋友对。
-
两种特殊情况:
- 偶数比萨:如果所有比萨的大小是偶数,那么朋友们不会因比萨的奇数大小而争吵,可以通过OR卷积计算。OR卷积可以使用SOS DP来优化。
- 奇数比萨:如果所有比萨的大小是奇数,则需要使用快速子集卷积(FSC)。FSC通过为每个比萨的组合计算相应的popcount(位数)来加速计算。
-
SOS DP原理:
- SOS DP可以高效地计算序列的子掩码和,用来处理OR卷积。
- 逆SOS DP则是通过减法操作恢复原始序列。
-
解决混合情况:
- 如果比萨的大小有奇偶之分,结合OR卷积和FSC的思想,分开处理并合并结果。
通过这些技巧,可以高效地计算所有符合条件的朋友对数,时间复杂度为 O(n22n+mn)O(n^2 2^n + mn)O(n22n+mn),其中 mmm 是处理掩码转换的时间。
代码分析
-
输入:
- 输入通过
getchar()和fread进行高效读取,适用于大数据量的输入。
- 输入通过
-
子集计算:
- 通过位运算将每个字母映射为二进制数,并通过组合这些二进制数来表示一个子集。
-
频次计算:
- 对于每个输入字符串,根据其字母的组合更新
f和cnt数组。
- 对于每个输入字符串,根据其字母的组合更新
-
动态规划:
- 利用
f数组的状态,通过动态规划的方式将所有子集的结果合并起来,最终得到每种和出现的次数。
- 利用
代码实现
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define BoBoowen ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
using namespace std;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const int N = 5e5 + 10;
int n, m, k;
int f[1 << 20];
int cnt[1 << 20];
int ans[1 << 20];
int a[25];
int C;
int sol[20 * 20000 + 5];
char buf[1 << 20], *p1, *p2;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 20, stdin), p1 == p2) ? 0 : *p1++)
template <typename T>
inline void readT(T &x)
{
bool f = 1;
x = 0;
char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-')
f = !f;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
x = (f ? x : -x);
return;
}
inline void read128(__int128 &x)
{
bool f = 1;
x = 0;
char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-')
f = !f;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
x = (f ? x : -x);
return;
}
template <typename T>
inline void writeT(T x)
{
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
writeT(x / 10);
putchar(x % 10 + '0');
return;
}
inline void readS(std::string &s)
{
char ch = getchar();
while (ch == ' ' || ch == '\n')
ch = getchar();
while (ch != ' ' && ch != '\n')
s += ch, ch = getchar();
}
inline void writeS(std::string s)
{
int len = s.length();
for (int i = 0; i < len; i++)
putchar(s[i]);
}
void solved()
{
readT(n);
readT(m);
for (int i = 1; i <= m; i++)
{
string s;
readS(s);
int z = 0;
for (auto c : s)
{
z |= (1 << (c - 'A'));
}
f[z]++;
cnt[z]++;
}
int suma = 0;
for (int i = 0; i < n; i++)
{
readT(a[i]);
suma += a[i];
}
for (int i = 0; i < n; i++)
{
for (int j = 0; j < (1 << n); j++)
{
if ((j >> i) & 1)
{
f[j] += f[j ^ (1 << i)];
}
}
}
for (int i = 0; i < n; i++)
{
if (a[i] & 1)
{
C |= (1 << i);
}
}
int D = (1 << 20) - 1 - 7;
for (int i = 0; i < (1 << n); i++)
{
int S = (i & D);
for (int j = S; j; j = (j - 1) & S)
{
ans[i] += 1LL * cnt[j] * f[i ^ (j & C)];
if (i & 1)
ans[i] += 1LL * cnt[j | 1] * f[i ^ ((j | 1) & C)];
if (i & 2)
ans[i] += 1LL * cnt[j | 2] * f[i ^ ((j | 2) & C)];
if (3 == (i & 3))
ans[i] += 1LL * cnt[j | 3] * f[i ^ ((j | 3) & C)];
if (i & 4)
ans[i] += 1LL * cnt[j | 4] * f[i ^ ((j | 4) & C)];
if (5 == (i & 5))
ans[i] += 1LL * cnt[j | 5] * f[i ^ ((j | 5) & C)];
if (6 == (i & 6))
ans[i] += 1LL * cnt[j | 6] * f[i ^ ((j | 6) & C)];
if (7 == (i & 7))
ans[i] += 1LL * cnt[j | 7] * f[i ^ ((j | 7) & C)];
}
{
int j = 0;
if (i & 1)
ans[i] += 1LL * cnt[j | 1] * f[i ^ ((j | 1) & C)];
if (i & 2)
ans[i] += 1LL * cnt[j | 2] * f[i ^ ((j | 2) & C)];
// printf("%d %lld : %d %d\n",i,ans[i] , cnt[0 | 1] , f[i ^ ((0 | 1) & C)]) ;
if (3 == (i & 3))
ans[i] += 1LL * cnt[j | 3] * f[i ^ ((j | 3) & C)];
if (i & 4)
ans[i] += 1LL * cnt[j | 4] * f[i ^ ((j | 4) & C)];
if (5 == (i & 5))
ans[i] += 1LL * cnt[j | 5] * f[i ^ ((j | 5) & C)];
if (6 == (i & 6))
ans[i] += 1LL * cnt[j | 6] * f[i ^ ((j | 6) & C)];
if (7 == (i & 7))
ans[i] += 1LL * cnt[j | 7] * f[i ^ ((j | 7) & C)];
}
}
for (int i = 0; i < n; i++)
{
for (int j = (1 << n) - 1; j >= 0; j--)
{
if ((j >> i) & 1)
{
ans[j] -= ans[j ^ (1 << i)];
}
}
}
for (int i = 0; i < (1 << n); i++)
{
if ((i & C) == 0)
{
ans[i] -= cnt[i];
}
ans[i] /= 2;
int s = 0;
for (int j = 0; j < n; j++)
{
if ((i >> j) & 1)
{
s += a[j];
}
}
sol[suma - s] += ans[i];
}
for (int i = 0; i <= suma; i++)
{
writeT(sol[i]);
writeS(" ");
}
return;
}
signed main()
{
// BoBoowen;
int T = 1;
// cin >> T;
while (T--)
{
solved();
}
}
代码分析
-
输入部分:
getchar和fread用于快速输入,适用于大规模数据的读取。
-
字符串处理与映射:
- 每个字符串中的字母通过
z |= (1 << (c - 'A'))映射为一个二进制数,表示字符串的字母组合。
- 每个字符串中的字母通过
-
频次统计与动态规划:
- 使用
f数组记录每个子集的出现次数,随后通过动态规划推导每个组合的结果。
- 使用
-
输出部分:
- 最后根据计算得到的结果,输出每个数字和出现的次数。
复杂度分析
- 时间复杂度:O(m * n * 2^n),其中
m是字符串的数量,n是集合的大小。由于每次更新子集组合需要遍历所有的子集,因此时间复杂度较高。


被折叠的 条评论
为什么被折叠?



