題解/算法 {G - Cut and Reorder}
@LINK: https://atcoder.jp/contests/abc328/tasks/abc328_g
;
先對A進行一次操作(比如A=[a,b | c,d | e,f]
切2刀(代價是C*2
) 然後把他亂序變成[ef, cd, ab]
)
然後再累加abs(A[i]-B[i])
;
此時有個性質 比如你切了K刀 1 | 2 | ... | K+1
然後亂序, 變成A1 | ... | A(K+1)
, 此時一定有Ai + 1 != A(i+1)
;
.
比如 1(ab) | 2(c) | 3(de) | 4(f)
, 如果你把他變成1 | 2 | 4 | 3
那麼你合併要1 | 2
多切一刀呢, 他可以去掉;
因此, 假如你操作過後 A是3 4 1 2 0
那麼一定說明 原來切的是3 4 | 1 2 | 0
即一段連續遞增的子段裡面 一定沒有切;
因此, 第一個做法: 暴力枚舉N!
全排列; 當然這會超時;
然後第二做法: 對N!
全排列的優化 這是狀壓DP經過可以處理的, 此時優化為(1<<N)*N*N
; (注意代碼中是abs(A[ind] - B[len-1])
而不是B[ind]
);
可是int64 [1<<22][22]
他需要4e6*20*8 = 6e8 Byte
已經爆空間了! 512MB = 5e8
;
for( int st = 1; st < (1 << ___N); st ++){
int len = __builtin_popcount( st); // `st`所表示的序列長度;
//>< 比如`st`裡的`1`的下標有`{0,2,3,5}` 即`len=4`, 這個`st`表示`[0235,0253,0325,2...,3...,5...]`共`len!`個序列;
if( len == 1){
int ind = __builtin_ctz(st); // `st`中的`1`的下標
___DP[ st][ ind] = std::abs( A[ind]-B[len-1]);
continue;
}
//>< `__builtin_popcount( st) > 1`;
for( auto temp = st; temp > 0; temp-=(temp&-temp)){
int curInd = __builtin_ctz( temp); // `curInd`從小到大枚舉`st`裡的所有的`1` (比如`st=11010` 則`ind=[1,3,4]`);
auto & curDP = ___DP[ st][ curInd];
bool __isFirst = true; // 比如用`[a,b,c,d]`更新`curDP`, 那麼第一次是`curDP=a` 後來是`Min/Max(curDP,b/c/d)`, 這個變量就用於此作用;
auto preST = st ^ (1<<curInd);
for( auto ttemp = preST; ttemp > 0; ttemp-=(ttemp&-ttemp)){
int preInd = __builtin_ctz( ttemp); // `preInd`從小到大枚舉`preST`裡的所有的`1` (比如`preST=11000` 則`ind=[3,4]`);
const auto & preDP = ___DP[ preST][ preInd]; // 此时表示所有的`[..., preInd, curInd]`序列 其中`[..., preInd] = preDP`;
//>< 此時`preDP=[...,preInd]`已經確定了, 此時你要做的是 在他右側再添加一個`curInd` 即變成`[...,preInd,curInd]` 因此你要考慮這個操作的代價;
// . 比如`st`裡的`1`的下標有`{0,2,3,5}`(他的全排列有`4!`種方案), 比如`curInd=3, preInd=0` 那麼此時`curDP= [{025},3](共`3!`種方案)` `preDP= [{2,5},0,3](共`2!`種方案)`;
auto updateDP = preDP;
updateDP += std::abs( A[curInd] - B[len-1]);
if( preInd+1 != curInd){ updateDP += C;}
if( __isFirst){ curDP = updateDP; __isFirst=false;}
else{ curDP = std::min( curDP, updateDP);}
}
}
}
正確做法 好像是對(1<<22)(22)
優化為(1<<22)
, 目前還沒有研究明白;