Skip to content

Commit 7b58700

Browse files
Merge pull request #5143 from Sosuke23/master
update PIE
2 parents 82f7021 + 1b65e49 commit 7b58700

File tree

2 files changed

+112
-38
lines changed

2 files changed

+112
-38
lines changed

content/5_Plat/PIE.mdx

+75-37
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,24 @@
22
id: PIE
33
title: 'Inclusion-Exclusion Principle'
44
author: Mihnea Brebenel
5+
contributor: Rameez Parwez
56
prerequisites:
67
- combo
78
description:
89
'The inclusion-exclusion principle is a counting technique that generalizes the formula for computing the size of union of n finite sets.'
910
frequency: 1
1011
---
1112

12-
## Tutorial
13+
## Resources
1314

1415
<Resources>
1516
<Resource source="cp-algo" title="The Inclusion-Exclusion Principle" url="https://cp-algorithms.com/combinatorics/inclusion-exclusion.html" starred> Well-covered article </Resource>
17+
<Resource source="CF" title="Inclusion-Exclusion Principle" url="https://codeforces.com/blog/entry/64625"> Good Explanation </Resource>
1618
<Resource source="Wikipedia" title="Inclusion-exclusion principle" url="https://en.wikipedia.org/wiki/Inclusion%E2%80%93exclusion_principle">Wiki</Resource>
1719
</Resources>
1820

21+
## Introduction
22+
1923
The inclusion-exclusion principle relates to finding the size of the union of some sets.
2024

2125
Verbally it can be stated as following:
@@ -79,8 +83,14 @@ for (int i = 1; i < VALMAX; i++) {
7983

8084
<FocusProblem problem="SQFREE" />
8185

86+
## Explanation
87+
8288
A perfect application for inclusion-exclusion principle and mobius function. In this particular case the set $A_i$ - previously mentioned in the tutorial section - denotes how many numbers are divisible with $i^2$ and we're asked to find out $\bigg| \bigcup_{i=1}^{\sqrt{n}} A_i \bigg|$. The precomputed mobius array tells whether to add or subtract $A_i$.
8389

90+
## Implementation
91+
92+
**Time Complexity:** $\mathcal{O}(V \log V + T \cdot \sqrt{n})$, where $V = 1e7$
93+
8494
<LanguageSection>
8595
<CPPSection>
8696

@@ -125,11 +135,17 @@ int main() {
125135

126136
<FocusProblem problem="cow" />
127137

138+
## Explanation
139+
128140
In this particular case the set $A_i$ - previously mentioned in the tutorial section - denotes how many pairs of cows have at least $i$ ice cream flavors in common. From the total number of pairs subtract the union of $A_i$. The global answer is:
129141
$$
130142
\frac{n \cdot(n-1)}{2}- \bigg| \bigcup_{i=1}^{5} A_i \bigg|
131143
$$
132144

145+
## Implementation
146+
147+
**Time Complexity:** $\mathcal{O}(N \log N)$
148+
133149
<LanguageSection>
134150
<CPPSection>
135151

@@ -206,39 +222,49 @@ print(ans, file=open("cowpatibility.out", "w"))
206222

207223
<FocusProblem problem="patterns" />
208224

209-
A dynamic programming approach with bitmasking would look like this: $dp[i][mask] = \text{the number of strings of length i that match all the patterns in set, but none other patterns. } $ The recurrence is:
225+
## Explanation 1
226+
227+
A dynamic programming approach with bitmasking would look like this:
228+
229+
$dp[i][mask] = \text{the number of strings of length i that match all the patterns in set, but none other patterns. } $ The recurrence is:
210230
$$
211231
dp[i][mask \& j]=dp[i-1][j]\text{ where j is a set of patterns that match charachter c at position i}
212232
$$
213233
The following code illustrates this:
214234

235+
## Implementation
236+
237+
**Time Complexity:** $\mathcal{O}(M \cdot N \cdot 2^N)$, where $M$ is size of each strings in patterns and $N$ is the size of patterns.
238+
215239
<LanguageSection>
216240
<CPPSection>
217241

218242
```cpp
243+
const int MOD = 1000003;
244+
219245
int howMany(vector<string> patterns, int k) {
220-
vector<vector<int>> dp(50, vector<int>((1 << (int)patterns.size())));
221-
for (int i = 0; i < (int)patterns[0].size(); i++) {
246+
int n = patterns.size();
247+
int m = patterns[0].size();
248+
vector<vector<int>> dp(50, vector<int>(1 << n));
249+
for (int i = 0; i < m; i++) {
222250
for (char c = 'a'; c <= 'z'; c++) {
223251
int mask = 0;
224-
for (int j = 0; j < (int)patterns.size(); j++) {
252+
for (int j = 0; j < n; j++) {
225253
if (patterns[j][i] == c || patterns[j][i] == '?') { mask |= (1 << j); }
226254
}
227255
if (i == 0) {
228256
dp[i][mask]++;
229257
} else {
230-
for (int j = 0; j < (1 << (int)patterns.size()); j++) {
258+
for (int j = 0; j < (1 << n); j++) {
231259
dp[i][j & mask] = (dp[i][j & mask] + dp[i - 1][j]) % MOD;
232260
}
233261
}
234262
}
235263
}
236264
237265
int ans = 0;
238-
for (int mask = 0; mask < (1 << (int)patterns.size()); mask++) {
239-
if (__builtin_popcount(mask) == k) {
240-
ans = (ans + dp[(int)patterns[0].size() - 1][mask]) % MOD;
241-
}
266+
for (int mask = 0; mask < (1 << n); mask++) {
267+
if (__builtin_popcount(mask) == k) { ans = (ans + dp[m - 1][mask]) % MOD; }
242268
}
243269
244270
return ans;
@@ -248,6 +274,8 @@ int howMany(vector<string> patterns, int k) {
248274
</CPPSection>
249275
</LanguageSection>
250276

277+
## Explanation 2
278+
251279
The problem can also be solved using the inclusion exclusion principle.
252280

253281
An important observation is that we can easily count the strings that satisfy some specific patterns. Simply iterate through the positions of all patterns. If all the patterns contain $?$ then we can use any letter from $a$ to $z$ giving us $26$ solution, otherwise we can only put the fixed letter contained by a pattern. The answer is the product.
@@ -258,49 +286,59 @@ $$
258286
solve(A) = \sum_{B \supseteq A} (-1)^{|B|-k} \cdot f(B)
259287
$$
260288

261-
$f(B)$ denotes the number of strings matching _at leat_ set $B$
289+
$f(B)$ denotes the number of strings matching _at least_ set $B$
262290

263291
The global answer is:
264292
$$
265293
ans = \sum_{A:|A|=k} solve(A)
266294
$$
267295

296+
## Implementation
297+
298+
**Time Complexity:** $\mathcal{O}(\binom{N}{k} \cdot 2^{N - k} \cdot M \cdot N)$
299+
268300
<LanguageSection>
269301
<CPPSection>
270302

271303
```cpp
304+
const int MOD = 1000003;
305+
272306
int howMany(vector<string> patterns, int k) {
307+
int n = patterns.size();
308+
int m = patterns[0].size();
273309
int ans = 0;
274-
for (int mask = 0; mask < (1 << (int)patterns.size()); mask++) {
275-
if (__builtin_popcount(mask) == k) {
276-
for (int supermask = mask; supermask < (1 << (int)patterns.size());
277-
supermask++) {
278-
if ((mask & supermask) == mask) {
279-
int sign = ((__builtin_popcount(supermask) - k) & 1 ? -1 : 1);
280-
int cnt = 1;
281-
for (int i = 0; i < (int)patterns[0].size(); i++) {
282-
bool flag = true;
283-
int j = 0, last_letter = -1;
284-
for (; j < (int)patterns.size(); j++) {
285-
if (((1 << j) & supermask) == 0) { continue; }
286-
if (patterns[j][i] != '?') {
287-
flag = false;
288-
if (last_letter == -1) {
289-
last_letter = j;
290-
} else {
291-
break;
292-
}
293-
}
294-
}
295-
if (flag) {
296-
cnt = cnt * 26 % MOD;
297-
} else if (j < (int)patterns.size()) {
298-
cnt *= 0;
310+
for (int mask = 0; mask < (1 << n); mask++) {
311+
// subsets with exactly k patterns matters
312+
if (__builtin_popcount(mask) != k) { continue; }
313+
// iterate over all superset of current subset mask
314+
for (int supermask = mask; supermask < (1 << n); supermask++) {
315+
if ((mask & supermask) != mask) { continue; }
316+
int sign = ((__builtin_popcount(supermask) - k) & 1 ? -1 : 1);
317+
int freq = 1; // checks how many valid strings satisfy the supermask
318+
for (int i = 0; i < m; i++) {
319+
bool flag = true;
320+
char last_letter = '?';
321+
for (int j = 0; j < n; j++) {
322+
if (((1 << j) & supermask) == 0) { continue; }
323+
// check for conflicts if the pattern specifies a letter not '?'
324+
if (patterns[j][i] != '?') {
325+
if (last_letter == '?') {
326+
last_letter = patterns[j][i];
327+
} else if (patterns[j][i] != last_letter) {
328+
// conflict! two patterns require different letters here
329+
flag = false;
330+
break;
299331
}
300332
}
301-
ans = (ans + sign * cnt) % MOD;
333+
}
334+
if (!flag) {
335+
freq = 0;
336+
} else if (last_letter == '?') {
337+
freq = (freq * 26) % MOD;
302338
}
303339
}
340+
ans = (ans + sign * freq) % MOD;
341+
ans = (ans + MOD) % MOD;
304342
}
305343
}
306344

content/5_Plat/PIE.problems.json

+37-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
{
3333
"uniqueId": "patterns",
3434
"name": "SetOfPatterns",
35-
"url": "https://community.topcoder.com/stat?c=problem_statement&pm=8307",
35+
"url": "https://archive.topcoder.com/ProblemStatement/pm/8307",
3636
"source": "TopCoder",
3737
"difficulty": "Hard",
3838
"isStarred": false,
@@ -66,6 +66,42 @@
6666
"solutionMetadata": {
6767
"kind": "internal"
6868
}
69+
},
70+
{
71+
"uniqueId": "cses-2421",
72+
"name": "Counting Reorders",
73+
"url": "https://cses.fi/problemset/task/2421",
74+
"source": "CSES",
75+
"difficulty": "Normal",
76+
"isStarred": false,
77+
"tags": ["PIE"],
78+
"solutionMetadata": {
79+
"kind": "none"
80+
}
81+
},
82+
{
83+
"uniqueId": "cses-2417",
84+
"name": "Counting Coprime Pairs",
85+
"url": "https://cses.fi/problemset/task/2417",
86+
"source": "CSES",
87+
"difficulty": "Normal",
88+
"isStarred": false,
89+
"tags": ["PIE", "Divisors"],
90+
"solutionMetadata": {
91+
"kind": "none"
92+
}
93+
},
94+
{
95+
"uniqueId": "cses-2429",
96+
"name": "Grid Completion",
97+
"url": "https://cses.fi/problemset/task/2429",
98+
"source": "CSES",
99+
"difficulty": "Normal",
100+
"isStarred": false,
101+
"tags": ["PIE"],
102+
"solutionMetadata": {
103+
"kind": "none"
104+
}
69105
}
70106
]
71107
}

0 commit comments

Comments
 (0)