【CSP2020】贪吃蛇(博弈,结论,双端队列)

Subtask 1:\(55pts\)

\(n\leq 2000\)

这就变成了一道模拟题了呀。

对于每一轮,先假设最大的吃了最小的,然后往下递归每一轮,并设置数组 \(alive\),维护这之后会有哪些蛇活着。在回溯的时候,如果最大蛇的发现它自己被吃了,就撤销吃这个动作,并重新更新 \(alive\) 数组。

可以用 set维护,时间复杂度 \(O(n^2)\)

#include<bits/stdc++.h>

#define N 1000010

using namespace std;

struct data
{
	int x,id;
	data(){};
	data(int a,int b){x=a,id=b;}
	bool operator < (const data &a) const
	{
		if(x==a.x) return id<a.id;
		return x<a.x;
	}
};

int t,n,maxn,ans,a[N],num[N],p[N];
bool alive[N],choose[N]; 

set<data>s;
vector<int>v[N];

void update(int k,bool tag)
{
	if(k>maxn) return;
	if(tag) alive[num[k]]=1;
	for(int i=0,size=v[k].size();i<size;i++)
		alive[v[k][i]]=!tag;
	if(tag&&choose[k])
	{
		alive[p[k]]=0;
		update(k+1,1);
	}
	else
	{
		alive[p[k]]=1;
		if(!tag) update(k+1,0);
	}
}

void dfs(int k)
{
	data st=*s.begin(),ed=*(--s.end()),eed=*(--(--s.end()));
	num[k]=ed.id;
	v[k].clear();
	while(s.size()>2&&ed.x-st.x>=eed.x)
	{
		data tmp=data(ed.x-st.x,ed.id);
		s.erase(s.begin());
		alive[st.id]=0;
		v[k].push_back(st.id);
		s.erase(--s.end());
		s.insert(tmp);
		st=*s.begin(),ed=*(--s.end()),eed=*(--(--s.end()));
	}
	if(s.size()==2)
	{
		alive[(*s.begin()).id]=0;
		v[k].push_back(st.id);
		alive[(*(--s.end())).id]=1;
		num[k]=ed.id;
		p[k]=0;
		maxn=k;
		return;
	}
	data tmp=data(ed.x-st.x,ed.id);
	s.erase(s.begin());
	p[k]=st.id;
	s.erase(--s.end());
	s.insert(tmp);
	dfs(k+1);
	choose[k]=alive[ed.id];
	update(k,1);
}

int main()
{
	scanf("%d",&t);
	t--;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		s.insert(data(a[i],i));
	}
	dfs(1);
	for(int i=1;i<=n;i++) ans+=alive[i];
	printf("%d\n",ans);
	while(t--)
	{
		memset(alive,0,sizeof(alive));
		int k;
		scanf("%d",&k);
		for(int i=1;i<=k;i++)
		{
			int x,y;
			scanf("%d%d",&x,&y);
			a[x]=y;
		}
		s.clear();
		for(int i=1;i<=n;i++)
			s.insert(data(a[i],i));
		dfs(1);
		ans=0;
		for(int i=1;i<=n;i++) ans+=alive[i];
		printf("%d\n",ans);
	}
	return 0;
}

Subtask2:\(70pts\)

\(n\leq5\times 10^4\)

\(\sout{55pts}\) 还好写。

发现如果进行到这一轮,那么这一轮的操作是固定的。(意思就是第 \(x\) 轮是谁吃谁是固定的)

那么就把每一轮是谁吃谁维护出来,最后在从后往前用桶扫一遍

set很好维护,时间复杂度 \(O(n\log n)\)

其实这个和 Subtask1 的做法的思想差不多,只不过这种做法是从首轮往末轮找哪个人最先停止,时间最坏就 \(O(n)\),而 Subtask1 的做法是从末轮往首轮找,时间最坏能 \(O(n^2)\)

Subtask3:\(100pts\)

\(n\leq 10^6\)

我们不用模拟所有轮过程的这个思路,回到原来题目中那个博弈的问题上来。

不妨设 \(\operatorname{check}(i)\) 表示第 \(i\) 轮的最强蛇吃了最弱蛇之后会不会被吃,那么我们就是要求出所有的 \(\operatorname{check}\) 或者找到第一个 \(i\) 使得 \(\operatorname{check}(i)=0\),因为在 \(\operatorname{check}(i)=0\) 时,最强蛇就会在这一轮停止。

首先是一个比较重要的结论:

如果当前最强的蛇吃了最弱的蛇之后,如果没有变成最弱的蛇,它就可以放心吃,也就是说此时 \(\operatorname{check}(i)=1\)

证明:

  1. 如果最强蛇吃完之后仍为最强蛇,那不吃白不吃。

  2. 如果最强蛇吃完之后不是最强蛇,那么设最强蛇为 \(A\),次强蛇为 \(B\),次弱蛇为 \(C\),最弱蛇为 \(D\)。那么 \(A\geq B \geq C \geq D\)

    首先第一轮取出 \(A\)\(D\),扔入 \(A-D\),其中满足 \(B>A-D\geq C\)\(A\) 吃完 \(D\) 之后不是最弱蛇,也不是最强蛇)。

    那么下一轮扔进去的肯定就是 \(B-C\),又由于 \(B-C\leq A-D\),所以如果我之后再要吃掉 \(A-D\),肯定要先吃掉 \(B-C\),那 \(B-C\) 能不能保证自己不被吃呢?答案是肯定的,因为 \(B\) 可以选择不吃 \(C\) 而是停止游戏,这样 \(B\) 肯定不会被吃,所以 \(A-D\) 也不会被吃掉。

证毕。

但如果当前最强的蛇吃了最弱蛇之后变成了最弱的蛇呢?

不妨设当前轮为第 \(i\) 轮,那么 \(\operatorname{check}(i)\) 的取值就要取决于:在最强蛇吃了最弱蛇、变成最弱蛇后,下一轮的最强蛇是否选择吃,如果选择吃。

即如果 \(\operatorname{check}(i+1)=1\),那么 \(\operatorname{check}(i)=0\),否则 \(\operatorname{check}(i)=1\)

递归的味道就很明显了。

递归边界就是到最后又重新出现了第一种情况或者只剩两条蛇。

有人可能问:那这个和 \(O(n^2 \log n)\) 的做法有什么区别吗?

答案是有的,按照这种方法,每一轮的 \(\operatorname{check}(i)\) 的取值,只会和下一轮的 \(\operatorname{check}(i+1)\) 的取值有关,而并不需要维护 \(alive\) 数组,而这一切的原因都是我们发现的那个重要的结论。

同样地,如果我们用 set维护,时间复杂度还是 \(O(n\log n)\) 的。

考虑 \(O(n)\) 维护。

发现第二种情况(最强蛇吃了最弱蛇之后变成最弱蛇)是可以 \(O(n)\) 维护的,主要是如何维护第一种情况(最强蛇吃了最弱蛇之后不是最弱蛇)。

不妨设当前蛇的能力序列为 \(a_1,\cdots,a_n\),那么第一种情况就是 \(a_n-a_1\geq a_2\)

那么这一轮操作后最小值就变为了 \(a_2\geq a_1\),最大值变为了 \(\max(a_{n-1},a_n-a_1)\leq a_1\)

发现如果一直在第一种情况,这个序列的最小值是单调不下降的,最大值是单调不上升的,那么这个序列最大值与最小值的差也是单调不上升的。

于是启发了我们用两个单调不下降的队列 \(q_1\)\(q_2\) 来表示这个序列 \(a\),并维护这个操作。

也就是说,当前的序列 \(a\) 可以用 \(q_1\)\(q_2\) 归并排序后的序列表示。

一开始先设 \(q_1\) 序列就是 \(a\) 序列,\(q_2\) 序列为空。

然后每次从取出全局最小值 \(minn=\min(q_{1,head},q_{2,head})\)\(maxn=\max(q_{1,tail},q_{2,tail})\),然后将 \(maxn-minn\) 插入到 \(q_2\) 的头。

容易发现 \(q_2\) 是单调不下降的,因为我们刚刚已经的出了结论:序列 \(a\) 的最大值和最小值的差是单调不上升的。

那么第一种情况就能 \(O(n)\) 维护了。

代码如下:

#include<bits/stdc++.h>

#define N 1000010

using namespace std;

struct data
{
	int x,id;
	data(){};
	data(int a,int b){x=a,id=b;}
};

bool operator < (data a,data b)
{
	if(a.x==b.x) return a.id<b.id;
	return a.x<b.x;
}

bool operator > (data a,data b)
{
	if(a.x==b.x) return a.id>b.id;
	return a.x>b.x;
}

data operator - (data a,data b){return data(a.x-b.x,a.id);}

struct Queue
{
	private:
		int head,tail;
		data q[N<<1];
	public:
		void clear(){tail=1e6,head=tail+1;}
		int size(){return tail-head+1;}
		bool empty(){return !size();}
		data front(){return q[head];}
		data back(){return q[tail];}
		void push_front(data x){q[--head]=x;}
		void push_back(data x){q[++tail]=x;}
		void pop_front(){head++;}
		void pop_back(){tail--;}
}a,b,c;

data getmin(bool flag)//求两个队列的最小值(flag=1就顺便pop掉)
{
	data minn;
	bool tag;
	if(a.empty()) minn=b.front(),tag=1;
	else if(b.empty()) minn=a.front(),tag=0;
	else
	{
		if(a.front()<b.front()) minn=a.front(),tag=0;
		else minn=b.front(),tag=1;
	}
	if(flag)
	{
		if(!tag) a.pop_front();
		else b.pop_front();
	}
	return minn;
}

data getmax(bool flag)//求两个队列的最大值(flag=1就顺便pop掉)
{
	data maxn;
	bool tag;
	if(a.empty()) maxn=b.back(),tag=1;
	else if(b.empty()) maxn=a.back(),tag=0;
	else
	{
		if(a.back()>b.back()) maxn=a.back(),tag=0;
		else maxn=b.back(),tag=1;
	}
	if(flag)
	{
		if(!tag) a.pop_back();
		else b.pop_back();
	}
	return maxn;
}

int T,n,k,val[N];

bool check(int step)
{
	if(c.size()==2) return 1;
	data minn=c.front(),maxn=c.back();
	c.pop_front(),c.pop_back();
	data smin=c.front();
	if(maxn-minn>smin) return 1;
	c.push_front(maxn-minn);
	return !check(step+1);
}

void work()
{
	//处理第一种情况
	int sum=-1;
	for(int i=1;i<n-1;i++)
	{
		data minn=getmin(1),maxn=getmax(1);
		data smin=getmin(0);
		b.push_front(maxn-minn);
		if(maxn-minn<smin)
		{
			sum=i;
			break;
		}
	}
	if(a.size()+b.size()==2&&sum==-1)
	{
		printf("%d\n",1);
		return;
	}
	//归并排序还原序列
	c.clear();
	while((!a.empty())&&(!b.empty()))
	{
		if(a.front()<b.front())
		{
			c.push_back(a.front());
			a.pop_front();
		}
		else
		{
			c.push_back(b.front());
			b.pop_front();
		}
	}
	while(!a.empty())
	{
		c.push_back(a.front());
		a.pop_front();
	}
	while(!b.empty())
	{
		c.push_back(b.front());
		b.pop_front();
	}
	//处理第二种情况
	bool flag=check(sum+1);
	if(flag)
	{
		printf("%d\n",n-sum+1);
		return;
	}
	else
	{
		printf("%d\n",n-sum);
		return;
	}
}

int main()
{
//	freopen("T4.in","r",stdin);
//	freopen("T4.out","w",stdout);
	scanf("%d%d",&T,&n);
	a.clear(),b.clear();
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&val[i]);
		a.push_back(data(val[i],i));
	}
	work();
	T--;
	while(T--)
	{
		scanf("%d",&k);
		for(int i=1;i<=k;i++)
		{
			int x,y;
			scanf("%d%d",&x,&y);
			val[x]=y;
		}
		a.clear(),b.clear();
		for(int i=1;i<=n;i++)
			a.push_back(data(val[i],i));
		work();
	}
	return 0;
}
/*
1 10
60 64 78 111 123 176 210 311 353 354
*/
posted @ 2022-10-28 20:39  ez_lcw  阅读(169)  评论(0)    收藏  举报