「CSP-S2020」贪吃蛇

就离谱。70 如此好打考场上面没敢实现。

首先有一个非常显然的结论,也就是说如果一条蛇做出了选择(无论吃与不吃)并且不会成为最小的一条蛇,这条蛇以后就永远不会被吃了。

考虑到这个问题是具有严格偏序性质的。假设当前排出来的蛇的序列为 (a_1,a_2, cdots ,a_n),并且保证 (a) 单调不减(因为有编号)。

  • 不吃:结束游戏,不会被吃;
  • 吃:
    • 如果仍然是最强的蛇,肯定不会被吃;
    • 如果弱了,显然 (a_n - a_1 > a_{n-1} - a_2),所以说如果下一条最大的蛇要吃,肯定也比当前这个最大的蛇吃掉后小,会吃。

所以说我们用一个容器,每次找到最大最小的蛇,相减比较。考虑以下按以下流程进行算法。

  • 找到当前的最小蛇 (x),次小蛇 (y),最大蛇 (z)
  • 如果 (z-x > y),吃掉;
  • 否则,我们的答案就与「下一只最大的蛇是否会吃」有关。再对这个子问题进行决策判断即可。

定义「冒险」为当前的最大蛇吃掉了最小蛇变成了最小蛇。

有一个比较显然的计算答案的方法就是每次递归找往上传答案。但是实际上我们知道有蛇开始「冒险」的时候,还有多少条蛇,设为 (p);「冒险」的蛇有多少,设为 (q)。答案就是 (p-(q mod 2))

对于 (p) 是显然的,关键是 ((q mod 2)) 是怎么来的。

这个可以用感性理解。如果说最后一个「冒险」的蛇「冒险」之后死不掉,那么倒数第二个「冒险」的蛇就不会选择去「冒险」。依次往上传,就有了这样的答案。但是 (O(Tn log n)) 的代码没那么写。

支持动态删除插入查询最大最小次小值,可以用 set 去维护。时间复杂度 (O(Tn log n))

附一个 luogu 民间数据 (75 sim 90) 的代码(代码很丑,而且愣是没卡过)。

#include<cstdio>
#include<algorithm>
#include<queue>
#include<map>
#include<set>
using namespace std;
char buf[1<<16],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<16,stdin),p1==p2)?EOF:*p1++)
int read()
{
	int x=0,f=1;
	char c=getchar();
	while(c<'0' || c>'9')
	{
		if(c=='-')	f=-1;
		c=getchar();
	}
	while(c>='0' && c<='9')	x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
	return x*f;
}
void write(int x)
{
	if(x<0)	putchar('-'),x=-x;
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
struct Snake{
	int val,pos;
	Snake(int V=0,int P=0){val=V,pos=P;}
	bool operator < (Snake ano) const {return val<ano.val || (val==ano.val && pos<ano.pos);}
	Snake operator - (Snake ano) const {return Snake(val-ano.val,pos);}
};
set<Snake> S;
int n,a[1000005],ans;
bool dfs(bool flag)
{
	if(int(S.size())==2)	return true;
	int del=0;
	while(int(S.size())>=3)
	{
		set<Snake>::iterator it1=S.begin(),it2=S.begin(),it3=S.end();
		++it2,--it3;
		int tmp=int(S.size());
		Snake x=*it1,y=*it2,z=*it3,dec=z-x;
		S.erase(it3);
		S.erase(it1);
		S.insert(dec);
		if(dec<y)
		{
			if(!dfs(true))
			{
				ans=tmp-1;
				return true;
			}
			else if(del)
			{
				ans=tmp;
				return true;
			}
			else
			{
				ans=tmp;
				return false;
			}
		}
		++del;
		if(del && flag)	return true;
	}
	ans=1;
	return true;
}
int main(){
	int T=read();
	n=read();
	long long sum=0;
	for(int i=1;i<=n;++i)	a[i]=read(),sum+=a[i];
	sum-=a[n];
	if(n==3)
	{
		if(a[1]+a[2]<=a[3])	puts("1");
		else	puts("3");
	}
	else
	{
		if(sum<=a[n])	puts("1");
		else
		{
			for(int i=1;i<=n;++i)	S.insert(Snake(a[i],i));
			ans=n;
			dfs(false);
			write(ans);
			puts("");
		}
	}
	--T;
	while(T-->0)
	{
		int k=read();
		for(int i=1;i<=k;++i)
		{
			int pos=read(),val=read();
			sum-=a[pos];
			a[pos]=val;
			sum+=a[pos];
		}
		if(n==3)
		{
			if(a[1]+a[2]<=a[3])	puts("1");
			else	puts("3");
			continue;
		}
		if(sum<=a[n])
		{
			puts("1");
			continue;
		}
		S.clear();
		for(int i=1;i<=n;++i)	S.insert(Snake(a[i],i));
		ans=n;
		dfs(false);
		write(ans);
		puts("");
	}
	return 0;
}

考虑优化,我们仔细回顾一下之前证明的那个结论。也就是 (a_n - a_1 > a_{n-1} - a_2)

这个性质会不会指引我们去用两个队列存一下原有蛇和吃掉了小蛇后变成的蛇(分别记成第一个队列和第二个队列)呢?

这个方法是有效的,必须满足一下四种情况:

  • 如果最大最小蛇都是原有的,那么新蛇小于第二个队列中的最小蛇;
  • 如果最大最小蛇都不是原有的,那么新蛇小于第二个队列中的最小蛇;
  • 如果最大蛇是原有的,最小蛇不是原有的,那么新蛇小于第二个队列中的最小蛇;
  • 如果最大蛇不是原有的,最小蛇是原有的,那么新蛇小于第二个队列中的最小蛇。

第一种情况:

因为 (a_n - a_1 > a_{n-1} - a_2),归纳可证明;

第二种情况:

乍一看是不符合的,实际上「不是原有的」和「冒险」是等价的。这样的话最小蛇肯定不会冒险,实际上这种情况是不存在的。

第三种情况:

同第二种情况。

第四种情况:

考虑用第一种情况归纳下来的结论,发现出现这种情况的条件是第二个队列只有一条蛇而且这条蛇是最大蛇,所以这种情况显然是成立的。

至此,可以用两个双端队列/链表去维护两个队列,时间复杂度 (O(Tn)),实际操作并没有什么两样。随便改一下应该就能过。

#include<cstdio>
#include<algorithm>
#include<queue>
#include<map>
#include<set>
using namespace std;
char buf[1<<16],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<16,stdin),p1==p2)?EOF:*p1++)
int read()
{
	int x=0,f=1;
	char c=getchar();
	while(c<'0' || c>'9')
	{
		if(c=='-')	f=-1;
		c=getchar();
	}
	while(c>='0' && c<='9')	x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
	return x*f;
}
void write(int x)
{
	if(x<0)	putchar('-'),x=-x;
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
struct Snake{
	int val,pos;
	Snake(int V=0,int P=0){val=V,pos=P;}
	bool operator < (Snake ano) const {return val<ano.val || (val==ano.val && pos<ano.pos);}
	Snake operator - (Snake ano) const {return Snake(val-ano.val,pos);}
}q1[1000005],q2[1000005];
//set<Snake> S;
int n,a[1000005],ans,l1,r1,l2,r2;
Snake Max()
{
	if(l1>r1)	return q2[l2++];
	if(l2>r2)	return q1[r1--];
	if(q1[r1]<q2[l2])	return q2[l2++];
	return q1[r1--];
}
Snake Min()
{
	if(l1>r1)	return q2[r2--];
	if(l2>r2)	return q1[l1++];
	if(q1[l1]<q2[r2])	return q1[l1++];
	return q2[r2--];
}
void dfs()
{
	int risk=0,kill=0,dep=0;
	while(1)
	{
		++kill;
		Snake x=Max(),z=Min(),y=min(l1<=r1?q1[l1]:Snake(100860010,n+1),l2<=r2?q2[r2]:Snake(1008610010,n+1)),dec=x-z;
		if(y<dec || kill==n-1)
		{
			if(risk)
			{
				write(n-risk+(dep&1));
				puts("");
				break;
			}
			if(kill==n-1)
			{
				puts("1");
				break;
			}
			q2[++r2]=dec;
		}
		else
		{
			++dep;
			if(!risk)	risk=kill;
			q2[++r2]=dec;
		}
	}
}
int main(){
	int T=read();
	n=read();
	long long sum=0;
	for(int i=1;i<=n;++i)	a[i]=read(),sum+=a[i];
	sum-=a[n];
	if(n==3)
	{
		if(a[1]+a[2]<=a[3])	puts("1");
		else	puts("3");
	}
	else
	{
		if(sum<=a[n])	puts("1");
		else
		{
			l1=1,r1=n,l2=1,r2=0;
			for(int i=1;i<=n;++i)	q1[i]=Snake(a[i],i);
			ans=n;
			dfs();
		}
	}
	--T;
	while(T-->0)
	{
		int k=read();
		for(int i=1;i<=k;++i)
		{
			int pos=read(),val=read();
			sum-=a[pos];
			a[pos]=val;
			sum+=a[pos];
		}
		if(n==3)
		{
			if(a[1]+a[2]<=a[3])	puts("1");
			else	puts("3");
			continue;
		}
		if(sum<=a[n])
		{
			puts("1");
			continue;
		}
		l1=1,r1=n,l2=1,r2=0;
		for(int i=1;i<=n;++i)	q1[i]=Snake(a[i],i);
		ans=n;
		dfs();
	}
	return 0;
}
原文地址:https://www.cnblogs.com/amagaisite/p/13968875.html