浅谈贪心【复习】

NYG的背包

【问题描述】

NYG有一个神奇的背包,每放进去一个物品,背包的体积就会变大。

也就是说,每放进一个物品,背包会被占用一定的体积,但是紧接着背包的总体积又会增大一定的值(注意是在放入物品后背包总体积才增大)。

NYG发觉这个背包十分好用,于是不由自主地想到了一个问题。

现在给出背包初始容量V 以及n个物品,每一个物品两个值a, b,分别表示物品所占体积 和放入背包后背包增大的体积。

NYG想知道能否把所有物品装进去? 因为NYG比较老实,这么简单的问题自然装作不会做的样子。 于是他来请教你。

分析:

因为只问你能否装完,这就好办了多

肯定需要把a<b的先用了,才可能搞得定a>b的

如果前者都没法的话,更就别说后者

对于a<b的部分,肯定是先用a较小的

因为有可能只有把前几个空间赚回来了才能装下后面几个

关键就在于a>b的情况

这时发现肯定是亏空间的,考虑最优一定是先亏小空间,这样能腾出更多的空间为后面放

读完题别以为是背包....

code:

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 100004;
inline int read()
{
    int ret, f=1;
    char c;
    while((c=getchar())&&(c<'0'||c>'9'))if(c=='-')f=-1;
    ret=c-'0';
    while((c=getchar())&&(c>='0'&&c<='9'))ret = (ret<<3)+(ret<<1)+c-'0';
    return ret*f;
}
int T, n, s, cnt1, cnt2;
struct thi{
    ll a, b;
}t[maxn], f[maxn];
bool cmp1(thi x, thi y)
{
    return x.a < y.a;
}
bool cmp2(thi x, thi y)
{
    return x.b > y.b;
}
int main()
{
    T = read();
    while(T--)
    {
        n = read();s = read();
        cnt1 = cnt2 = 0;    
        for(int i = 1; i <= n; ++i)
        {
            ll x = 1LL * read(), y = 1LL * read();
            if(y > x)
                t[++cnt1] = (thi){x, y};
            else
                f[++cnt2] = (thi){x, y};
        }
        sort(t + 1, t + cnt1 + 1, cmp1);
        ll now = 1LL * s;    
        bool fl = 0;    
        for(int i = 1; i <= cnt1; ++i)
        {
            now -= t[i].a;
            if(now < 0)
            {
                fl = 1;
                break;
            }
            now += t[i].b;    
        }
        if(!fl)
        {
            sort(f + 1, f + cnt2 + 1, cmp2);
            for(int i = 1; i <= cnt2; ++i)
            {
                now -= f[i].a;
                if(now < 0)
                {
                    fl = 1;
                    break;
                }
                now += f[i].b;    
            }
        }
        printf("%s\n", fl? "No": "Yes");
    }
    return 0;
}

描述
一张普通的国际象棋棋盘,它被分成 8 乘 8 (8 行 8 列) 的 64 个方格。设有形状一样的多米诺牌,每张牌恰好覆盖棋盘上相邻的两个方格,即一张多米诺牌是一张 1 行 2 列或者 2 行 1 列的牌。那么,是否能够把 32 张多米诺牌摆放到棋盘上,使得任何两张多米诺牌均不重叠,每张多米诺牌覆盖两个方格,并且棋盘上所有的方格都被覆盖住?我们把这样一种排列称为棋盘被多米诺牌完美覆盖。这是一个简单的排列问题,同学们能够很快构造出许多不同的完美覆盖。但是,计算不同的完美覆盖的总数就不是一件容易的事情了。不过,同学们 发挥自己的聪明才智,还是有可能做到的。

现在我们通过计算机编程对 3 乘 n 棋盘的不同的完美覆盖的总数进行计算。

这里写图片描述

分析:

首先奇数肯定无解,所以考虑偶数递推转移

很明显设f[i]表示前i列的方案数

只看两列的话只有三种方法

所以有f[i]=f[i-2]*3

但是有一种四列格子的会多出两种方案

一种六列格子会多出两种方案

所以转移就有

f(n)=3f(n-2)+2f(n-4)+2f(n-6)+...+2f(0)

f(n-2)= 3f(n-4)+2f(n-6)+......+2f(0)

两式相减得到

f(i)=4f(i-2)-f(i-4)

递推转移就行

代码就是一个递推式,就懒得写了

分析:

排完序后

答案选的区间一定是一个连续的区间

一个连续的区间什么时候取到最优解呢?

当然是中位数了

这样用尺取法双指针就可以做到O(n)判断了

code by std:

#include<cstdio>
#include<algorithm>
using namespace std;
#define rep(i,n) for(int i=1;i<=n;++i)
#define mp make_pair
#define pb push_back
#define x0 gtmsub
#define y0 gtmshb
#define x1 gtmjtjl
#define y1 gtmsf
long long s[100010];
int n,k,a[100010],ans;
bool check(int l,int r)
{
    int x=(l+r)>>1;
    return (1ll*(x-l+1)*a[x]-(s[x]-s[l-1]))+((s[r]-s[x])-1ll*(r-x)*a[x])<=k;
}
int main()
{
    freopen("signin.in", "r", stdin);
    freopen("signin.out", "w", stdout);
    scanf("%d%d",&n,&k);
    rep(i,n)scanf("%d",&a[i]);
    sort(a+1,a+n+1);
    rep(i,n)s[i]=s[i-1]+a[i];
    int now=1;
    rep(i,n)
    {
        now=max(now,i);
        for(;now<=n&&check(i,now);now++);
        ans=max(ans,now-i);
    }
    printf("%d\n",ans);
    return 0;
}

这题果然够签到的

吐槽:最讨厌这种n又要考虑点,又要考虑边的题了

假如说考虑将点x设为晴天点

把该点之前的点和该点之后的点的贡献分别考虑

将原式化简

code by std

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
#define N 1000005
#define ll long long
using namespace std;
int n,d;
int s[N];
ll sum1[N],sum2[N],sum3[N],ans[N];
ll work(int a,int b,int x){
	ll s1,s2,s3;
	if(a<=0||a>n) s1=sum1[b],s2=sum2[b],s3=sum3[b];
	else s1=sum1[b]-sum1[a],s2=sum2[b]-sum2[a],s3=sum3[b]-sum3[a];
	ll an=s3+(ll)2*s2*x+(ll)x*x*s1;
	return an;
}
int main()
{
	scanf("%d%d",&n,&d);
	for(int i=1;i<=n;i++) scanf("%d",&s[i]);
	for(int i=1;i<=n;i++) sum1[i]=sum1[i-1]+s[i],sum2[i]=sum2[i-1]+(ll)i*s[i],sum3[i]=sum3[i-1]+(ll)i*i*s[i];
	for(int i=1;i<=n+1;i++) ans[i]+=work(i-d-1,i-1,d-i+1);
	for(int i=n;i>=1;i--) sum1[i]=sum1[i+1]+s[i],sum2[i]=sum2[i+1]+(ll)(n-i+1)*s[i],sum3[i]=sum3[i+1]+(ll)(n-i+1)*(n-i+1)*s[i];
	for(int i=1;i<=n+1;i++) ans[i]+=work(i+d,i,d+i-n-1);
	ll mx=0;
	for(int i=1;i<=n+1;i++) mx=max(mx,ans[i]);
	printf("%lld\n",mx);
	return 0;
}

https://www.luogu.org/problem/P4393

题目描述

对于一个给定的序列a1, …, an,我们对它进行一个操作reduce(i),该操作将数列中的元素ai和ai+1用一个元素max(ai,ai+1)替代,这样得到一个比原来序列短的新序列。这一操作的代价是max(ai,ai+1)。进行n-1次该操作后,可以得到一个长度为1的序列。

我们的任务是计算代价最小的reduce操作步骤,将给定的序列变成长度为1的序列。

分析:

事实上每个数最多只可能被有效加两次(想一想,为什么

合并一次后被另一个数合并,或者两边的数都比他小(如图a3)

而看每一边的情况

当一边的数都比a[i]小,最终一定会使a[i]被加一次,如果如图中a3,一侧有a1比a3大,显然在加上a1前会先和a3合并使得结果更优

另一边同理

code:

#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
int n;
long long ans, a[1000100];
int main()
{
    cin >> n;
    for (int i = 0; i < n; i++){
        cin >> a[i];
    }
    for (int i = 1; i < n; i++){
        ans += max(a[i - 1], a[i]);
    }
    cout << ans;
}
posted @ 2019-11-15 12:36  wzx_believer  阅读(223)  评论(0编辑  收藏  举报