この重複を防ぐために、「コイン配列 c のうち、index が ci のコインまで使って合計 s を作るときの方法数」を状態として管理します。すなわち、d[s][ci] は「c の 0 番目から ci 番目までのコインを使って合計 s を作る方法の数」を表すようにします。ci 番目のコインを使わないこともあれば、複数枚使うことも可能で、すべてこの状態に含まれます。
d[s][ci] を更新する際に考慮する遷移は、大きく以下の 2 つです(現在のコインの添字は ci, その値を val とします):
コイン ci を全く使わない → d[s][ci - 1]
コイン ci を 1 枚以上使う → d[s - val][ci]
つまり、最終的に ci 番目までのコインで合計 s を作る方法の数は、上記 2 つの遷移先からの和になります。
c = [...]
x = ...
# d を (x+1) 行 × len(c) 列の 0 で初期化
d = [[0] * len(c) for _ in range(x + 1)]
すべてのコインを処理し終わったら、合計 x を作る方法の数は d[x][len(c) - 1] に格納されているので、それを出力すれば正解です。
for ci, val in enumerate(c): # コインを1つずつ処理
d[0][ci] = 1 # 合計0はコインを使わなくても作れる
for s in range(1, x + 1): # 1 から x までの合計値を順に計算
if ci - 1 >= 0: # (1) ci をまったく使わない
d[s][ci] += d[s][ci - 1]
if s - val >= 0: # (2) ci を1枚以上使う
d[s][ci] += d[s - val][ci]
print(d[x][len(c) - 1])
# d を x+1 個の要素で初期化
d = [1] + [0] * x # 合計0はコインを使わなくても作れる
for ci, val in enumerate(c): # コインを1つずつ処理
for s in range(1, x + 1): # 1 から x までの合計値を順に計算
if s - val >= 0: # (2) ci を1枚以上使う
d[s] += d[s - val]
d[s] %= MOD
print(d[x])