Per the OP's request, below is Python code that I used to simulate question #3, the results of which agree with the (revised) answer of @Dodezw, i.e.,$$\mathbb{E}(N)= m\sum_{k=2}^n \bigl(1-\bigl(1-k\,q\,p^{n-1}\bigr)^{m-1}\bigr)\binom nk\,q^kp^{n-k}.$$
Here are contour plots of the above function, for $q=1/2$ and $q=1/3$, for $2\le m\le 100$ and $2\le n\le 20$. Each curve interpolates $(m,n)$-values for which $\mathbb{E}(N)$ approximates the labelled constant:
Numerical observations:
- For all $q\in(0,1)$ and all $n\ge 1$, $f(m,n,q):=\mathbb{E}(N)$ is a strictly increasing function of $m$.
- For all $q\in(0,1)$ and all $m\ge 2$, as $n$ increases, $f(m,n,q)$ first increases to a maximum, then decreases toward $0$: i.e., there exists $n^*$, such that $f(m,n^*,q)=\max_{n}f(m,n,q).\ $ (NB: $n^*(m,q)$ is the dotted line shown in each plot above, forming a "ridge line" along the top of the "hill" we're "viewing from above" in each of these plots.)
- For $q=1/2$ and for $m$ up to $4$, Wolfram Alpha symbolically computes that
$$\mathbb{E}(N)= m\,(m+1)\,n\,2^{-(n+1)}(1+\mathcal{O}(n\,2^{-(n+1)}))
$$ which suggests the approximate formula
$$f(m,n,1/2) \approx g(m,n):=m\,(m+1)\,n\,2^{-(n+1)}
$$
for some range of $(m,n)$-values. The following plot shows error contours, such that for all points $(m,n)$ above a given labelled contour, $|g(m,n)-f(m,n,1/2)|$ is less than the label-value:
Python code for simulating question #3:
import math
import random
def get_M(m, n, q):
"""returns m x n matrix M with elements i.i.d Bernoulli(q), q=P(bit=1)"""
M = []
for i in range(m):
row = [(1 if random.random()<q else 0) for j in range(n)]
M += [row]
return M
def trial(m, n, q):
"""return the number of dropped rows in one sample of M(m, n, q)"""
M = get_M(m, n, q)
indrows = [] # accumulates "indicator" rows (i.e. which rows have exactly one 1)
indcols = [] # accumulates "indicator" cols (i.e. which cols have the 1 in the indicator rows)
for i in range(m): # for all rows ...
if M[i].count(1)==1: # if there's exactly one 1 in the row ...
indrows += [i] # accum. that row#
indcols += [M[i].index(1)] # accum. the index of the 1 in that row
count = 0
for i in range(m): # for all rows ...
if (i not in indrows): # if the row does not contain exactly one 1 ...
for j in range(n): # then check each element in that row
if M[i][j]==1 and j in indcols: # if element==1 in an "indicator" column
count += 1 # then that row counts as dropped
break # go on to the next row
return count
def EN(m, n, q): #q = P(bit=1)
"""return E(N) computed from the posted formula"""
p = 1 - q
return m*sum([(1-(1-k*q*p**(n-1))**(m-1))*math.comb(n,k)*q**k*p**(n-k) for k in range(2, n+1)])
def est_EN(nsamp, m, n, q):
"""return (mean, sd) such that mean +- 3*sd is an approx 99% CI for E(N), using nsamp simulated random samples
NB: this is generic code implementing a 'running' mean & variance algorithm"""
mean = ssd = 0
for i in range(1, nsamp+1):
z = trial(m, n, q)
dev = z - mean
mean += dev/i
ssd += dev*(z - mean)
sd = math.sqrt(ssd/(nsamp-1)/nsamp)
return mean, sd
Example:
m, n, q = 3, 2, .5
print(f"{(m, n, q)=}, {nsamp=}")
formula_EN = EN(m, n, q)
print(f" {formula_EN = :.6f}")
nsamp = 10**8
(mean, sd) = est_EN(nsamp, m, n, q)
print(f"simulated_EN = {mean:.6f} +- {3*sd:.6f}")
Output for this example:
(m, n, q)=(3, 2, 0.5), nsamp=100000000
formula_EN = 0.562500
simulated_EN = 0.562553 +- 0.000198
Wall time: 4min 52s