Subtask 1: 55 p t s 55pts 55pts
n ≤ 2000 n\leq 2000 n≤2000。
这就变成了一道模拟题了呀。
对于每一轮,先假设最大的吃了最小的,然后往下递归每一轮,并设置数组 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\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: 70 p t s 70pts 70pts
n ≤ 5 × 1 0 4 n\leq5\times 10^4 n≤5×104。
比
55
p
t
s
\sout{55pts}
55pts 还好写。
发现如果进行到这一轮,那么这一轮的操作是固定的。(意思就是第 x x x 轮是谁吃谁是固定的)
那么就把每一轮是谁吃谁维护出来,最后在从后往前用桶扫一遍
用 set
很好维护,时间复杂度
O
(
n
log
n
)
O(n\log 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 n\leq 10^6 n≤106。
我们不用模拟所有轮过程的这个思路,回到原来题目中那个博弈的问题上来。
不妨设 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。
证明:
-
如果最强蛇吃完之后仍为最强蛇,那不吃白不吃。
-
如果最强蛇吃完之后不是最强蛇,那么设最强蛇为 A A A,次强蛇为 B B B,次弱蛇为 C C C,最弱蛇为 D D D。那么 A ≥ B ≥ C ≥ D A\geq B \geq C \geq D A≥B≥C≥D。
首先第一轮取出 A A A、 D D D,扔入 A − D A-D A−D,其中满足 B > A − D ≥ C B>A-D\geq C B>A−D≥C( A A A 吃完 D D D 之后不是最弱蛇,也不是最强蛇)。
那么下一轮扔进去的肯定就是 B − C B-C B−C,又由于 B − C ≤ A − D B-C\leq A-D B−C≤A−D,所以如果我之后再要吃掉 A − D A-D A−D,肯定要先吃掉 B − C B-C B−C,那 B − C B-C B−C 能不能保证自己不被吃呢?答案是肯定的,因为 B B B 可以选择不吃 C C C 而是停止游戏,这样 B B B 肯定不会被吃,所以 A − D A-D A−D 也不会被吃掉。
证毕。
但如果当前最强的蛇吃了最弱蛇之后变成了最弱的蛇呢?
不妨设当前轮为第 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(n\log 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_1\geq a_2 an−a1≥a2。
那么这一轮操作后最小值就变为了 a 2 ≥ a 1 a_2\geq a_1 a2≥a1,最大值变为了 max ( a n − 1 , a n − a 1 ) ≤ a 1 \max(a_{n-1},a_n-a_1)\leq a_1 max(an−1,an−a1)≤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 maxn−minn 插入到 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\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
*/