[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;
	}
}

启示

  1. 在没有思路的时候,一定要把思路放简单了去打暴力,即使它甚至没有分数。
  2. 构造题通查会与基础的算法结合起来,如分治、欧拉图等。

代码

#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();
}

posted @ 2024-11-13 13:43  Add_Catalyst  阅读(69)  评论(0)    收藏  举报