【Atcoder Grand Contest 043】【AGC043】E - Topology(构造)

传送门


题解:

首先简单说一下题意,我因为英文不好还理解错了半天。。。

给出 n n n 个点,其中第 i i i 个点为 ( i + 0.5 , 0.5 ) (i+0.5,0.5) (i+0.5,0.5),同时给出所有 2 n 2^n 2n 个点集的合法性,请你构造一条闭合曲线,使得该合法的点集合法,不合法的点集不合法。

一个点集合法当且仅当你能够移动该曲线使得其整体处于 x x x 轴下方,且移动过程中其不与该点集相交。

好,注意这里的移动,英语原文是 “move”,日语原文是 “連続的に動”。然而实际上你去看一下 Notes 就会明白,这里的移动不是平移(translate),而是类似一个绳圈那样的移动,就是说它在构造的时候可以重叠,在移动的时候可以像绳子一样扭来扭去。。。

这个结论可能在一些诡异的小学奥数里面出现过,我总感觉我有那么点印象,又记不太清楚。

过每个点作一条关于 x x x 轴的垂线,从绳子的最左端开始,沿绳子指定一个方向行进,穿过第 i i i 条线上方的时候记录一个 u i u_i ui,穿过第 i i i 条线下方的时候记录一个 d i d_i di,这样游走得到一个字符串,不断地在字符串中选择两个相邻且相同的字符删掉,如果能清空,则该点集合法,否则非法。

证明很显然,每次抽掉两个相同字符意味着我们可以把绳圈的一部分拉过来,能清空意味能完全分离,否则不能。

于是思路非常显然了,先判断合法性,一个合法点集的所有子集都应该合法,一个非法点集的所有超集都应该非法,特别地,空集必须合法。

然后现在考虑怎么构造,一个显然的思路是对于所有极小非法集合 S S S,构造一个经过 ( 0 , 0 ) (0,0) (0,0) 的绳圈,使得它在 S S S 非法,在 S S S 的所有真子集合法,然后将这样构造出的所有绳圈在 ( 0 , 0 ) (0,0) (0,0) 首尾连接。

考虑如下递归构造,当前处于所有点最左侧。

  1. 若只有一个点,绕个圈即可,假设顺时针绕,得到字符串 u 1 d 1 u_1d_1 u1d1
  2. 否则穿过第一个点的上方,对后面的所有点进行构造,得到字符串 S S S,从第一个点的上方穿回来,
  3. 再从第一个点的下方穿过去,按照 S − 1 S^{-1} S1 对后面的点集进行游走,再从下方穿回来。
  4. 得到字符串为 u 1 S u 1 d 1 S − 1 d 1 u_1Su_1d_1S^{-1}d_1 u1Su1d1S1d1

其中 S − 1 S^{-1} S1 S S S u i u_i ui 变为 d i d_i di d i d_i di 变为 u i u_i ui 的结果,其实和 reverse 的结果是一样的。

显然,由于没有任何两个字符是相同的, S S S 非法,考虑证明 S S S 的任意真子集合法。

证明其实很明显,如果某个 S S S 可以被清空,那么剩下的所有部分显然可以全部清空。
如果删掉最后一个点,显然满足条件,否则,删掉任何一个点会导致 S S S S − 1 S^{-1} S1 首尾连接,由于互为 r e v e r s e reverse reverse 显然可以清空。

长度的限制根据官方题解所说似乎很宽裕,随便写写即可。

具体实现的时候字符串可以只维护一半,详情见代码


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

using std::cerr;
using std::cout;
using pii=std::pair<int,int>;
#define fi first
#define se second

std::string inv(std::string s){
	std::string res="";
	std::reverse(s.begin(),s.end());
	for(char c:s)
		if(isupper(c))res+=c-'A'+'a';
		else res+=c-'a'+'A';
	return res;
}

int n;
std::string A;

void Main(){
	std::cin>>n>>A;
	if(A[0]=='0'){
		puts("Impossible");
		return ;
	}for(int re i=0;i<(1<<n);++i)
		if(A[i]=='1')for(int re j=i;j;j=i&(j-1))
			if(A[j]=='0'){
				puts("Impossible");
				return ;
			}
	std::vector<pii> p({{0,0}});
	for(int re s=0;s<(1<<n);++s)if(A[s]=='0'){
		bool flag=true;
		for(int re t=s&(s-1);t&&flag;t=s&(t-1))
			if(A[t]=='0')flag=false;
		if(!flag)continue;
		std::string q;
		for(int re i=0;i<n;++i)
			if(s&(1<<i)){
				if(q.empty())q+='a'+i;
				else q=(char)('a'+i)+q+(char)('A'+i)+inv(q);
			}
		int nw=0;
		for(char c:q){
			int i,dir;
			if(isupper(c))
				i=c-'A',dir=0;
			else 
				i=c-'a',dir=1;
			for(int j=nw+1;j<=i;++j)
				p.push_back({j,0});
			for(int j=nw-1;j>=i;--j)
				p.push_back({j,0});
			nw=i;
			if(dir){
				p.push_back({i,1});
				p.push_back({i+1,1});
				p.push_back({i+1,0});
				p.push_back({i,0});
			}else {
				p.push_back({i+1,0});
				p.push_back({i+1,1});
				p.push_back({i,1});
				p.push_back({i,0});
			}
		}
		for(int j=nw-1;~j;--j)
			p.push_back({j,0});
	}
	cout<<"Possible\n"<<(p.size()-1)<<"\n";
	for(auto t:p)cout<<t.fi<<" "<<t.se<<"\n";
}

inline void file(){
#ifdef zxyoi
	freopen("E.in","r",stdin);
#endif
}signed main(){file();Main();return 0;}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值