2.18題型總結
貪心練習題
最大積分
https://www.ybtoj.com.cn/contest/130/problem/6
這題想策略倒不難,價值高的後買,乘以的等級就更高收益更高
難在等級增加時的演算法模擬
(PS:題面其實說的很模糊。。T[i]到底表示總購買數還是再購買數也沒清楚,導致前半段一直出錯)
#define FOR(i,a,b) for(int i = (a);i <= (b);i++) // // FOR(i,1,n){ tmp = cnt - t[lev-1]; cnt = a[i].num+cnt; if(cnt < t[lev]){ ans+=a[i].num*a[i].c*lev; } else while(1){ if(cnt < t[lev]){ ans+=(cnt-t[lev-1])*a[i].c*lev;break; } else{ ans+=(t[lev]-t[lev-1]-tmp)*a[i].c*lev; lev++; tmp = 0; } } }
cnt一直表示總購買量,tmp在購買下種物品之前記錄,用來補充升級前的那部分
一次升級之後tmp就歸0了,等購買下一種再記錄。
砍樹問題
https://www.ybtoj.com.cn/contest/130/problem/7
這讓我想起之前的雷達問題!
同樣要找到一個排序方式並說明其依據,那麼根據經驗,應該按橫座標(x)排序
好處是,只需要依次對相鄰兩棵樹比較,不需要比較不相鄰的樹
對於P,左邊的樹到他距離D1,右邊D2,那麼這棵樹的最高允許高度就為h = min(d1,d2),至少要砍去的長度就為max(ai-h,0)。
出棧序列
https://www.ybtoj.com.cn/contest/130/problem/8
字典序對區域性最優策略提示很明顯了,每次都要儘量找到序列數值最大的數先輸出,直到序列全部入棧,再從棧頂依次輸出。
我們用O(n)做法,可以記一個數組來找數值最大的數:如果後面的數都比它小,那麼他一定要立即輸出。
因此建立字尾最大值陣列Max[n]。
int main(){ scanf("%d",&n); FOR(i,1,n) scanf("%d",&a[i]); Max[n] = a[n]; ROF(i,n-1,1) Max[i] = max(a[i],Max[i+1]); //FOR(i,1,n) printf("%d ",Max[i]); enter; //模擬 FOR(i,1,n){ int x = a[i]; z[++top] = x; //進棧 while(z[top] > Max[i+1]) { //如果已是從現在到結束為止最大的數,彈出 printf("%d ", z[top--]); } } while(top) printf("%d ",z[top--]); //退完 }
矩陣問題
荷馬史詩
https://www.ybtoj.com.cn/contest/130/problem/4
一道構建出模型才能化繁為簡的問題。
題的含義是建立n個k進位制數,使他們沒有字首包含另一個數。
建立k進位制數好辦,保證條件就有難度了。
題解給的演算法中運用了樹:
發現,如果將根到某個葉子路徑邊權按順序連就得到了一個Si,一個葉子對應一個Si,同時可以滿足前面的字首條件!
那麼,問題就轉化為建造一個k叉樹。
再想想最優方案:給邊加上邊權(Si出現的次數為w[i],對應葉子節點的深度為dep[i](也可以說單詞i轉化為的字元個數),那麼總長為
所有n個詞的dep[i]*w[i]
那麼,邊權較小的葉子節點應該深度較淺,以達到整體總長更小。
比如上圖,原來3位置沒有節點,那麼將2節點挪移過去,一定不會比挪移前更差。
但是要只比較邊權較小的葉子節點也不行,建不起多層樹,所以我們用a[i]表示i子樹中邊權和,按a[i]從小到大取。
在計算中,不能保證每個樹都有k個節點,計算的時候各種空位麻煩,所以我們搬出優化策略:
優化策略:補充若干個w=0的節點,使(n-1)%(k-1)=0
這樣可以每次取前k個,這個演算法就完美了
附程式碼
#include<bits/stdc++.h> #define FOR(i,a,b) for(int i = (a);i <= (b);i++) #define int long long #define il inline typedef long long ll; using namespace std; const int M = 110086; int n,k,cnt,ans,w[M]; struct tle{ int v; //該點權值 int h; //該點子樹高度 }; il bool operator < (const tle &a,const tle &b){ if(a.v!=b.v) return a.v > b.v; else return a.h > b.h; } priority_queue <tle>q; signed main(){ cin>>n>>k; FOR(i,1,n) { scanf("%lld",&w[i]); tle tmp; tmp.v = (ll)w[i]; tmp.h = 0; q.push(tmp); } //初始化:全是葉子,深度為0,權值w[i] cnt = n; if((n-1)%(k-1)!=0){ cnt+=k-1-(n-1)%(k-1); } FOR(i,1,cnt-n) { tle tmp; tmp.v = 0ll; tmp.h = 0; q.push(tmp); } //n-1不是k-1整數倍時,用權值為0的點補全 while(cnt != 1) { ll val = 0ll; int dep = 0; FOR(i,1,k){ //step1:取出前k小 tle tot = q.top(); val += (ll) tot.v; dep = max(dep,tot.h); q.pop(); } cnt = cnt - k + 1; //step2:cnt減值 ans += val;//step3:加上權值之和 tle tmp; tmp.v = val; tmp.h = dep+1; //+1是因為要變父親了,加輩 q.push(tmp); //step4:把父親壓進去,合併完成,一次迴圈結束 } cout << ans << endl << q.top().h << endl; return 0; }
二分練習題
二分的適用範圍:最優解具有單調性
合法|不合法:把最優化問題轉化為判定問題
數列分段
https://www.ybtoj.com.cn/contest/131/problem/1
經典的二分轉化--找到合適的L與R(此題是數列max到數列sum)--二分判斷mid是否滿足--滿足則大於mid部分丟掉,不滿足則小於mid部分丟掉
#include<bits/stdc++.h> #define FOR(i,a,b) for(int i = (a);i <= (b);i++) #define mid ((l+r)>>1) using namespace std; int n,m,a[100005],max_of_ai,sum_of_ai; int ck(int limit){ int cnt = 1,sum = 0; FOR(i,1,n) { if(sum + a[i] <= limit) sum+=a[i]; else cnt++,sum = a[i]; }//單調選擇,看作找不大於limit的最少連續段的問題 return cnt <= m; } int main(){ scanf("%d%d",&n,&m); FOR(i,1,n) scanf("%d",&a[i]),max_of_ai = max(max_of_ai,a[i]),sum_of_ai += a[i]; int l = max_of_ai,r = sum_of_ai; while(l < r){ //printf("%d %d\n",l,r); if(ck(mid)) r = mid; else l = mid+1; } cout<<l<<endl; }
攻擊法壇
https://www.ybtoj.com.cn/contest/131/problem/6
二分的題各不相同,但是能推出二分的模型,按照題本身特點來做
這個題特點是融合DP
#include<bits/stdc++.h> #define define define #define enter puts("\n") #define FOR(i,a,b) for(int i = (a);i <= (b);i++) #define mid (l+r)/2 using namespace std; const int MAXN = 2005; int n, R, G, a[MAXN], l = 1, r = 0,ans = 0,cnt; int dp[MAXN][MAXN],p[MAXN],q[MAXN]; bool ck (int L){ memset(dp,0,sizeof(dp)); memset(p,0,sizeof(p)); memset(q,0,sizeof(q)); FOR(i,1,n) FOR(j,i,n){ if(a[j]-a[i]+1<=L) p[i] = j;if(a[j]-a[i]+1<=2*L) q[i] = j; } p[n+1] = q[n+1] = n; FOR(i,0,R) FOR(j,0,G){ cnt++; if(i>0) dp[i][j]=max(dp[i][j],p[dp[i-1][j]+1]); if(j>0) dp[i][j]=max(dp[i][j],q[dp[i][j-1]+1]); }//列舉 return dp[R][G]==n; } int main(){ scanf("%d%d%d",&n,&R,&G); FOR(i,1,n) scanf("%d",&a[i]); sort(a+1,a+n+1); int l = 1,r = a[n] - a[1] + 1;a[0] = 0; if(R+G >= n) return printf("1\n"),0; while(l <= r){ if(ck(mid)) ans = mid,r = mid-1;else l = mid+1; } cout<<ans<<endl; }