#include using namespace std; using ll = long long; using ull = unsigned long long; //using uLL = unsigned __int128; template using pqg = priority_queue, greater>; template using pq = priority_queue; using pll = pair; using pii = pair; using vvvvl = vector>>>; using vvvi = vector>>; using vvvl = vector>>; using vvvc = vector>>; using vvvd = vector>>; using vvi = vector>; using vvl = vector>; using vvc = vector>; using vvp = vector>>; using vvb = vector>; using vvd = vector>; using vp = vector>; using vi = vector; using vl = vector; using vu = vector; using vc = vector; using vb = vector; using vd = vector; #define vvvm vector>> #define vvm vector> #define vm vector template using umap = unordered_map; template using uset = unordered_set; #define rrr(l, r) mt()%(r-l+1)+l #define rep(i, s, f) for(long long i = s; i <= f; i++) #define rep1(i, s, f) for(long long i = s; i <= f; i++) #define per(i, s, f) for(long long i = s; i >= f; i--) #define per1(i, s, f) for(long long i = s; i >= f; i--) #define all0(x) (x).begin() ,(x).end() #define all(x) (x).begin() + 1, (x).end() #define ENDL '\n' //////////////////////////////////////////////////////////////////////////////////////////////////////////// //これが本当の組み込み関数ってね(笑) template int LB(vector &A, T x) { return lower_bound(A.begin(), A.end(), x) - A.begin(); } template int UB(vector &A, T x) { return upper_bound(A.begin(), A.end(), x) - A.begin(); } template void UNIQUE(vector &A, int indexed) { sort(A.begin() + indexed, A.end()); A.erase(unique(A.begin() + indexed, A.end()), A.end()); } int msb(long long a) { if(a == 0) return -1; return 64 - int(__builtin_clzll(a)); } template void chmin(T &a, T b) { a = min(a, b); } template void chmax(T &a, T b) { a = max(a, b); } ////////////////////////////////////////////////////////////////////// //数学系 /////////////////////////////////////////////////////////////////////// ll extgcd (ll a, ll b, ll &x, ll &y) { if(b == 0) { x = 1;y = 0;return a;} ll d = extgcd(b, a%b, y, x); y -= a/b * x; return d; } template T ceil__(T a, T b) { assert(b != 0); if(a % b == 0) return a / b; if((a <= 0 && b < 0) || (a >= 0 && b > 0)) return a/b + 1; else return a / b; } template T floor__(T a, T b) { assert(b != 0); if(a % b == 0) return a / b; if((a <= 0 && b < 0) || (a >= 0 && b > 0)) return a/b; else return a/b - 1; } ll modpow(ll x, ll y, ll mod) { if(x > mod) x %= mod; if(y == 0) return 1; ll res = modpow(x, y >> 1, mod); res = res * res % mod; if(y & 1) res *= x, res %= mod; return res; } ll sqrt__(ll a) { ll l = 0; ll r = 3037000499LL; while(l < r) { ll mid = (l + r + 1) / 2; if(mid * mid <= a) l = mid; else r = mid - 1; } return l; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //グローバル変数を置くところ //#pragma GCC optimize("O3") //#pragma GCC optimize("unroll-loops") //#pragma GCC optimize "trapv" //お祈り。変数同士の演算でのみ感知。代入や、 a *= 10000 では感知しない。 const ll big = 1001001001; const ll BIG = 1001001001001001001LL; const double pi = 3.141592653589793; vl dx{0, 1, 0, -1, 0, 1, 1, -1, -1}; // 座標平面において、(番兵) → ↓ ← ↑ ※ 右から時計回り 注 : グリッド or 座標で上下は反転する。 vl dy{0, 0, -1, 0, 1, 1, -1, -1, 1}; //const ll mod = 1000000007; //const ll mod = 998244353; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// template struct SegTree { int n; vector dat; SegTree(int siz) { n = 1; while(n < siz) n <<= 1; dat.resize(n * 2, T::ide()); } private: T query(int l, int r) { T L = T::ide(), R = T::ide(); l += n - 1, r += n - 1; while(l < r) { if(l & 1) L = op(L, dat[l++]); if(r & 1) R = op(dat[--r], R); l >>= 1, r >>= 1; } return op(L, R); } public: void set(int pos, T x) { pos += n-1; dat[pos] = x; } void init() { for(int i = n-1; i >= 1; i--) dat[i] = op(dat[i<<1], dat[(i<<1) + 1]); } void change(int pos, T x) { pos += n - 1; dat[pos] = update(dat[pos], x); while(pos >= 2) { pos >>= 1; dat[pos] = op(dat[pos<<1], dat[pos<<1|1]); } } T get(int l, int r) {// [l, r]の演算結果。 return query(l, r+1); } T operator[](int pos) { return dat[pos + n - 1]; } }; struct Monoid { int a; Monoid(){} Monoid(int _a) : a(_a) { } friend Monoid op(const Monoid& l, const Monoid& r) { return min(l.a, r.a); } friend Monoid update(const Monoid& l, const Monoid& r) { return r.a; } static Monoid ide() { return big; } }; void solve() { int N, M; cin >> N >> M; vi A(M+2); rep(i,1,M) cin >> A[i]; A[M+1] = big; /* SegTree seg1(N); SegTree seg2(N); vi dp(N+1, big); dp[1] = 0; seg1.change(ti, -ti);// dp[i][j] - j seg2.change(ti, ti);// dp[i][j] + j*/ vi dp(N+1, big); dp[1] = 0; per(i, M, 1) { vi pre(N+1, big); swap(pre, dp); rep(j, 1, N) { rep(pj, 1, N) { int now = pj; if(A[i] == now) now++; else if(A[i] == now-1) now--; chmin(dp[pj], pre[j] + abs(now - j)); } } } rep(ti, 2, N) { int res = big; rep(j, 1, N) { chmin(res, dp[j] + abs(ti - j)); } cout<< res << " "; } cout << ENDL; return; rep(ti, 2, N) { SegTree seg1(N); SegTree seg2(N); vi dp(N+1, big); dp[ti] = 0; seg1.change(ti, -ti);// dp[i][j] - j seg2.change(ti, ti);// dp[i][j] + j rep(i, 1, M+1) { vi pre(N+1, big); swap(pre, dp); rep(j, 1, N) { int tar = j; if(A[i] == j) tar = j+1; else if(A[i] == j-1) tar = j-1; {//pj = [1, tar] chmin(dp[j], seg1.get(1, tar).a + tar); } {//pj = [tar+1, N] chmin(dp[j], seg2.get(tar+1, N).a-tar); } } rep(j, 1, N) if(dp[j] != big) { seg1.set(j, dp[j] - j); seg2.set(j, dp[j] + j); } seg1.init(); seg2.init(); } cout << dp[1] << " "; } cout << ENDL; } /* -違法な読み替えをしていないか・違法な操作をしていないか ・実装が重そうな時は確認する 一度飛ばしても良い ・waが出た時は嘘を吐いていないかの他に、違法な操作をしていないかもチェックする 特にふわっとした操作は危ない ・区間をちょっと伸ばす←伸ばす余地はあるか ・選択肢のうち、どれでも良いから消す←本当にどれでも良いか 場合分けを挟んでないか -その情報はdpで纏めて良いものか ・dpで持つ:「この先の探索をするのに必要な情報」 なぜ必要なのか? ・途中までは辻褄が合っても、最後に分岐とかはある -何もわからないという時 ・解を列挙した上で特徴づけ ・制約に具体的な数値が登場している←適当に解をいじると何となく性質が見えることが多い ・何もわからない(自由度が高い) という時 -解の形を貪欲で絞る -上階が達成できガチ 必要条件をつみかさねていく ”非自明な最悪ケース(例:点の数が多いのに答え=0)”を作る時、何を意識したか? ・要素と制約をグラフで表してみる ・制約を「全ての要素についてoo ⇔ (1<=i<=N)で、iについてoo」といったように、独立なものに言い換えた方がやりやすいこともある ・よくわからないものを言い換える。 -慣れ親しんだ制約も、問題設定によってより扱いやすい形に言い換えられるかもしれない(例:グラフが連結) -一つの解法で駄目な時、周辺の解法も試すようにする。周辺で見えないなら全部試す。 ・「貪欲で最適解」と「解の形を絞って全探索」 ・「dp/最適化」と「解の形を絞って全探索」と 上界を見積もる」 ・「dp」と「必要な情報を言い換える・何でのその情報が必要なのか考える事による高速化」 ・「dp」と「探索の順番を変更する: でかい方から、深い方から、DAGで先端になっている・或いは葉になっている所から、全てを逆に言い換えて時系列的に後ろから」 ・「dp」と「軸にするものを変える: マスを軸にするのではなく、燃料を軸にするなど」 */ int main() { ios::sync_with_stdio(false); std::cin.tie(nullptr); cout << fixed << setprecision(15); ll T = 1; //cin >> T; rep(i, 1, T) { solve(); } return 0; }