P1020導彈攔截(最長上升子序列/最長不降子序列 - O(N2)與O(NlogN)作法)ios
題意:數組
有n個導彈分別飛行在不一樣高度,一顆攔截導彈能夠分別攔截幾個高度不上升的導彈,問一個攔截導彈能夠攔截最多多少個導彈,攔截全部導彈要多少顆攔截導彈ide
思路:優化
第一個問題:一個攔截導彈能夠攔截最多多少個導彈,這就是一個求最長不降子序列問題spa
先考慮O(N2)的作法,對於每個導彈,咱們向前遍歷以前的每個導彈,若是小於或者等於其高度,就能夠試着更新dp[i]=max(dp[i],dp[j]+1)code
dp[i]的每個元素初始化爲1blog
再來看O(nlogn)的作法,隊員前移作法遍歷以前每個元素這裏,咱們能夠選擇優化繼承
創建一個樹狀數組,用數值作下標,維護長度最大值,從後往前循環,每次查詢以前已經放到樹狀數組裏面的數中以這個數結尾的最長不上升子序列的長度的最大值,而後把這個最大值+1做爲以本身結尾的最長不上升子序列的長度,放到樹狀數組裏面隊列
第二個問題:問最少須要多少個導彈string
要用到一個Dilworth定理
Dilworth定理:偏序集的最少反鏈劃分數等於最長鏈的長度
簡單來講就是求一個最長上升子序列,試着來簡單證實一下
(1)假設打導彈的方法是這樣的:取任意一個導彈,從這個導彈開始將能打的導彈所有打完。而這些導彈所有記爲爲同一組,再在沒打下來的導彈中任選一個重複上述步驟,直到打完全部導彈
(2)假設咱們獲得了最小劃分的K組導彈,從第a(1<=a<=K)組導彈中任取一個導彈,一定能夠從a+1組中找到一個導彈的高度比這個導彈高(由於假如找不到,那麼它就是比a+1組中任意一個導更高,在打第a組時應該會把a+1組全部導彈一塊兒打下而不是另歸爲第a+1組),一樣從a+1組到a+2組也是如此。那麼就能夠從前日後在每一組導彈中找一個更高的連起來,連成一條上升子序列,其長度即爲K
(3)設最長上升子序列長度爲P,則有K<=P;又由於最長上升子序列中任意兩個不在同一組內(不然不知足單調不升),則有P>=K,因此K=P
#include<iostream> #include<algorithm> #include<cstring> #include<cstdio> #define lowbit(x) (x&(-x)) using namespace std; const int maxn=100100; int n=0,len; int a[maxn],mx[maxn]; void add(int x,int val) { while(x<=maxn){ mx[x]=max(mx[x],val); x+=lowbit(x); } } int query(int x) { int ans=0; while(x>=1){ ans=max(mx[x],ans); x-=lowbit(x); } return ans; } int main() { while(scanf("%d",&a[++n])!=EOF); n--; int ans=0; for(int i=n;i>=1;i--){ int x=query(a[i])+1; add(a[i],x); ans=max(ans,x); } cout<<ans<<endl; memset(mx,0,sizeof(mx)); ans=0; for(int i=1;i<=n;i++){ int x=query(a[i]-1)+1; add(a[i],x); ans=max(ans,x); } cout<<ans<<endl; return 0; }
P1091 合唱隊列(最長上升子序列)
思路:
分別從左邊跟右邊求一次最上上升子序列d[i]與f[i],而後枚舉最高點i,求max(d[i] + f[i] - 1)(由於i被重複計算)
#include<iostream> #include<algorithm> #include<cstring> #include<cstdio> using namespace std; const int maxn=105; int a[maxn],d[maxn],f[maxn]; int main() { int n; scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); d[i]=f[i]=1; } for(int i=1;i<=n;i++) for(int j=1;j<i;j++) if(a[i]>a[j]) d[i]=max(d[i],d[j]+1); for(int i=n;i>=1;i--) for(int j=n;j>i;j--) if(a[i]>a[j]) f[i]=max(f[i],f[j]+1); int ans=0; for(int i=1;i<=n;i++) ans=max(ans,d[i]+f[i]-1); cout<<n-ans<<endl; return 0; }
P1280 尼克的任務(逆推)
題意:
尼克有k個任務,每一個任務有起始時間跟完成所須要的時間,同一個時刻有多個任務尼克可任選一個一個完成,若是隻有一個任務則必須完成,問尼克的最大閒暇時間是多少
思路:
能夠發現第i時刻的最大空閒時間是和後面i+選擇任務的持續時間的時刻有關係的,那麼,正着找確定是不行的,咱們來試一下倒着搜,即設f[i]表示i~n的最大空閒時間,經嘗試,發現是徹底可行的,能夠列出動態轉移方程以下
①(本時刻無任務)f[i]=f[i+1]+1;//繼承上一個時刻的最大空閒時間後+1
②(本時刻有任務)f[i]=max(f[i],f[i+a[sum])//a[sum]表示在這個時刻的任務的持續時間,找出選擇哪個本時刻任務使空閒時間最大化
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> using namespace std; const int maxn=1e4+10; int dp[maxn],b[maxn]; struct tim{ int s,h; }a[maxn]; int cmp(tim a,tim b){return a.s>b.s;} int main() { int n,k,cnt=1; scanf("%d%d",&n,&k); memset(b,0,sizeof(b)); for(int i=1;i<=k;i++){ scanf("%d%d",&a[i].s,&a[i].h); b[a[i].s]++; } sort(a+1,a+1+k,cmp); dp[n+1]=0; for(int i=n;i>=1;i--){ if(!b[i]) dp[i]=max(dp[i],dp[i+1]+1); for(int j=1;j<=b[i];j++){ dp[i]=max(dp[i],dp[i+a[cnt].h]); cnt++; } } cout<<dp[1]<<endl; return 0; }
P1880[NOI1995]石子合併(區間DP)
題意:
給你N堆石子爲一圈,每堆石子都有其相應的重量,每次能夠合併兩堆石子,得分爲兩堆石子的重量和,問得分的最大值與最小值分別是多少
思路:
先考慮全部石子線性排列
咱們定義dp[i][j]爲從i合併到j的最小值,cost[i][j]爲這一堆石子的重量和
能夠推出動態規劃方程爲f[i][j]=f[i][k]+f[k+1][j]+cost[i,j]
以後咱們看看須要哪些循環來推出最後的答案
首先得枚舉一次合併多少個石子-階段:len: 2 ~ n
其次得枚舉每次合併的區間-狀態:i: 1 ~ n – len + 1, j: i + len – 1
再來看看合併哪兩堆是比較好的-策略:k: i ~ j
因此時間複雜度爲n^4
優化:預處理sum[i]表示前i堆石子總數量,則cost[i,j] = sum[j] – sum[i - 1],時間優化爲n^3
最後得試着解出爲一圈的狀況-只須要將後面的石子再放一次也就是將數組拓展爲2*n便可
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #define inf 0x3f3f3f3f using namespace std; const int maxn=205; int dp1[maxn][maxn],dp2[maxn][maxn],sum[maxn][maxn],a[maxn]; int main() { int n; scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); a[i+n]=a[i]; } for(int i=1;i<=n*2;i++) for(int j=i;j<=2*n;j++) for(int k=i;k<=j;k++) sum[i][j]+=a[k]; for(int i=1;i<=n;i++) dp1[i][i]=dp2[i][i]=0; for(int len=1;len<n;len++){ for(int l=1,r=l+len;r<2*n&&l<2*n;l++,r=l+len){ dp1[l][r]=inf; for(int k=l;k<r;k++){ dp1[l][r]=min(dp1[l][r],dp1[l][k]+dp1[k+1][r]+sum[l][r]); dp2[l][r]=max(dp2[l][r],dp2[l][k]+dp2[k+1][r]+sum[l][r]); } } } int ans1=inf,ans2=0; for(int i=1;i<=n;i++){ ans1=min(ans1,dp1[i][i+n-1]); ans2=max(ans2,dp2[i][i+n-1]); } cout<<ans1<<endl<<ans2; return 0; }