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进制数好办,保证条件就有难度了。

题解给的算法中运用了树:

2.18题型总结

 

 发现,如果将根到某个叶子路径边权按顺序连就得到了一个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;
}

 

上一篇:【移动开发】Android无线调试 使用adbWireless软件


下一篇:传纸条