zoukankan      html  css  js  c++  java
  • 【CSP2020】贪吃蛇(博弈,结论,双端队列)

    Subtask 1: 55 p t s 55pts 55pts

    n ≤ 2000 nleq 2000 n2000

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

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

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

    #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
    ",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
    ",ans);
    	}
    	return 0;
    }
    

    Subtask2: 70 p t s 70pts 70pts

    n ≤ 5 × 1 0 4 nleq5 imes 10^4 n5×104

    55 p t s sout{55pts} 55pts 还好写。

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

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

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

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

    Subtask3: 100 p t s 100pts 100pts

    n ≤ 1 0 6 nleq 10^6 n106

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

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

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

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

    证明:

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

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

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

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

    证毕。

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

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

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

    递归的味道就很明显了。

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

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

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

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

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

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

    不妨设当前蛇的能力序列为 a 1 , ⋯   , a n a_1,cdots,a_n a1,,an,那么第一种情况就是 a n − a 1 ≥ a 2 a_n-a_1geq a_2 ana1a2

    那么这一轮操作后最小值就变为了 a 2 ≥ a 1 a_2geq a_1 a2a1,最大值变为了 max ⁡ ( a n − 1 , a n − a 1 ) ≤ a 1 max(a_{n-1},a_n-a_1)leq a_1 max(an1,ana1)a1

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

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

    也就是说,当前的序列 a a a 可以用 q 1 q_1 q1 q 2 q_2 q2 归并排序后的序列表示。

    一开始先设 q 1 q_1 q1 序列就是 a a a 序列, q 2 q_2 q2 序列为空。

    然后每次从取出全局最小值 m i n n = min ⁡ ( q 1 , h e a d , q 2 , h e a d ) minn=min(q_{1,head},q_{2,head}) minn=min(q1,head,q2,head) m a x n = max ⁡ ( q 1 , t a i l , q 2 , t a i l ) maxn=max(q_{1,tail},q_{2,tail}) maxn=max(q1,tail,q2,tail),然后将 m a x n − m i n n maxn-minn maxnminn 插入到 q 2 q_2 q2 的头。

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

    那么第一种情况就能 O ( n ) O(n) 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
    ",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-sum+1);
    		return;
    	}
    	else
    	{
    		printf("%d
    ",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
    */
    
  • 相关阅读:
    Navicat Premium 最新激活码 12 免安装(不用注册码,不用注册机)
    Vs Code 2019软件安装教程及常用的入门设置
    (WinForm)FormBorderStyle属性
    背包的题后总结。
    123
    网络最大流——最高标号预流推进
    厉害的网站
    一般图最大匹配——带花树
    四边形不等式
    你好,OI
  • 原文地址:https://www.cnblogs.com/ez-lcw/p/14448639.html
Copyright © 2011-2022 走看看