浅谈贪心策略——相邻交换

浅谈贪心策略——相邻交换

题解主要写贪心的考虑方法:相邻交换法。

我们在平时的贪心题几乎都可以正确的贪心方法。

主要思想

设交换前对答案的贡献为x,交换后对答案的贡献为y

l  若x>y则不交换

l  若x<y则需交换

l  若x==y则不交换(交换反而增加时间复杂度)

作为题目,需要建立数学模型设置未知数表示x和y得到不等式从而得出排序的关键字、

例题:皇后游戏(Luogu OJ P2123)

网址: https://www.luogu.org/problemnew/show/P2123

题目大意:给出序列a和b,可以同时改变ai和bi 求出一种最优的序列最小化的[n]

解法

设排序后,某位置上编号为i,后面一位的编号为j,第i个之前所有a之和为x,i前面位置的c(c_(i-1))值为y,那么

T1=max(max(y,x+ai)+bi,x+ai+aj)+bj

T2= max(max(y,x+aj),x+ai+aj)+bi

(T1<T2 不交换)

不妨把T1化简:

max(max(y,x+ai)+bi,x+ai+aj)+bj 可以化为

max(max(y+bi,x+ai+bi),x+ai+aj)+bj

max(max(y+bi+bj,x+ai+bi+bj),x+ai+aj+bj)

max(y+bi+bj,x+ai+bi+bj,x+ai+aj+bj)

同理 T2化简结果是:

max(y+bi+bj,x+aj+bi+bj,x+ai+aj+bi)

列出来就是:

max(y+bi?+bj?,x+ai?+bi?+bj?,x+ai?+aj?+bj?)<max(y+bi?+bj?,x+aj?+bi?+bj?,x+ai?+aj?+bi?)

消去y+bi+bj可得:

max(x+ai?+bi?+bj?,x+ai?+aj?+bj?)<max(x+aj?+bi?+bj?,x+ai?+aj?+bi?)

消去x可得:

max(ai?+bi?+bj?,ai?+aj?+bj?)< (aj?+bi?+bj?,ai?+aj?+bi?)

打出去:

max(bi?,aj?)+ai?+bj?<max(bj?,ai?)+aj?+b

移项

max(bi?,aj?)−aj?−bi?<max(bj?,ai?)−ai?−bj

左边:aj和bi中大数消掉留下aj和bi小数的相反数

右边:aj和ai中大数被消掉,留下ai和bj中小数的相反数

得:

l  −min(aj?,bi?)<−min(ai?,bj?)

再得:

min(ai?,bj?)<min(aj?,bi?)

# include <bits/stdc++.h>
# define LL long long
using namespace std;
const int MAXN=20005;
struct rec{
    LL a,b;
}a[MAXN];
bool cmp(rec a, rec b)
{
    return min(a.a,b.b)<min(a.b,b.a);
}
int main()
{
    int T; scanf("%d",&T);
    while (T--) {
        int n;
        scanf("%d",&n);
        for (int i=1;i<=n;i++)
         scanf("%d%d",&a[i].a,&a[i].b);
        sort(a+1,a+1+n,cmp);
        LL lt=a[1].a+a[1].b,s=a[1].a;
        for (int i=2;i<=n;i++) {
            s+=a[i].a;
            lt=a[i].b+max(lt,s);
        }
        printf("%lld\n",lt);
    }
    return 0;
 } 

有个问题:(by liuzibujian)

小于号没有传递性,如排出的序列可能是这样的:

7 3

1 1

1 6

Ans=17

这样也是最优的:

1 1

1 6

7 3

Ans=12

显然下面正确,最终我们找到这样的解释:

按条件判断相等的两组数交换一次对后面确实不会产生影响,但可以通过多次交换对最终结果产生影响。

这个判断的条件没有传递性。

怎么让他有传递性呢?

显然分块来做

ai<bi

ai=bi

ai>bi

# include <bits/stdc++.h>
# define LL long long
using namespace std;
const int MAXN=20005;
struct rec{
    LL a,b;
}a[MAXN];
int part(rec a)
{
    if (a.a<a.b) return 0;
    else if (a.a==a.b) return 1; else if (a.a>a.b) return 2;
}
bool cmp11(rec a,rec b)
{
    if (part(a)!=part(b)) return part(a)<part(b);
    if (part(a)==0)   return a.a<b.a;   if (part(b)==2)   return a.b>b.b;
    return false;
}
int main()
{
    int T; scanf("%d",&T);
    while (T--) {
        int n;
        scanf("%d",&n);
        for (int i=1;i<=n;i++)
         scanf("%d%d",&a[i].a,&a[i].b);
        sort(a+1,a+1+n,cmp11);
        LL lt=a[1].a+a[1].b,s=a[1].a;
        for (int i=2;i<=n;i++) {
            s+=a[i].a;
            lt=a[i].b+max(lt,s);
        }
        printf("%lld\n",lt);
    }
    return 0;
 } 

例题2:国王游戏

网址: https://www.luogu.org/problemnew/show/P1080

题目大意:

国王自己也在左、右手上各写一个整数。然后,让这 n 位大臣排成一排,国王站在队伍的最前面。排好队后,所有的大臣都会获得国王奖赏的若干金币,每位大臣获得的金币数分别是:排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。

解法

方法是一样的,选取序列中间的连续的两个人相邻交换法求解。

有中间两个人 i 和 i+1,显然他们俩怎么排对后面没有影响(因为只跟乘积有关)

设p为国王到i-1个人左手数的乘积,

l  若不换 max{p/b[i], p*a[i]/b[i+1]} < max{p/b[i+1],p*a[i+1]/b[i]}

l  两边除以p乘以(b[i]*b[i+1])

l  可知:max{b[i+1],a[i]*b[i]} < max{b[i],a[i+1]*b[i+1]}

l  左边a[i]*b[i]恒>右边b[i]

l  右边a[i+1]*b[i+1]恒>左边b[i+1]

原式可化为: a[i]*b[i]<a[i+1]*b[i+1]

所以排序方式为两数左手数乘以右手数递增即可

注意高精度。

# include <bits/stdc++.h>
# define Rint register int
# define LL long long
using namespace std;
const int MAXN=1e4 + 10,L=3e4 + 10;
char s[L];
struct rec{ LL l,r;}a[MAXN];
int n;
bool cmp(rec a,rec b){ return a.l*a.r<b.l*b.r;}
inline LL read()
{
    LL X=0,w=0; char c=0;
    while(c<‘0‘||c>‘9‘) {w|=c==‘-‘;c=getchar();}
    while(c>=‘0‘&&c<=‘9‘) X=(X<<3)+(X<<1)+(c^48),c=getchar();
    return w?-X:X;
}
struct lint{
    int len,num[L];
    inline lint operator + (const lint &t) const{
        lint ans;
        memset(ans.num,0,sizeof(ans.num));
        if (len>t.len) ans.len=len;
        else ans.len=t.len;
        for (Rint i=1;i<=ans.len;i++) {
            ans.num[i]+=num[i]+t.num[i];
            ans.num[i+1]+=ans.num[i]/10;
            ans.num[i]%=10;
        }
        if (ans.num[ans.len+1]>0) ans.len++;
        return ans;
    }
    inline lint operator * (const lint &t) const{
        lint ans;
        memset(ans.num,0,sizeof(ans.num));
        for (Rint i=1;i<=len;i++)
         for (Rint j=1;j<=t.len;j++)
          ans.num[i+j-1]+=num[i]*t.num[j];
        for (Rint i=1;i<=len+t.len;i++) {
            ans.num[i+1]+=ans.num[i]/10;
            ans.num[i]%=10;
        }
        if (ans.num[len+t.len]>0)
         ans.len=len+t.len;
        else ans.len=len+t.len-1;
        return ans;
    }
    inline lint operator / (const int &x) {
        lint a;
        a.len=len;
        int rest=0;
        for (int i=len;i>=1;i--){
            rest=rest*10+num[i];
            a.num[i]=rest/x;
            rest%=x;
        }
        while (!a.num[a.len]&&a.len>1) a.len--;
        return a;
    }
    inline bool operator > (const lint &t) const{
        if (len>t.len) return 1;
        else if (len<t.len) return 0;
        for (Rint i=len;i>=1;i--)
            if (num[i]<t.num[i]) return 0;
            else if (num[i]>t.num[i]) return 1;
        return 0;
    }
    inline void swap(lint &a,lint &b) { lint t=a; a=b; b=t;}
    inline void print(lint x)
    {
        for (Rint i=x.len;i>=1;i--) printf("%d",x.num[i]);
        putchar(‘\n‘);
    }
    inline lint change(LL x) {
        lint a; memset(a.num,0,sizeof(a.num));
        if (x==0) { a.num[1]=0; a.len=1; return a;}
        a.len=0;
        while (x>0) a.num[++a.len]=x%10,x/=10;
        return a;
    }
}R;
int main()
{
    cin>>n;
    n++;
    for (int i=1;i<=n;i++)
     a[i].l=read(),a[i].r=read();
    sort(a+2,a+1+n,cmp);
    lint ans; ans.len=1; ans.num[1]=0;
    lint s; s.len=1; s.num[1]=1;
    for (Rint i=1;i<=n-1;i++) {
        s=s*R.change(a[i].l);
        lint rr=(s/a[i+1].r);
        if (rr>ans) ans=rr;
    }
    R.print(ans);
    return 0;
}

From:     HGOI

Name:ljc20020730

Date: 20181003

原文地址:https://www.cnblogs.com/ljc20020730/p/9745032.html

时间: 2024-10-07 06:21:16

浅谈贪心策略——相邻交换的相关文章

浅谈贪心与动归

浅谈贪心与动归 初学时 想必都会对两者的认识有一些混淆 概念性质的就不赘述了 来谈谈我在刷题过程中对两者的见解(诚心接受各位的指正) 从搜索到贪心--求解算法的优化 这篇文章非常值得一看 P1478 陶陶摘苹果(升级版) 对应的oj题目 对比 贪心像是动归的一个特例 动归的核心在于:状态转移,找出那个转移方程 贪心的核心在于:局部选取最优解,并且选取的贪心策略不会影响到其他的状态 用01背包举个例子 在n件物品取出若干件放在空间为c的背包里,每件物品的体积为w1 w2...wn,与之相对应的价值

浅谈贪心

序言 贪心算法是 \(\text{OI}\) 界中一门高深又基础的算法,一般用于求解最优性问题. 贪心变化多端,时易时难,例如:易的 删数问题 ,难的 树上的数 . 博主因为学业繁忙,只能抽出时间写,经常会咕咕咕,写的不好的请见谅. 参考文献:李煜东<算法竞赛进阶指南>. \(~\) 贪心是个啥? 贪心贪心,当然是贪心啦. 贪心是一种在每一次做决策的时候,总是采取当前意义下最优策略的算法. 换句话说,每次做出选择,我们总是会选择当前看来最优秀的选择. 这是一种 局部最优性 推导至 整体最优性

浅谈组策略设置IE受信任站点

在企业中,通常会有一些业务系统,要求必须加入到客户端IE受信任站点,才能完全正常运行访问,在没有域的情况下,可能要通过管理员手动设置,或者通过其它网络推送方法来设置. 有了域之后,这项工作就可以很好的通过组策略来统一完成,管理员可以在AD里面专门定义一条用于IE设置的组策略,来集中管理客户端的IE设置,那么这条组策略应该如何设置,其实有很多种办法,今天笔者提取其中三条比较常见的场景来和各位看官进行讨论. 首先,最常见的肯定就是这条了,在计算机配置 - 管理模板 - Windows组件 - Int

浅谈mysql innodb缓存策略

浅谈mysql innodb缓存策略: The InnoDB Buffer Pool Innodb 持有一个存储区域叫做buffer pool是为了在内存中缓存数据和索引,知道innodb bufferpool怎么工作,和利用它读取频繁访问的数据,是mysql优化重要的方面. 理想状况下,把bufferpool的大小调整到足够大,留下足够的内存空间给其他该服务器上的进程(使其无缺页即可).bufferpool越大,innodb 月表现为内存型数据库,从硬盘上一次读取数据,之后并成了从内存中读取数

浅谈加速因子在策略中的意义

他站链接:浅谈加速因子在策略中的意义 NO:01没有完美的交易系统,但是却有完美的交易哲学.交易哲学.交易策略和资金管理三者缺一不可,才能构成正期望的交易系统.投机依赖价格的移动获得盈利(低买高卖或高买更高卖).在上升或下降趋势中,价格虽然在整体上朝着一个方向移动,但中间也会有短暂的反方向移动.而在横盘过程中,价格的移动方向则显得相对"随机"一些. NO:02关于价格的移动,可以类比物理学中的运动.其中包括:位移距离.时间.速度等.价格的位移相对于时间的比率就是价格的速度.除了速度之外

浅谈MySQL索引背后的数据结构及算法

摘要 本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题.特别需要说明的是,MySQL支持诸多存储引擎,而各种存储引擎对索引的支持 也各不相同,因此MySQL数据库支持多种索引类型,如BTree索引,哈希索引,全文索引等等.为了避免混乱,本文将只关注于BTree索引,因为这是 平常使用MySQL时主要打交道的索引,至于哈希索引和全文索引本文暂不讨论. 文章主要内容分为四个部分. 第一部分主要从数据结构及算法理论层面讨论MySQL数据库索引的数理基础. 第二部分结合MySQL数据库中

【转】浅谈Nginx负载均衡与F5的区别

前言 笔者最近在负责某集团网站时,同时用到了Nginx与F5,如图所示,负载均衡器F5作为处理外界请求的第一道"墙",将请求分发到web服务器后,web服务器上的Nginx再进行处理,静态内容直接访问本地门户,动态数据则通过反向代理指向内网服务. 其实Nginx和F5这两者均可用作网站负载均衡,那二者有什么区别呢?笔者在此浅谈下Nginx与F5的一些区别. 目前很多网站或应用在设计之初都会为高并发的数据请求做负载均衡,不差钱的土豪用户一般会直接买F5硬件设备作为其负载均衡器,原因不用多

浅谈Android多屏幕的事

浅谈Android多屏幕的事 一部手机可以同时看片.聊天,还可以腾出一支手来撸!这么吊的功能(非N版本,非第三方也能实现,你不知道吧)摆在你面前,你不享用?不关注它是怎样实现的?你来,我就满足你的欲望! 一部手机可以同时看片.聊天,还可以腾出一支手来撸==!就像这样: 是时候告别来回切换应用屏幕的酸爽了,还可以在分屏模式下两Activity间直接拖放数据! 好高大上的样子!这是怎么实现的?别急,我们一一道来: kitkat(4.4)版本对多任务分屏的实现 由于相关的代码和功能被封装及隐藏起来,所

递归算法浅谈

递归算法 程序调用自身的编程技巧称为递归( recursion). 一个过程或函数在其定义或说明中又直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题类似的规模较小的问题来求解,递归策略仅仅需少量的程序就可描写叙述出解题过程所须要的多次反复计算,大大地降低了程序的代码量. 注意: (1) 递归就是在过程或函数里调用自身; (2) 在使用递增归策略时,必须有一个明白的递归结束条件,称为递归出口. 一个比較经典的描写叙述是老和尚讲故事,他说从前有座山,山上有座庙,庙里有个