[NOIP2020] 移球游戏 题解
[NOIP2020] 移球游戏 题解
知识点
分治,构造。
分析
\(10\%\)
我们把操作、思路化到最简:为每一个栈钦定一个颜色,如果里面有不一样的,就把它们一个一个提到栈顶,然后最后就可以很简单地交换。
次数等级与时间复杂度:\(O(nm^2)\)。
代码
namespace Subtask_1 {
bool Check() {
return n<=2;
}
int Cmain() {
auto Float=[&](Stack &A,Stack &B,Stack &E,int col) -> void {
auto check=[&](Stack &A,int col) -> int {
bool flag(0);
DOR(i,A.size(),1) {
if(A[i]!=col)flag=1;
else if(flag)return i;
}
return 0;
};
for(int u(check(A,col)); u; u=check(A,col)) {
move(B,E);
while(A.size()>u)move(A,E);
move(A,B);
while(E.size()>1)move(E,A);
move(B,A),move(E,B);
}
};
auto Move=[&](Stack &A,Stack &B,Stack &E,int col) -> void {
Float(A,B,E,col),Float(B,A,E,col^3);
while(!A.empty()&&A.top()==col)move(A,E);
while(!B.empty()&&B.top()!=col)move(B,A);
while(!E.empty())move(E,B);
};
Move(st[1],st[2],st[3],1),Print();
return 0;
}
}
\(40\%\)
我们把柱子的情况从 2 个推广到多个:枚举两个柱子进行上述操作。
次数等级与时间复杂度:\(O(n^2m^2)\)。
代码
namespace Subtask_2 {
bool Check() {
return n<=10||m<=85;
}
int Cmain() {
auto Float=[&](Stack &A,Stack &B,Stack &E,auto &&cmp) -> void {
auto check=[&](Stack &A) -> int {
bool flag(0);
DOR(i,A.size(),1) {
if(!cmp(A[i]))flag=1;
else if(flag)return i;
}
return 0;
};
for(int u(check(A)); u; u=check(A)) {
move(B,E);
while(A.size()>u)move(A,E);
move(A,B);
while(E.size()>1)move(E,A);
move(B,A),move(E,B);
}
};
auto Move=[&](Stack &A,Stack &B,Stack &E,auto &&cmp) -> void {
Float(A,B,E,cmp);
while(!B.empty()&&!cmp(B.top()))move(B,E);
while(!A.empty()&&cmp(A.top()))move(A,B);
while(!E.empty())move(E,A.full()?B:A);
};
FOR(i,1,n-1) {
Float(st[i],st[i+1],st[n+1],[&](int a) -> bool {
return a!=i;
});
FOR(j,i+1,n)Move(st[j],st[i],st[n+1],[&](int a) -> bool {
return a==i;
});
}
Print();
return 0;
}
}
\(80\%\)
我们把可以一次性把不同颜色全部提出,操作优化成单次 \(O(m)\) 的。
次数等级时间复杂度:\(O(n^2m)\)。
代码
namespace Subtask_3 {
bool Check() {
return m<=300;
}
int Cmain() {
auto Float=[&](Stack &A,Stack &B,Stack &E,auto &&cmp) -> void {
bool flag(0);
int cnt(0),low(m);
DOR(i,m,1)if(cmp(A[i])) {
if(i<m&&!cmp(A[i+1]))flag=1;
++cnt,low=i;
}
if(!flag)return;
FOR(i,1,cnt)move(B,E);
while(A.size()>=low)move(A,cmp(A.top())?B:E);
while(E.size()>cnt)move(E,A);
FOR(i,1,cnt)move(B,A);
while(!E.empty())move(E,B);
};
FOR(i,1,n-1) {
auto cmp_true=[&](int a) -> bool {
return a==i;
};
auto cmp_false=[&](int a) -> bool {
return a!=i;
};
FOR(j,i+1,n)Float(st[j],st[i],st[n+1],cmp_true);
Float(st[i],st[i+1],st[n+1],cmp_false);
while(!st[i].empty()&&cmp_false(st[i].top()))move(st[i],st[n+1]);
FOR(j,i+1,n) {
while(!st[j].empty()&&cmp_true(st[j].top()))move(st[j],st[i]);
while(!st[j].full())move(st[n+1],st[j]);
}
}
Print();
return 0;
}
}
\(100\%\)
其实我们所有的上述操作都是基于二元性,即是一种颜色或不是,那么根据这个性质我们会想到快速排序,即用分治法来进行优化,只不过我们这里可以严格 \(\log_2{n}\) 的分治,毕竟它的值域是连续的。
在分治的时候可以进行尺取,把不满足条件少的先填满,然后到下一个。
用主定理算一下次数等级和复杂度:
\[\begin{aligned}
T(n) & = 2T(\frac{n}{2}) + nm \\
T(n) & = nm\log_2{n} \\
\end{aligned}
\]
次数等级时间复杂度:\(O(nm\log_2{n})\),算一下非常足够。
代码
namespace Subtask {
void Sep(int l,int r) {
if(l==r)return;
const int mid((l+r)>>1);
auto cmp_true=[&](int a) -> bool {
return a<=mid;
};
auto cmp_false=[&](int a) -> bool {
return a>mid;
};
auto Float=[&](Stack &A,Stack &B,Stack &E,auto &&cmp) -> void {
bool flag(0);
int cnt(0),low(m);
DOR(i,m,1)if(cmp(A[i])) {
if(i<m&&!cmp(A[i+1]))flag=1;
++cnt,low=i;
}
if(!flag)return;
FOR(i,1,cnt)move(B,E);
while(A.size()>=low)move(A,cmp(A.top())?B:E);
while(E.size()>cnt)move(E,A);
FOR(i,1,cnt)move(B,A);
while(!E.empty())move(E,B);
};
auto Count=[&](Stack &A,Stack &B,auto &&cmp) -> int {
int cnt(0);
FOR(i,1,m)cnt+=cmp(A[i])+cmp(B[i]);
return cnt<m?-1:(cnt==m?0:1);
};
auto Move=[&](Stack &A,Stack &B,Stack &E,auto &&cmp) -> void {
while(!B.empty()&&cmp(B.top()))move(B,E);
while(!A.empty()&&!cmp(A.top()))move(A,B);
while(!E.empty())move(E,A.full()?B:A);
};
int L(l),R(mid+1);
while(L<=mid||R<=r){
Float(st[L],st[R],st[n+1],cmp_false),Float(st[R],st[L],st[n+1],cmp_true);
int sit(Count(st[L],st[R],cmp_true));
sit>=0?Move(st[L],st[R],st[n+1],cmp_true):Move(st[R],st[L],st[n+1],cmp_false);
if(sit>=0)++L;
if(sit<=0)++R;
}
Sep(l,mid),Sep(mid+1,r);
}
int Cmain() {
Sep(1,n),Print();
return 0;
}
}
启示
- 在没有思路的时候,一定要把思路放简单了去打暴力,即使它甚至没有分数。
- 构造题通查会与基础的算法结合起来,如分治、欧拉图等。
代码
#define Plus_Cat "ball"
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define Fi first
#define Se second
#define ll long long
#define Pii pair<int,int>
#define RCL(a,b,c,d) memset(a,b,sizeof(c)*(d))
#define FOR(i,a,b) for(int i(a);i<=(int)(b);++i)
#define DOR(i,a,b) for(int i(a);i>=(int)(b);--i)
#define tomin(a,...) ((a)=min({(a),__VA_ARGS__}))
#define tomax(a,...) ((a)=max({(a),__VA_ARGS__}))
#define EDGE(g,i,x,y) for(int i(g.h[x]),y(g[i].v);~i;y=g[i=g[i].nxt].v)
using namespace std;
constexpr int N(50+10),M(400+10),K(8.2e5+10);
int n,m,ans;
Pii Ans[K];
struct Stack {
int idx;
int st[M];
int &operator[](int i) {
return st[i];
}
bool empty() {
return !st[0];
}
bool full() {
return st[0]==m;
}
int size() {
return st[0];
}
int top() {
return st[st[0]];
}
void push(int x) {
st[++st[0]]=x;
}
void pop() {
return --st[0],void();
}
void Build() {
st[0]=m;
FOR(i,1,m)scanf("%d",&st[i]);
}
void Print() {
cerr<<"idx:"<<idx<<endl;
FOR(i,1,st[0])cerr<<st[i]<<" ";
cerr<<endl;
}
} st[N];
bool move(Stack &A,Stack &B) {
if(A.empty()||B.full())return 0;
return Ans[++ans]=Pii(A.idx,B.idx),B.push(A.top()),A.pop(),1;
}
void Print() {
printf("%d\n",ans);
FOR(i,1,ans)printf("%d %d\n",Ans[i].Fi,Ans[i].Se);
}
namespace Subtask_1 {
bool Check() {
return n<=2;
}
int Cmain() {
auto Float=[&](Stack &A,Stack &B,Stack &E,int col) -> void {
auto check=[&](Stack &A,int col) -> int {
bool flag(0);
DOR(i,A.size(),1) {
if(A[i]!=col)flag=1;
else if(flag)return i;
}
return 0;
};
for(int u(check(A,col)); u; u=check(A,col)) {
move(B,E);
while(A.size()>u)move(A,E);
move(A,B);
while(E.size()>1)move(E,A);
move(B,A),move(E,B);
}
};
auto Move=[&](Stack &A,Stack &B,Stack &E,int col) -> void {
Float(A,B,E,col),Float(B,A,E,col^3);
while(!A.empty()&&A.top()==col)move(A,E);
while(!B.empty()&&B.top()!=col)move(B,A);
while(!E.empty())move(E,B);
};
Move(st[1],st[2],st[3],1),Print();
return 0;
}
}
namespace Subtask_2 {
bool Check() {
return n<=10||m<=85;
}
int Cmain() {
auto Float=[&](Stack &A,Stack &B,Stack &E,auto &&cmp) -> void {
auto check=[&](Stack &A) -> int {
bool flag(0);
DOR(i,A.size(),1) {
if(!cmp(A[i]))flag=1;
else if(flag)return i;
}
return 0;
};
for(int u(check(A)); u; u=check(A)) {
move(B,E);
while(A.size()>u)move(A,E);
move(A,B);
while(E.size()>1)move(E,A);
move(B,A),move(E,B);
}
};
auto Move=[&](Stack &A,Stack &B,Stack &E,auto &&cmp) -> void {
Float(A,B,E,cmp);
while(!B.empty()&&!cmp(B.top()))move(B,E);
while(!A.empty()&&cmp(A.top()))move(A,B);
while(!E.empty())move(E,A.full()?B:A);
};
FOR(i,1,n-1) {
Float(st[i],st[i+1],st[n+1],[&](int a) -> bool {
return a!=i;
});
FOR(j,i+1,n)Move(st[j],st[i],st[n+1],[&](int a) -> bool {
return a==i;
});
}
Print();
return 0;
}
}
namespace Subtask_3 {
bool Check() {
return m<=300;
}
int Cmain() {
auto Float=[&](Stack &A,Stack &B,Stack &E,auto &&cmp) -> void {
bool flag(0);
int cnt(0),low(m);
DOR(i,m,1)if(cmp(A[i])) {
if(i<m&&!cmp(A[i+1]))flag=1;
++cnt,low=i;
}
if(!flag)return;
FOR(i,1,cnt)move(B,E);
while(A.size()>=low)move(A,cmp(A.top())?B:E);
while(E.size()>cnt)move(E,A);
FOR(i,1,cnt)move(B,A);
while(!E.empty())move(E,B);
};
FOR(i,1,n-1) {
auto cmp_true=[&](int a) -> bool {
return a==i;
};
auto cmp_false=[&](int a) -> bool {
return a!=i;
};
FOR(j,i+1,n)Float(st[j],st[i],st[n+1],cmp_true);
Float(st[i],st[i+1],st[n+1],cmp_false);
while(!st[i].empty()&&cmp_false(st[i].top()))move(st[i],st[n+1]);
FOR(j,i+1,n) {
while(!st[j].empty()&&cmp_true(st[j].top()))move(st[j],st[i]);
while(!st[j].full())move(st[n+1],st[j]);
}
}
Print();
return 0;
}
}
namespace Subtask {
void Sep(int l,int r) {
if(l==r)return;
const int mid((l+r)>>1);
auto cmp_true=[&](int a) -> bool {
return a<=mid;
};
auto cmp_false=[&](int a) -> bool {
return a>mid;
};
auto Float=[&](Stack &A,Stack &B,Stack &E,auto &&cmp) -> void {
bool flag(0);
int cnt(0),low(m);
DOR(i,m,1)if(cmp(A[i])) {
if(i<m&&!cmp(A[i+1]))flag=1;
++cnt,low=i;
}
if(!flag)return;
FOR(i,1,cnt)move(B,E);
while(A.size()>=low)move(A,cmp(A.top())?B:E);
while(E.size()>cnt)move(E,A);
FOR(i,1,cnt)move(B,A);
while(!E.empty())move(E,B);
};
auto Count=[&](Stack &A,Stack &B,auto &&cmp) -> int {
int cnt(0);
FOR(i,1,m)cnt+=cmp(A[i])+cmp(B[i]);
return cnt<m?-1:(cnt==m?0:1);
};
auto Move=[&](Stack &A,Stack &B,Stack &E,auto &&cmp) -> void {
while(!B.empty()&&cmp(B.top()))move(B,E);
while(!A.empty()&&!cmp(A.top()))move(A,B);
while(!E.empty())move(E,A.full()?B:A);
};
int L(l),R(mid+1);
while(L<=mid||R<=r){
Float(st[L],st[R],st[n+1],cmp_false),Float(st[R],st[L],st[n+1],cmp_true);
int sit(Count(st[L],st[R],cmp_true));
sit>=0?Move(st[L],st[R],st[n+1],cmp_true):Move(st[R],st[L],st[n+1],cmp_false);
if(sit>=0)++L;
if(sit<=0)++R;
}
Sep(l,mid),Sep(mid+1,r);
}
int Cmain() {
Sep(1,n),Print();
return 0;
}
}
int main() {
#ifdef Plus_Cat
freopen(Plus_Cat ".in","r",stdin),freopen(Plus_Cat ".out","w",stdout);
#endif
scanf("%d%d",&n,&m);
FOR(i,1,n+1)st[i].idx=i;
FOR(i,1,n)st[i].Build();
// if(Subtask_1::Check())return Subtask_1::Cmain();
// if(Subtask_2::Check())return Subtask_2::Cmain();
// if(Subtask_3::Check())return Subtask_3::Cmain();
return Subtask::Cmain();
}

浙公网安备 33010602011771号