1. 程式人生 > 其它 >2.18題型總結

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;
}