Structs & Data Structures · intermediate · ~15 min
Split a set to meet a sum condition.
Partition problems ask whether a set can be split so the parts balance, and how many ways there are to hit a target sum. Both reduce to the subset-sum table from the knapsack lesson. Equal partition is the neat case: a set can be split into two equal halves only if its total is even and some subset sums to exactly half — so the feasibility question becomes one subset-sum query. Counting subsets that reach a target uses the same table with += instead of a boolean OR, and the same downward capacity sweep that keeps each element used at most once.
Balanced partitioning is load balancing: split jobs across two machines so they finish together, divide a bill fairly, distribute weight evenly across two containers. The counting variant answers "how many ways" questions in combinatorics and probability. And the parity shortcut — an odd total can never split evenly — is a good habit: cheap impossibility checks before expensive computation.
The parity gate. If the total is odd, no equal partition exists. Return early; this costs one pass and rules out half of all inputs.
Reduce to subset sum. With an even total S, ask whether any subset sums to S/2. If one does, the remaining elements necessarily sum to S/2 as well — so a single subset-sum query answers the partition question.
Feasibility table. dp[0] = 1 (the empty subset reaches zero), then for each element sweep the target downward: dp[s] |= dp[s - a[i]]. Downward is what enforces once-each use, exactly as in the 0/1 knapsack.
Counting table. Replace the OR with addition: dp[s] += dp[s - a[i]], still sweeping downward. dp[t] then holds the number of distinct subsets summing to t.
Zeros and negatives. An element equal to 0 doubles the count of every subset (include it or not) — correct, but surprising. Negative values break the amount-indexed table entirely; they need a shifted index range or a different formulation.
Complexity. O(n x target), pseudo-polynomial — fine for modest sums, expensive when the total is huge even if n is small.
#include <stdlib.h>
int can_partition(const int *a, int n) {
long long tot = 0;
for (int i = 0; i < n; i++) tot += a[i];
if (tot & 1) return 0; /* odd total -> impossible */
int half = (int)(tot / 2);
char *dp = calloc((size_t)half + 1, 1);
if (!dp) return -1;
dp[0] = 1; /* empty subset reaches 0 */
for (int i = 0; i < n; i++)
for (int s = half; s >= a[i]; s--) /* DOWNWARD: use each element once */
if (dp[s - a[i]]) dp[s] = 1;
int ok = dp[half];
free(dp);
return ok;
}
/* how many subsets sum to exactly t */
long long count_subsets(const int *a, int n, int t) {
long long *dp = calloc((size_t)t + 1, sizeof *dp);
if (!dp) return -1;
dp[0] = 1;
for (int i = 0; i < n; i++)
for (int s = t; s >= a[i]; s--)
dp[s] += dp[s - a[i]];
long long r = dp[t];
free(dp);
return r;
}
Key points:
long long before halving — the total can overflow int even when each element is small.dp[0] = 1 seeds both variants; calloc supplies the zeros everywhere else.Equal-sum partition asks whether a set splits into two equal halves — a subset-sum to total/2, feasible only when the total is even. Counting subsets with a given sum is the same table with addition instead of a boolean OR, tallying how many ways rather than whether.
#include <stdio.h>
#include <stdlib.h>
static int can_partition(const int*a,int n){long long tot=0;for(int i=0;i<n;i++)tot+=a[i];if(tot&1)return 0;int half=tot/2;char*dp=calloc(half+1,1);dp[0]=1;for(int i=0;i<n;i++)for(int t=half;t>=a[i];t--)if(dp[t-a[i]])dp[t]=1;int r=dp[half];free(dp);return r;}
static long long count_subsets(const int*a,int n,int t){long long*dp=calloc(t+1,sizeof(long long));dp[0]=1;for(int i=0;i<n;i++)for(int s=t;s>=a[i];s--)dp[s]+=dp[s-a[i]];long long r=dp[t];free(dp);return r;}
int main(void){
int a[]={1,5,11,5};
printf("can {1,5,11,5} split into equal halves? %s\n", can_partition(a,4)?"yes":"no");
int b[]={1,2,3,3};
printf("subsets of {1,2,3,3} summing to 6 = %lld\n", count_subsets(b,4,6));
return 0;
}
| Step | Line | What happens |
|---|---|---|
| 1 | sum {1,5,11,5} |
tot = 22, even, so a split is at least conceivable. |
| 2 | half = 11 |
The question becomes: does any subset sum to 11? |
| 3 | dp[0] = 1 |
Only zero is reachable before any element is considered. |
| 4 | element 1, sweep down | dp[1] becomes reachable. |
| 5 | element 5, then 11, then 5 | Reachable sums accumulate; dp[11] is set by {11} and also by {1,5,5}. |
| 6 | dp[half] |
1 -> the set splits into {11} and {1,5,5}, both summing to 11. |
Not short-circuiting on an odd total; using OR when you meant to count.
Compiler errors and warnings:
warning: implicit conversion narrowing tot to int; cast explicitly after the parity check.-Wsign-compare at the calloc size; cast to size_t.Runtime symptoms:
dp[0] = 1 is missing, so nothing is ever reachable.half or the index range meaningless — this formulation assumes non-negative values.tot / 2 silently truncates and you answer a different question.long long, or the problem's modulus.Technique: test {1,5,11,5} (true) and {1,2,3,5} (total 11, odd -> false immediately). The second confirms the parity gate fires before any allocation.
tot must be long long; summing many int values into an int can overflow (undefined behaviour) and produce a nonsense half.half derives from input data. A negative or absurdly large total turns (size_t)half + 1 into a wrapped or enormous allocation. Bound the input and check the result of calloc.dp[half] into a local before freeing.Concrete uses: Splitting tasks between two workers so both finish at the same time. Balancing cargo across two holds. Dividing an expense fairly between two parties. In testing, generating balanced data splits. The counting variant answers combinatorial questions such as how many ways a score can be reached from a set of point values.
Professional best practices:
Beginner:
Intermediate:
1. (Beginner) Equal partition. Implement int can_partition(const int *a, int n) with the parity gate. Example: {1,5,11,5} -> 1; {1,2,3,5} -> 0. Concepts: reduction to subset sum.
2. (Beginner) Count subsets. Implement long long count_subsets(const int *a, int n, int t). Example: {1,1,2,3}, t=3 -> 3. Concepts: counting instead of feasibility.
3. (Intermediate) Minimum difference. Find the smallest possible difference between the two parts when an equal split is impossible. Hint: find the largest reachable s <= tot/2; the answer is tot - 2*s. Concepts: using the whole reachability table.
4. (Intermediate) Bitset feasibility. Reimplement the feasibility test with a bitset, shifting by each element and ORing. Concepts: bit-parallel DP, reuse of the bitset lesson.
Partition problems reduce to subset sum. An equal split is possible only when the total is even and some subset reaches exactly half, so a cheap parity check comes first and one subset-sum query answers the rest. The table is seeded with dp[0] = 1 and swept downward for each element, which is what keeps every element used at most once; swapping the OR for += counts the subsets instead of merely testing feasibility. Sum into long long, enforce non-negative inputs because the table is indexed by sum, and remember the cost is pseudo-polynomial in the target.