P7078 [CSP-S2020] 贪吃蛇

描述了蛇群决斗问题的解决方案,涉及动态策略和递归思想

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

草原上有 �n 条蛇,编号分别为 1,2,…,�1,2,…,n。初始时每条蛇有一个体力值 ��ai​,我们称编号为 �x 的蛇实力比编号为 �y 的蛇强当且仅当它们当前的体力值满足 ��>��ax​>ay​,或者 ��=��ax​=ay​ 且 �>�x>y。

接下来这些蛇将进行决斗,决斗将持续若干轮,每一轮实力最强的蛇拥有选择权,可以选择吃或者不吃掉实力最弱的蛇:

  1. 如果选择吃,那么实力最强的蛇的体力值将减去实力最弱的蛇的体力值,实力最弱的蛇被吃掉,退出接下来的决斗。之后开始下一轮决斗。
  2. 如果选择不吃,决斗立刻结束。

每条蛇希望在自己不被吃的前提下在决斗中尽可能多吃别的蛇(显然,蛇不会选择吃自己)。

现在假设每条蛇都足够聪明,请你求出决斗结束后会剩几条蛇。

本题有多组数据,对于第一组数据,每条蛇体力会全部由输入给出,之后的每一组数据,会相对于上一组的数据,修改一部分蛇的体力作为新的输入。

输入格式

第一行一个正整数 �T,表示数据组数。
接下来有 �T 组数据,对于第一组数据,第一行一个正整数 �n,第二行 �n 个非负整数表示 ��ai​。
对于第二组到第 �T 组数据,每组数据:
第一行第一个非负整数 �k 表示体力修改的蛇的个数。
第二行 2�2k 个整数,每两个整数组成一个二元组 (�,�)(x,y),表示依次将 ��ax​ 的值改为 �y。一个位置可能被修改多次,以最后一次修改为准。

输出格式

输出 �T 行,每行一个整数表示最终存活的蛇的条数。

输入输出样例

输入 #1复制

2
3
11 14 14
3
1 5 2 6 3 25

输出 #1复制

3
1

输入 #2复制

2
5
13 31 33 39 42
5
1 7 2 10 3 24 4 48 5 50

输出 #2复制

5
3

输入 #3复制

见附件中的 snakes/snakes3.in

输出 #3复制

见附件中的 snakes/snakes3.ans

输入 #4复制

见附件中的 snakes/snakes4.in

输出 #4复制

见附件中的 snakes/snakes4.ans

说明/提示

【样例 #1 解释】

第一组数据,第一轮中 33 号蛇最强,11 号蛇最弱。若 33 号蛇选择吃,那么它将在第二轮被 22 号蛇吃掉。因此 33 号蛇第一轮选择不吃,33 条蛇都将存活。

对于第二组数据,33 条蛇体力变为 5,6,255,6,25。第一轮中 33 号蛇最强,11 号蛇最弱,若它选择吃,那么 33 号蛇体力值变为 2020,在第二轮中依然是最强蛇并能吃掉 22 号蛇,因此 33 号蛇会选择两轮都吃,最终只有 11 条蛇存活。

【数据范围】

对于 20%20% 的数据,3n=3。
对于 40%40% 的数据,10n≤10。
对于 55%55% 的数据,2000n≤2000。
对于 70%70% 的数据,n≤5×104。

附件下载

snakes.zip1.36MB

首先有一个结论:

当前最强的蛇吃了最弱的蛇之后,如果没有变成最弱的蛇,他一定会选择吃!

证明:

假设当前最强的蛇叫石老板。

  • 如果下一条最强的蛇如果依旧是石老板,那肯定不吃白不吃;

  • 如果下一条最强蛇不是石老板,此时最强的蛇没有原先强,最弱的蛇也没原先弱,吃掉后肯定比石老板要弱。也就是说,当前最强的蛇吃了之后,如果会死,也会死在石老板前面。那么这样一来,这条蛇会想尽办法不死,从而石老板也一定能不死。

有了这个结论,一部分蛇可以放心大胆地吃了,但是问题来了,如果吃了之后变成最弱的蛇了,到底选择吃不吃呢?

稍微往后推一推就明白了:

当前最强蛇记为石老板,下一条最强蛇记为喵老板。石老板进食后变成最弱的蛇了,如果喵老板进食后不是最弱的蛇,他就会选择吃(根据开头的结论),这样石老板就凉了,所以石老板当初的选择一定是不吃。

如果喵老板进食后依旧是最弱的蛇,那就会考虑下一条最强蛇的情况,起名为汪老板。同样分两种情况:如果汪老板进食后不是最弱的蛇,那他就会选择吃,这样喵老板就凉了,所以他当初会选择不吃,这样石老板就不会死,那么石老板当初就会选择吃。如果汪老板进食后变成了最弱的蛇,那就再考虑下一条蛇………………

这个问题就变成了一个递归的问题了,直到某条蛇吃了之后不是最弱的蛇或者只能下两条蛇为止。这样,最后一条蛇会选择吃,倒数第二条蛇为了保命会选择不吃,倒数第三条蛇可以放心大胆的吃,倒数第四条蛇会保命选择不吃,倒数第五条蛇可以放心吃………………

这样,石老板选择吃不吃,就和最后一条蛇之间的奇偶性相关了。并且石老板选择不吃,游戏结束,石老板选择吃,游戏也会在下一轮结束(因为喵老板会选择不吃)。

到目前为止,这个题目很清晰了,只需模拟两个阶段即可:

阶段一:所有最强蛇进食后都不是最弱蛇,放心大胆吃!

阶段二:所有最强蛇进食后都是最弱蛇,直到有条蛇可以放心吃为止(吃了后不是最弱或者只剩两条)

阶段一结束时,游戏就基本结束了(根据阶段二的奇偶性看能不能再吃一次)

利用set可以方便地维护最强、最弱蛇,时间复杂度 �(��log⁡�)O(Tnlogn),只能拿 70 分,代码如下:

int a[N];
int main() {
    int _;
    scanf("%d", &_);
    int n;
    for (int cas = 1; cas <= _; cas++) {
        if (cas == 1) {
            scanf("%d", &n);
            for (int i = 1; i <= n; i++) {
                scanf("%d", &a[i]);
            }
        } else {
            int k;
            scanf("%d", &k);
            while (k--) {
                int x, y;
                scanf("%d%d", &x, &y);
                a[x] = y;
            }
        }
        set<pair<int, int> > se;
        for (int i = 1; i <= n; i++) {
            se.insert({a[i], i});
        }
        int flag = 0, ans;
        while (1) {
            if (se.size() == 2) {
                se.erase(se.begin());
                if (flag) {
                    if ((flag - se.size()) % 2) {
                        ans = flag + 1;
                    } else {
                        ans = flag;
                    }
                } else
                    ans = 1;
                break;
            }
            set<pair<int, int> >::iterator it = se.end();
            it--;
            int x = it->first, id = it->second;
            int y = se.begin()->first;
            se.erase(it);
            se.erase(se.begin());
            se.insert({x - y, id});
            if (se.begin()->second != id) {
                if (flag) {
                    if ((flag - se.size()) % 2) {
                        ans = flag + 1;
                    } else {
                        ans = flag;
                    }
                    break;
                }
            } else {
                if (flag == 0) flag = se.size();
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

正解:用两个双端队列 �1,�2q1​,q2​ 维护即可。

先把初始的有序蛇放进 �1q1​ 里,此时 �1q1​ 已满足单调性,头部小,尾部大,我们后面会让 �2q2​ 也满足这样的单调性。

第一阶段:

每次从 �1,�2q1​,q2​ 的尾部取出最强的蛇,从 �1q1​ 头部取出最弱的蛇,如果吃了以后是最弱的,那就进入第二阶段,否则直接装入 �2q2​ 的头部。

为什么可以装进 �2q2​ 并且是 �2q2​ 里最弱的?其实证明最初的结论时已经解释过了,后面进食的蛇肯定是越来越弱的,而且这个阶段最弱的蛇一定在 �1q1​ 中。

第二阶段:

此时最弱的蛇,就没必要丢进队列里了,单独维护一下就好了,因为连续的一段进食后都是最弱的,直到总蛇数等于 22 或者进食后不是最弱为止,而最强的依旧从 �1,�2q1​,q2​ 的尾部找。

这样就不需要用其他带 log⁡log 的数据结构维护了,时间复杂度 �(��)O(Tn),代码如下:

#include <bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define mp(x,y) make_pair(x,y)
inline int read(){
	char ch=getchar();
	int res=0,f=1;
	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())res=(res<<3)+(res<<1)+(ch-'0');
	return res*f;
}
const int MAXN=1e6+5,inf=1e9;
int t,n,a[MAXN];
pii q1[MAXN],q2[MAXN];
int l1,r1,l2,r2;
inline pii mx(){
	if(r1==l1)return q2[l2++];
	else if(r2==l2)return q1[--r1];
	else if(q2[l2]>q1[r1-1])return q2[l2++];
	else return q1[--r1];
}
inline pii mn(){
	if(l1==r1)return q2[--r2];
	else if(r2==l2)return q1[l1++];
	else if(q2[r2-1]<q1[l1])return q2[--r2];
	else return q1[l1++];
}
inline pii M_min(pii x,pii y){
	return x<y?x:y;
}
inline void solve(){
	l1=r1=l2=r2=0;
	for(int i=1;i<=n;++i)q1[r1++]=mp(a[i],i);
	int fl=0,cnt=0,alf=0;
	while(1){
		++cnt;
		pii x=mn(),y=mx();
		pii z=M_min((l1<r1?q1[l1]:mp(inf,-inf)),(l2<r2?q2[r2-1]:mp(inf,-inf)));
		y.first-=x.first;
		if(y>z||cnt==n-1){
			if(fl){
				printf("%d\n",n-(fl-(alf&1)));
				return ;
			}
			if(cnt==n-1){
				printf("1\n");
				return ;
			}
			q2[r2++]=y;
		}
		else {
			alf++;
			if(!fl)fl=cnt;	
			q2[r2++]=y;
		}
	}
}
int main(){
	t=read()-1;
	n=read();
	for(int i=1;i<=n;++i)a[i]=read();
	solve();
	while(t--){
		int k=read();
		for(int i=1,x;i<=k;++i)x=read(),a[x]=read();
		solve();
	}
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值