F - Simple Operations on Sequence Editorial by kyopro_friends
気持ちとしては「swapの操作で位置合わせするのが本質なので、順列全探索 \(O(N! \ {\rm poly}(N))\) を bitDPで \(O(2^N{\rm poly}(N))\) に落とすいつものやつ」です。
より基本的な例題: ABC199E『Permutation』、 ABC180E『Traveling Salesman among Aerial Cities』
入れ替えの操作により \(A_i\) が \(A_{\sigma(i)}\) に移ったとします。\(\sigma\) を固定した時のトータルの操作コストを求めることは容易です。この \(\sigma\) を全探索することを考えます。
\(S\) を\(\{1,\ldots,N\}\) の部分集合として、\(DP[i][S]\) を 「\(\sigma(S)=\{1,\ldots,i\}\) であるような全ての \(\sigma\) についての、\(B_1,\ldots,B_i\) を一致させるまでに必要なコストの最小値」とします。
例
例えば \(A=(7,10,6),B=(6,12,9)\) であったとき、\(DP[2][\{2,3\}]\) は
- \(\sigma(2)=1,\sigma(3)=2\) のときの \((7,10,6) \xrightarrow{\rm swap} (10,6,7)\xrightarrow{\rm inc,dec}(6,12,7)\) と操作して \(2Y+10X\)
- \(\sigma(3)=1,\sigma(2)=2\) のときの \((7,10,6)\xrightarrow{\rm swap}(6,10,7)\xrightarrow{\rm inc,dec}(6,12,7)\) と操作して \(3Y+2X\)
の\(2\) つの小さい方の値を持つことになります。
遷移としては、次に \(\sigma(k)=i+1\) となる \(k\) を決めることになるので、各 \(k\not \in S\) に対して、\(dp[i+1][S\cup\{k\}]\) に遷移することになります。
ここで、この時点での \(i\) 番目より後ろのindexの並び順は、\(\{1,\ldots,N\}\setminus S\) が昇順になっているので \(\sigma(k)=i+1\) とするために必要なswapの回数は \(j\not\in S\) かつ \(j<k\) をみたすような \(j\) の個数に等しくなります。これは愚直にやって \(O(N)\) 、ないし、bit演算により(適当な仮定のもとで) \(O(1)\) でできます。
例
$N=10,i=2,\sigma(2)=1,\sigma(9)=2$ のとき、先頭 $i$ 項さえ一致させればいいので、swap操作後の $A$ の並びは$(A_2,A_9,A_1,A_3,A_4,A_5,A_6,A_7,A_8,A_{10})$ のようになっています。この状態から、$\sigma(7)=3$ と決めたとすると、$A_7$ を前から $3$ 番目に持ってくるには、$A_1,A_3,A_4,A_5,A_6$ の $5$ つと後ろから順にswapする必要があります。
\(i\neq |S|\) のケースを考慮する必要は無いことから、dpのキーに \(i\) は不要です。したがって以上により、状態数が \(O(2^N)\)、各状態からの遷移先が \(O(N)\)、遷移が \(O(1)\) なので、全体で \(O(N2^N)\) のDPにより解けました。
def popcount(n):return bin(n).count("1")
N,X,Y=map(int,input().split())
A=list(map(int,input().split()))
B=list(map(int,input().split()))
dp=[10**18]*(1<<N)
dp[0]=0
for s in range(1<<N):
i=popcount(s)
for k in range(N):
# A[k]をiの位置に持っていく
if (s>>k)&1:continue
cnt=popcount((~s)&((1<<k)-1)) # 1からnのうち、sに入っておらず、kより小さいものの個数
dp[s^(1<<k)]=min(dp[s^(1<<k)],dp[s]+abs(A[k]-B[i])*X+cnt*Y)
print(dp[(1<<N)-1])
posted:
last update: