贪心策略(相邻交换) 学习笔记

贪心策略(相邻交换) 学习笔记

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

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

主要思想

设交换前对答案的贡献为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

 

posted @ 2018-10-05 16:01  ljc20020730  阅读(719)  评论(0编辑  收藏  举报