これ久々に使った。
https://yukicoder.me/problems/no/3306
問題
N日間にわたり、M種類の魔法石を売買する。
i日目におけるj種類目の魔法石の価格はA[i][j]とする。なお、iに対しA[i][j]は単調増加である。
毎日最大1個、魔法石を売り買いできる。
初期状態で無限にお金があり、手持ちの魔法石が0個の場合、最終的にどれだけお金を増やせるか。
解法
A[i][j]は単調増加なので、n=floor(N/2)とすると、前半のn日で石を1個ずつ買い、後半n日で1個ずつ売るのが良い。
その際、石の数をまじめに考える必要はなく、a日目に買ってb日目に売るなら、a,bに対し(A[b][j]-A[a][j])が最大となるjを選択するとよい。
あとは、n点ずつの二部グラフにおける、最大重みマッチングの問題となる。
これはハンガリー法でO(n^3)で解ける。
なお、M=1の場合Nが大きくてハンガリー法でTLEするが、M=1の時はそもそも石が1種類なので売り買いの仕方は自明であり、ハンガリー法を使わなくてよい。
int H,W; ll A[3030][3030]; template<class V> pair<ll, vector<int>> Hungarian(vector<vector<V>>& A) { const int MV=1510; int match[MV+1], vis[MV+1]; int N=A.size(); assert(A[0].size()==N && MV>=2*N); int y,x,i,j; vector<V> R(N),C(N,0); FOR(y,N) R[y]=*max_element(ALL(A[y])); MINUS(match); while(1) { retry: FOR(y,N) if(match[y]==-1) break; if(y==N) break; int tar=y; MINUS(vis); queue<int> Q; vector<int> S,T; Q.push(tar); FOR(y,N) T.push_back(y); ret2: while(Q.size()) { y=Q.front(); Q.pop(); if(vis[y]==-1) S.push_back(y); vis[y]=0; vector<int> T2; FORR(x,T) { if(A[y][x]==R[y]+C[x]) { vis[N+x]=y; if(match[N+x]==-1) { //交代路を張る int cur=N+x; while(1) { int nex=match[y]; match[cur]=y; match[y]=cur; if(y==tar) goto retry; cur=nex; y=vis[cur]; } } Q.push(match[N+x]); } else T2.push_back(x); } swap(T,T2); } //交代路が無い V dif=numeric_limits<V>::max(); int ty=-1,tx=-1; FORR(y,S) FORR(x,T) if(R[y]+C[x]-A[y][x]<dif) { dif=R[y]+C[x]-A[y][x]; ty=y,tx=x; } FOR(i,N) if(vis[i]>=0) R[i]-=dif; FOR(i,N) if(vis[i+N]>=0) C[i]+=dif; Q.push(ty); goto ret2; } vector<int> P; ll ret=0; FOR(y,N) P.push_back(match[y]-N), ret+=A[y][match[y]-N]; return {ret,P}; } void solve() { int i,j,k,l,r,x,y; string s; cin>>H>>W; FOR(y,H) FOR(x,W) cin>>A[y][x]; if(W==1) { ll ret=0; FOR(i,H/2) ret+=A[H-1-i][0]-A[i][0]; cout<<ret<<endl; return; } vector<vector<ll>> B; B.resize(H/2); FORR(b,B) b.resize(H/2); FOR(i,H/2) FOR(j,H/2) { FOR(x,W) B[i][j]=max(B[i][j],A[H-1-j][x]-A[i][x]); } cout<<Hungarian(B).first<<endl; }
まとめ
ハンガリー法はだいぶ昔に書いたライブラリを使った。