wtpavssite

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

贪心

1.CF1238C Gas Pipeline

一开始的贪心策略是对的,但是比较复杂和分类的时候错了,我把中间0组成的hole按照总的来判断是否该下降而不是有的下降和有的不下降

2.CF1077轮B Seats

一个学生最多占据三个位置,那么就按照一个学生三个位置,我们把字符串中连续的0找出来,计算出长度,我们只需要将其中每三个坐一个学生就可以,但是需要分情况讨论,一种是这段0在两个1中间,另一种是一段在边界,最后一种是字符串全是0。对于第一种情况我们只需要用长度len/3向下取整就可以,余下的放到两边去可以被周围的1覆盖,按照这种思路,第二种情况有一段的余出来的0无法被1覆盖,那么这时候必须在无法覆盖的那段加上一个位置——0(虚拟),那么这段len+1,那么最后一种情况就是要多安排两个了,len+=2。

3.超哥的果园

链接:https://hydro.ac/d/CUIT_DSA/p/886?tid=69be7e9787636554247dc0a0

开始的思路:

我们先考虑最大的体力消耗,而最小的体力消耗只需要和最大的反过来就可以了。那么我们只需要每次都对最重的一堆果子和他的邻居进行合并,那么每次体力消耗都是最大值mx,最终的体力消耗就是(n-1)*mx。现在最小的体力消耗思路就是尽量避免用最重的进行合并,因为最轻的一堆肯定是只要被合并就会被消耗,而这次的合并的体力消耗就是和这个最轻的一堆相邻的更轻的一堆,那么我们尝试使合并的代价尽量小,所以我们一定是每次都选择目前重量最小的一堆,看他的左右邻居的重量,才有可能碰到第二小的重量,反之一定是无法取到每次最小的。

错误的原因:

其实这个思路是对的,但是在实现的时候,不能更好的优化,不仅T了,内存还会爆,而且无法处理重复数字的问题,正确的代码实现用到双向链表,比如L[i]代表序号i的数字的左邻居(没有被合并)

代码:

点击查看代码
#include<bits/stdc++.h>
#define PII pair<int,int>
using namespace std;
using ll=long long;
const int N=1e5+10;
int main(){
	int t;
	cin>>t;
	int L[N],R[N];
	while(t--){
		int n;
		cin>>n;
		vector<int>a(n+1);
		priority_queue<PII,vector<PII>,greater<PII>>q;
		for(int i=1;i<=n;i++){
			cin>>a[i];
			q.push({a[i],i});
			if(i==1)L[i]=n;
			else L[i]=i-1;
			if(i==n)R[i]=1;
			else R[i]=i+1;
		}
		ll ans=0;
		while(q.size()>1){
			auto [x,i]=q.top();
			q.pop();
			ans+=min(a[L[i]],a[R[i]]);
			R[L[i]]=R[i];
			L[R[i]]=L[i];
		}
		cout<<ans<<endl;
	}
}

4.小红的中位数

链接:https://ac.nowcoder.com/acm/contest/130107/D

开始的思路:

这道题我的思路是先分类这个数组的大小n为奇数和偶数的情况,如果是奇数,那么删除了一个数字(不是删除之前的中位数)之后新的中位数会在原来的中位数的前一个,那么删除了一个之后会变成偶数个,而偶数个按照给出的规则删除之后新的中位数是原来的中位数的后一个。那么我们可以得到,奇数的情况下不要删除中位数前面的数字,因为删除之后新的中位数和原来的中位数是同一个,偶数的情况就是不要删除中位数后面的数字。然后来讨论删除当前的中位数的情况,我觉得每次都删除原来的中位数是最优的解决方法。所以我用while来寻找最开始的中位数的两边的和自己相同的数字的圆的半径,半径取两个最小值,然后发现左边半径和右边半径的大小情况不同的时候,最后的计算逻辑是不同的。

错误的原因:

求求你不要再爬塔二了

链接:https://qoj.ac/contest/3935/problem/18961

开始的思路

最开始考虑到的是贪心,但是我认为有些情况贪性价比不是最优的,就比如两个能量的情况下有两张牌,第一张费用是1,价值是5,第二张费用是2,价值是6,如果简单的贪性价比就会是错误的答案。所以我就考虑了dp,一开始认为用dpi表示用了多少费用的情况下,能打出的最大的伤害。但是我将他们进行了排序,强制了dp的选择顺序是先选同费用伤害高的,这实际上是贪心,而dp是不考虑顺序的,他选择的最优解是全集里面的子集。

正确的思路:

1.这道题是能用贪心来做的只是和贪性价比不一样,我们将x的牌的最高的伤害记作是基底伤害,对于1费和2费牌,如果伤害没有超过这个伤害我们完全不用去考虑他们,然后我们来枚举选了分别选了多少个能量给二费牌以及一费牌,多余的能量全部给X牌。当确定了i个能量给二费牌,j个能量给一费牌的时候,因为他们的费用是完全一样的,所以我们只需要知道选了几个能量给对方,直接取前面的牌就可以了,所以我们可以用前缀数组来优化这个过程。

代码:

点击查看代码
#include<bits/stdc++.h>
#define  ll long long
#define pii pair<int,int>
using namespace std;
void solve(){
    int n,m;
    cin>>n>>m;
    vector<ll>one,two;
    ll best=0;
    for(int i=0;i<n;i++){
        char op;
        ll num;
        cin>>op>>num;
        if(op=='1')one.push_back(num);
        if(op=='2')two.push_back(num);
        if(op=='X') {
            best= max(num,best);
        }
    }
    vector<ll>a,b;
    for(int i=0;i<one.size();i++){
        if(one[i]>best){a.push_back(one[i]);}
    }
    for(int i=0;i<two.size();i++){
        if(two[i]>2*best){b.push_back(two[i]);}
    }
    sort(a.rbegin(),a.rend());
    sort(b.rbegin(),b.rend());
    vector<ll>pre1(a.size()+1,0),pre2(b.size()+1,0);
    for(int i=1;i<=a.size();i++){
        pre1[i]=pre1[i-1]+a[i-1];
    }
    for(int i=1;i<=b.size();i++){
        pre2[i]=pre2[i-1]+b[i-1];
    }
    ll ans=(ll)m*best;
    for(int i=0;i<=b.size();i++){
        int f=2*i;
        if(f>m)break;
        int res=m-f;
        int j=min(res,(int)a.size());
        ll damage=pre2[i]+pre1[j]+(m-2*i-j)*best;
        ans=max(ans,damage);
    }
    cout<<ans<<endl;
    return ;
}
int main(){
    int t;
    t=1;
//    cin>>t;
    while(t--){
        solve();
    }
}

2.dp思路:
首先观察到这些牌只能打一次,很自然联想到01背包问题,特殊的是因为X牌的存在,所以我们需要枚举考虑给出多少个能量来分给X牌,也就是剩下这些能量然后打出X牌。可惜的是这里的时间复杂度其实是O(m*n)的,所以这样写的dp是通过不了的,但是我们还是利用dp的思想,我们发现这些牌的费用全是1或者2,他们的费用是相同的,那么如果我们选择了某个能量打一费牌,拿肯定是打伤害最高的牌,所以类似上面的代码,我们也能得到答案,因为这样的代码和上面的太过类似,所以只写出超时的dp的代码(AI生成代码,未修改)

点击查看代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    cin >> n >> m;

    // dp[e] = 只用1费和2费牌,恰好消耗e点能量时的最大伤害
    // -1 表示该能量值无法恰好达到
    vector<int> dp(m + 1, -1);
    dp[0] = 0;

    int bestX = 0; // 伤害系数最大的X牌

    for (int i = 0; i < n; ++i) {
        char type;
        int v;
        cin >> type >> v;

        if (type == '1') {
            // 0-1背包:倒序更新,防止重复使用同一张牌
            for (int e = m; e >= 1; --e) {
                if (dp[e - 1] != -1) {
                    dp[e] = max(dp[e], dp[e - 1] + v);
                }
            }
        } else if (type == '2') {
            for (int e = m; e >= 2; --e) {
                if (dp[e - 2] != -1) {
                    dp[e] = max(dp[e], dp[e - 2] + v);
                }
            }
        } else if (type == 'X') {
            // X牌只保留伤害系数最大的那一张
            bestX = max(bestX, v);
        }
    }

    int ans = 0;
    // 情况1:纯普通牌,用完所有能量
    if (dp[m] != -1) ans = dp[m];

    // 情况2:普通牌消耗e点能量,剩下的全部给X牌
    for (int e = 0; e <= m; ++e) {
        if (dp[e] != -1) {
            ans = max(ans, dp[e] + (m - e) * bestX);
        }
    }

    cout << ans << '\n';
    return 0;
}
posted on 2026-01-29 12:55  wt_pav  阅读(8)  评论(0)    收藏  举报