SRM605D1Med AlienAndSetDiv1
面倒なだけ(といってもそれほど面倒ではない)
問題概要(原文)
$\{ 1,2,\ldots,2N \}$ を $A,B$ の $2$ つの集合に分ける方法は何通りあるか?.ただし以下の制約を満たさなくてはならない.
- $|A|=|B|=N$
- $|A_i-B_i| \geq K(1 \leq K \leq 10)$
考察
$K$ が小さいので,直前 $K-1$ 個の選び方を全て覚えておける.
$dp(i,j,mask)=$ 今 $i$ まで $A,B$ に振り分けて, $|A|=j$ で直前 $K-1$ 個の選び方が $mask$ で表現できるときの選び方の通り数,というDPを丁寧にやる.
計算量は $O(N^{2} 2^{K})$
ソースコード
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Debug = System.Diagnostics.Debug;
using StringBuilder = System.Text.StringBuilder;
public class AlienAndSetDiv1
{
public int getNumber(int N, int K)
{
//dp(i,mask)=|A|==i && mask=直前 k個の買い方0:A,1:B
K--;
int ALL = (1 << K) - 1;
var dp = new ModInteger[N + 2, 1 << K];
dp[K, 0] = dp[0, ALL] = 1;
for (int k = K; k < 2 * N; k++)
{
var next = new ModInteger[N + 2, 1 << K];
for (int i = 0; i <= N; i++)
{
var j = k - i;
for (int mask = 0; mask < 1 << K; mask++)
{
if (dp[i, mask].num == 0) continue;
{
var cnt = PopCount(mask);
var minj = -1;
var maxj = 100000;
if (cnt > 0) { minj = j - cnt + 1; maxj = j; }
else maxj = -1;
if (i + 1 < minj || maxj < i + 1)
next[i + 1, (mask * 2) & ALL] += dp[i, mask];
}
{
var cnt = K - PopCount(mask);
var mini = -1;
var maxi = 100000;
if (cnt > 0) { mini = i - cnt + 1; maxi = i; }
else maxi = -1;
if (j + 1 < mini || maxi < j + 1)
next[i, (mask * 2 + 1) & ALL] += dp[i, mask];
}
}
}
dp = next;
}
ModInteger ans = 0;
for (int i = 0; i < 1 << K; i++)
ans += dp[N, i];
return (int)(ans.num);
}
static public int PopCount(int i)
{
i = i - ((i >> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
return ((i + (i >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;
}
}
#region ModNumber
public partial struct ModInteger
{
public const long Mod = (long)1e9 + 7;
public long num;
public ModInteger(long n) : this() { num = n % Mod; if (num < 0) num += Mod; }
public override string ToString() { return num.ToString(); }
public static ModInteger operator +(ModInteger l, ModInteger r) { var n = l.num + r.num; if (n >= Mod) n -= Mod; return new ModInteger() { num = n }; }
public static ModInteger operator -(ModInteger l, ModInteger r) { var n = l.num + Mod - r.num; if (n >= Mod) n -= Mod; return new ModInteger() { num = n }; }
public static ModInteger operator *(ModInteger l, ModInteger r) { return new ModInteger(l.num * r.num); }
public static ModInteger operator ^(ModInteger l, long r) { return ModInteger.Pow(l, r); }
public static implicit operator ModInteger(long n) { return new ModInteger(n); }
public static ModInteger Pow(ModInteger v, long k)
{
ModInteger ret = 1;
var n = k;
for (; n > 0; n >>= 1, v *= v)
{
if ((n & 1) == 1)
ret = ret * v;
}
return ret;
}
}
#endregion
コメント
- こういうのは境界条件をきっちり書いてからやらないとバグる