【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\)。
证明:
-
如果最强蛇吃完之后仍为最强蛇,那不吃白不吃。
-
如果最强蛇吃完之后不是最强蛇,那么设最强蛇为 \(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
*/

浙公网安备 33010602011771号