果然还是压轴题……
# 题面
> Linked 洛谷 P7078
点击展开/折叠 题面
有 $n$ 条蛇,每条蛇有一个体力值 $h_i$。蛇之间会发生竞争,每次竞争由 $(h_i,i)$ 最大的蛇决定,$(h_i,i)$ 最大的蛇可以选择:
- 停止竞争;
- 吃掉 $(h_j,j)$ 最小的蛇,体力变为 $h_i-h_j$;
每条蛇都希望在自己不被吃掉的前提下尽可能多地吃掉其他蛇,求每条蛇都采取最优策略时,最后会剩下多少蛇。
$n\le10^6$,保证输入的 $h_i$ 不减。
# 解析
一道博弈思想非常复杂的题……
- 小暴力
首先较简单的暴力想法就是先假设每次最大蛇都选择吃掉最小蛇,一直竞争到只剩一条。然后再从只剩一条蛇的状态倒推回来,即现在我们知道——如果第 $i$ 次竞争中,最大蛇吃掉最小蛇(转移到下一个状态),那么最终剩下的蛇有哪些。如果发现第 $i$ 次竞争后最大蛇还活着,就可以吃,否则就停止。
只需要 set
维护一下最大蛇最小蛇,做到单次 $O(n\log n)$ 的复杂度,可以得到 70pts 的好成绩~
- 正解
但是正解思路跟这个完全不一样。
先分析一个性质:
(剩下至少三条蛇)设现在 $h_i$ 的最大值、次小值和最小值分别是 $x,y,z$
若 $x-z>y$ 或 $x-z=y$ 且 x 的编号大于 y,即最大蛇吃掉最小蛇后没有变成剩下的蛇中最小的,则最大蛇一定可以吃。
简单证明如下:分析下一次竞争,记此时的最大值为 $w$,显然有 $w\le x$,此时最小值是 $y$,又有 $y\ge z$;则 $w-y\le x-z$,则下一次竞争得到的“新蛇”(带上编号考虑)一定更小;如果 $x-z$ 被吃,$w-y$ 一定会先被吃掉,于是 $w$ 此时就会选择不吃 $y$ 然后竞争结束,而 $x$ 就可以非常安全地吃掉 $z$。
下面借用博主 Resuscitate 的思路
如果最大蛇 $x$ 吃掉最小蛇 $z$ 后变成最小蛇了(情况二),则称这次竞争是一次“冒险”。如果要冒险,则必须保证冒险后竞争立即结束。
先一直模拟,直到第一次“冒险”。假设竞争还没有结束,然后继续模拟,直到某一次竞争中,最大蛇可以非常安全地吃掉最小蛇,此时最小蛇一定是上一次竞争中冒险的蛇。设这两次竞争之间一共发生了 $k$ 次冒险,则
$k$ 为偶数时,第一条将冒险的蛇会冒险,然后竞争立即结束;否则第一条将冒险的蛇直接结束竞争。
其实手玩儿一下就可以弄懂——
- 最后一条冒险的蛇会被吃掉,所以不会冒险;
- 倒数第二条冒险的蛇不会被吃掉(因为下一条蛇让竞争立即结束了),所以冒险;
- 倒数第三条冒险的蛇会被倒数第二条冒险的蛇吃掉,所以不冒险;
- ……
- 如果 $k$ 是偶数,则第二条冒险的蛇会终止竞争,那么第一条冒险的蛇可以安全地冒险;否则第一条蛇直接结束竞争。
于是我们只需要在模拟的时候维护 $k$ 和第一次冒险时已经有多少条蛇被吃掉即可。
还有个问题是怎么 $O(n)$ 模拟。注意到,对于最大蛇可以安全吃掉最小蛇的情况一,有 $x-z\ge w-y$,即新产生的蛇的体力值单调不增。可以想到用两个队列分别维护原有的蛇和新产生的蛇。
情况一可以满足两个队列都单调,但是情况二能否单调?实际上如果带编号考虑的确不单调,但是我们发现情况二不需要考虑编号——
设第一次冒险时的体力值从小到大排列为 $\{h_1,h_2,\dots,h_n\}$,则有 $h_n-h_1<h_2$。暴力模拟竞争过程
- $h_n\to h_n-h_1$ 一定变为最小蛇,则 $\{h_n-h_1,h_2,\dots,h_{n-1}\}$;
- $h_{n-1}\to h_{n-1}-h_n+h_1$,$(h_{n-1}-h_n)+h_1\le h_1\le h_2$,在数值上一定等于最小蛇;
- $h_{n-2}\to (h_{n-2}-h_{n-1})+(h_n-h_1)<h_2$,一定是最小蛇;
- ……
于是在数值的意义上是单调的,因此可以用队列维护。
# 源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
| #include<queue> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std;
typedef pair<int,int> pii; const int N=1e6+10,INF=0x3f3f3f3f;
int n,cas; int num[N];
struct DEQUE{ pii ary[N<<1]; int le,ri; DEQUE(){clear();} void clear(){le=N+1,ri=N;} bool empty(){return le>ri;} void push_back(pii x){ary[++ri]=x;} void pop_back(){ri--;} void push_front(pii x){ary[--le]=x;} void pop_front(){le++;} pii front(){return ary[le];} pii back(){return ary[ri];} };
DEQUE org,mdi;
inline int Read(int &r){ int b=1,c=getchar();r=0; while(c<'0' || '9'<c) b=c=='-'? -1:b,c=getchar(); while('0'<=c && c<='9') r=(r<<1)+(r<<3)+(c^'0'),c=getchar(); return r*=b; } pii PopFront(){ pii ret; bool tmp=false; if(org.empty()) tmp=false; else if(mdi.empty()) tmp=true; else tmp=org.front()<mdi.front(); if(tmp) ret=org.front(),org.pop_front(); else ret=mdi.front(),mdi.pop_front(); return ret; } pii PopBack(){ pii ret; bool tmp=false; if(org.empty()) tmp=false; else if(mdi.empty()) tmp=true; else tmp=org.back()>mdi.back(); if(tmp) ret=org.back(),org.pop_back(); else ret=mdi.back(),mdi.pop_back(); return ret; } pii Front(){ if(org.empty()) return mdi.front(); else if(mdi.empty()) return org.front(); else return min(mdi.front(),org.front()); } pii Back(){ if(org.empty()) return mdi.back(); else if(mdi.empty()) return org.back(); else return max(mdi.back(),org.back()); } bool Empty(){return org.empty() && mdi.empty();} pii operator -(pii mx,pii mn){return make_pair(mx.first-mn.first,mx.second);}
void Solve(){ int tim=0,rem=-1,ris=0; while((++tim)<=n-1){ if(org.empty()) swap(org,mdi); pii mn=PopFront(),mx=PopBack(),now=mx-mn; if(Empty() || now>Front()){ if(~rem){ printf("%d\n",n-rem+(ris&1)); return; } } else{ if(rem==-1) rem=tim; ris++; } mdi.push_front(now); } printf("1\n"); } int main(){ Read(cas); bool fir=true; while(cas--){ if(fir){ Read(n); for(int i=1;i<=n;i++) Read(num[i]); fir=false; } else{ int tmp;Read(tmp); for(int i=1,p,q;i<=tmp;i++) Read(p),Read(q),num[p]=q; } org.clear(),mdi.clear(); for(int i=1;i<=n;i++) org.push_back(make_pair(num[i],i)); Solve(); } return 0; }
|
THE END
Thanks for reading!
> Linked 听闻-网易云