題解 UVA1322 優化最大值電路 Minimizing Maximizer
阿新 • • 發佈:2021-07-17
演算法分析:線段樹優化 dp
先提醒一下坑點:
- 題中 Sorter 的順序不可改變。
一些約定:
- 記第 \(k\) 個 Sorter 的左右端點為 \(l_k\),\(r_k\),長度為 \(len_k\)。
首先分析 dp。我們關心的是能對目標序列全部排序的最少 Sorter 個數,但與第幾個無關,因此我們設計狀態 \(dp_i\) 表示將目標序列前 \(i\) 個數進行排序的最少 Sorter 個數(即右端點為 \(i\))。對於第 \(k\) 個 Sorter,有:
\[dp_{r_k}=\min(dp_j+1),j\in (l_k,r_k) \]初始值為 \(dp_1=0,dp_i=inf,(i\ne1)\)
此時,對於每一個 Sorter,都要進行 \(r_k-l_k+1\) 次檢查,時間複雜度為 \(\mathcal{O}(m\times \sum\limits_{k=1}^nlen_k)\)。顯然超時。
考慮優化轉移。
在暴力轉移時,我們是從一段連續區間內取一個最小值。需要區間修改,區間查詢最小值的操作。顯然,線段樹可以完成這個任務。於是我們用線段樹代替迴圈進行查詢和轉移。我們可以直接查詢 \(l_k\to r_k\) 這一段 \(dp\) 的最小值 \(x\),然後再把 \(x+1\) 作為新的 \(dp\) 值儲存到對應的區間上。最後答案就是 \(1\to n\)
具體見程式碼:
#include<cstdio> #include<ctype.h> #include<algorithm> #include<iostream> #include<cstring> #define reg register #define F(i,a,b) for(reg int i=(a);i<=(b);++i) using namespace std; inline int read(); const int N=5e5+5,inf=1e9; int n,m,dp[N],ans; struct P { int l,r; } a[N]; namespace tree { #define ls (x<<1) #define rs (x<<1|1) const int P=5e4+10; struct P { int l,r,val,lz; } f[P<<2]; void build(int x,int l,int r) { f[x]= {l,r,inf,inf}; if(l==r) { f[x].val=inf;//賦極大值 return; } int mid=l+r>>1; build(ls,l,mid); build(rs,mid+1,r); } inline void push_down(int x) { if(f[x].lz==inf)return; //區間修改,取最小值 f[ls].val=min(f[ls].val,f[x].lz); f[ls].lz=min(f[ls].lz,f[x].lz); f[rs].val=min(f[rs].val,f[x].lz); f[rs].lz=min(f[rs].lz,f[x].lz); f[x].lz=inf; } int query(int x,int l,int r) {//查詢最小值 if(l<=f[x].l and f[x].r<=r)return f[x].val; push_down(x); int mid=f[x].l+f[x].r>>1,ans=m; if(l<=mid)ans=min(ans,query(ls,l,r)); if(r>mid)ans=min(ans,query(rs,l,r)); return ans; } void modify(int x,int l,int r,int val) { if(l<=f[x].l and f[x].r<=r) { f[x].val=min(f[x].val,val); f[x].lz=min(f[x].lz,val); return; } push_down(x); int mid=f[x].l+f[x].r>>1; if(l<=mid)modify(ls,l,r,val); if(r>mid)modify(rs,l,r,val); f[x].val=min(f[ls].val,f[rs].val); } } int main() { int T=read(); while(T--) { n=read(),m=read(); tree::build(1,1,n);//建樹並賦極大值 tree::modify(1,1,1,0);//賦值 dp[1]=0 F(i,1,m) { a[i].l=read(),a[i].r=read(); int x=tree::query(1,a[i].l,a[i].r);//查詢最小值 tree::modify(1,a[i].l,a[i].r,x+1);//轉移 } ans=tree::query(1,n,n);//最終答案 printf("%d\n",ans); if(T)putchar('\n'); } return 0; } inline int read() { reg int x=0; reg char c=getchar(); while(!isdigit(c))c=getchar(); while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar(); return x; }
線段樹部分就是一個模板,還是十分容易寫的。
歡迎交流討論,請點個贊哦~