XJ_Day_29_dp優化_K - Cross the Wall
先附上上午碼的,思路寫在裡面(但是我不會斜率優化,預處理部分看看就好了),我會在後面重新詳細說明
#include <bits/stdc++.h> #define LL long long using namespace std; const int MAX=5e4+3; struct milk { LL h,w; }a[MAX]; int st[MAX]; inline bool myru(milk x,milk y){if(x.h==y.h)return x.w>y.w;return x.h>y.h;} LL rin() { LL s=0; char c=getchar(); bool bj=0; for(;(c>'9'||c<'0')&&c!='-';c=getchar()); if(c=='-')c=getchar(),bj=true; for(;c>='0'&&c<='9';c=getchar())s=(s<<1)+(s<<3)+(c^'0'); if(bj)return -s; return s; } int main() { //將人分成至多k組,每組的費用為max(hi)*max(wi),求最小的費用和 //首先貪心: //1.對於任意1<=i,j<=n,如果滿足h[i]>=h[j]&&w[i]>=w[j],則j是不需要考慮的 //2.那麼剩下的人中,均滿足,if(h[i]>h[j])w[i]<w[j];(上面的刪的條件保證不會出現相等的情況) //3.所以需要考慮的人中,最高的一定寬度最小,最寬的一定高度最小 //4.按高度從大到小排序,滿足寬度遞增 //5.顯而易見,如果存在i<j<k(序號),k和j塞在同一組明顯優於k和i塞在同一組,所以就按照這個排序之後的順序,每組都是一段連續的區間 //6.那麼對於每個i,要麼自己新開一組,要麼接在前一組 //7.問題在於怎麼找到前一組的開頭(即知道這組的洞的高度來更新答案),如果退化成O(N^2)是肯定不行的 //奇怪的思路1: //列舉k個斷點……,時間O(N^K),而且這題也並不是必須要k個,而是<=k,例子很好舉,比方說,k=3;h1=100,w1=3;h2=99,w2=4;h3=98,w3=5;全放一組明顯最優 //8.基本上f[N][k]就是極限 //先寫一波假的複雜度的推導式吧 //f[i][j]表示當前到i,分了j塊,i為第j個塊的終點 的最小花費 //ans=max(f[N][j]),1<=j<=k; //f[i][j]=min(f[i][j],f[x][j-1]+w[i]*h[x+1]),x<i;時間複雜度O(k*(N^2)) //分成若干組w[i]*h[x+1]!!! //奇怪的思路2: //if(h[i]*w[N]+h[last]*w[i-1]<h[last]*w[N])last=i; //實現了一下,WA了,因為這個zz貪心會導致後面的分塊數不夠 // 10 4 // 1 100 // 2 99 // 3 98 // 4 97 // 5 96 // 6 95 // 7 94 // 8 93 // 9 92 // 10000 1 //對於這組資料,最後一個人肯定是單獨作為一塊,但是由於前面錯誤的貪心導致最後輸出:970592 //奇怪的思路3: //設st[i]表示第i個塊的開始位置 //初始,st[1]=1;st[i]=N+1,i>1; //若當前st[i]往前移能使答案更優,則移動 //初步估計時間複雜度為O(N*K) //h[st[i-1]]*w[st[i]-1]+h[st[i]]*w[st[i+1]-1] 這個是當前這兩段區間的和 //h[st[i-1]]*w[st[i]-2]+h[st[i]-1]*w[st[i+1]-1] 這個是st[i]向前移動一格後兩端區間的和 //if(h[st[i-1]]*w[st[i]-2]+h[st[i]-1]*w[st[i+1]-1]<h[st[i-1]]*w[st[i]-1]+h[st[i]]*w[st[i+1]]-1)st[i]--; //感覺是跑N次,每次看每個起始點能不能往前移 //寫出來之後仍然是被那個資料Hack了 //因為這樣附初值會導致st[2]跑到N就跑不上去了,後面的自然也上不去 //奇怪的思路4: //續接奇怪的思路3,考慮外面列舉一層區間數,賦初值就根據這個賦 //WA了 ,艹 //反例: // 10 4 // 1 100 // 2 99 // 3 98 // 4 97 // 5 96 // 9995 6 // 9996 5 // 9997 4 // 9998 3 // 10000 1 // Out put:1000000 //很好推翻的想法,至少我賦初值的方式不對,從後往前賦,加上正確但不一定完整的判定,WA掉很正常 //還是想想怎麼優化DP式吧,別整花裡胡哨的東西了 //9.h遞減,w遞增,這個應該是關鍵 int i,j; int n,k; for(;scanf("%d%d",&n,&k)>0;) { for(i=1;i<=n;i++)a[i].w=rin(),a[i].h=rin(); sort(a+1,a+n+1,myru); int nam=0; int max_w=0; for(i=1;i<=n;i=j+1) { for(j=i;j<n&&a[j+1].h==a[i].h;j++); if(a[i].w>max_w)max_w=a[i].w,a[++nam]=a[i]; } n=nam; // for(i=1;i<=nam;i++)printf("(%d,%d) ",a[i].h,a[i].w); //奇怪的思路2程式碼 // int last=1,s=1; // LL ans=0; // for(i=2;i<=n;i++) // { // if(s==k)break; // // printf("last:%d,ans:%lld -->",last,ans); // if(a[i].h*a[n].w+a[last].h*a[i-1].w<a[last].h*a[n].w)ans+=a[last].h*a[i-1].w,last=i,s++; // // printf("last:%d,ans:%lld\n",last,ans); // } // ans+=a[last].h*a[n].w; // printf("%lld\n",ans); //奇怪的思路3: // st[1]=1; // for(i=2;i<=k+1;i++)st[i]=n+1; // for(j=1;j<=n;j++) // for(i=2;i<=k;i++) // { // if(st[i-1]==st[i])break; // if(a[st[i-1]].h*a[st[i]-2].w+a[st[i]-1].h*a[st[i+1]-1].w<a[st[i-1]].h*a[st[i]-1].w+a[st[i]].h*a[st[i+1]-1].w)st[i]--; // } // for(i=1;i<=k;i++)printf("%d ",st[i]);printf("\n"); // LL ans=0; // for(i=1;i<=k;i++)ans+=a[st[i]].h*a[st[i+1]-1].w; // printf("%lld\n",ans); //奇怪的思路4: // st[1]=1; // LL ans=a[1].h*a[n].w; // for(int now=2;now<=k;now++) // { // for(i=1;i<now;i++)st[i+1]=n-now+i+1; // st[now+1]=n+1; // for(j=1;j<=n;j++) // for(i=2;i<=k;i++) // { // if(st[i]==st[i-1]+1)continue; // if(a[st[i-1]].h*a[st[i]-2].w+a[st[i]-1].h*a[st[i+1]-1].w<a[st[i-1]].h*a[st[i]-1].w+a[st[i]].h*a[st[i+1]-1].w)st[i]--; // } // // for(i=1;i<=k;i++)printf("%d ",st[i]);printf("\n"); // LL sum=0; // for(i=1;i<=now;i++)sum+=a[st[i]].h*a[st[i+1]-1].w; // ans=min(ans,sum); // } // printf("%lld\n",ans); } return 0; }
設第\(i\)個人的高度為\(h(i)\),寬度為\(w(i)\)
1.對於任意\(1\le i,j\le n,\)如果滿足\(h(i)\ge h(j)\&\&w(i)\ge w(j)\),則\(j\)是不需要考慮的人
這個應該是很好理解的,更高更寬的人都能過,就不需要考慮更矮更窄的人
2.那麼剩下的人中,均滿足,\(if(h(i)>h(j))w(i)<w(j);\)(上面的刪的條件保證不會出現相等的情況)
寬度(或高度)相等的若干個人,高度最高的會把其他的人都判成沒用的
3.所以需要考慮的人中,最高的一定寬度最小,最寬的一定高度最小
4.按高度從大到小排序,滿足寬度遞增
這裡就不用解釋了吧,很好懂的,就是由2推導過來的結論
5.顯而易見,如果存在\(i<j<k\)(序號),\(k\)和\(j\)塞在同一組明顯優於\(k\)和\(i\)塞在同一組,所以就按照這個排序之後的順序,每組都是一段連續的區間
這句話當時寫的比較模糊,這裡重新解釋一下:
在前面排序以後的情況下,是這樣的圖形
對於當前人\(i\),考慮讓他和前面的某個人\(j\)分成一組,打一個兩個人都能通過的洞
高度為\(h(j)\),寬度為\(w(i)\)
那麼這個操作之後\(i\)和\(j\)之間的所有點都可以直接通過這個洞(這個結論很顯然,不想解釋,前面的排序就是為了能夠得到這個結論)
所以可以知道,每一組的人員都是一段連續的區間上的人,設左端點為\(l\),右端點為\(r\),這組的花費就是\(h(l)\ast w(r)\)
6.那麼對於每個\(i\),要麼自己新開一組,要麼接在前一組
其實到這裡狀態轉移方程就很明顯了:
設\(f(i)(j)\)表示(當前到第\(i\)個人,已經分了\(j\)塊,\(i\)為第\(j\)個塊的終點)的最小花費
\(ans=max(f(n)(j)),1\le j\le k\)
\(f(i)(j)=min(f(i)(j),f(x)(j-1)+w(i)*h(x+1)),x<i\)
時間複雜度\(O(k\ast N^2)\)
這個方程需要優化,一看就知道是用斜率優化來做
然後是需要把高度從小到大排序,滿足\(h\)單調遞減(不過這個不是什麼大問題)
但是我還不會