P8172

太良心了,搬题人!联考开了 1.5s 随便过。

这个复杂度不太对,需要卡常。

upd:循环节长度的结论是错的,所以跟他爆了。

思路

为方便,交换一下定义,是一个 行 列的矩阵,列从 开始编号,。

考虑状压 ,设 表示已经填满了 行,第 行填了状态 。从 转移到 时,设 表示现在要填 ,第 行 的部分和第 行 部分并起来的状态为 。发现如果 和 都为空, 的三角无用,所以没有后效性。

发现 很像矩阵优化 dp。对于每个 都 算能否转移到每个 。然后就是有 个第 行点不能是状态 的限制,形如要求做完第 行的状态 逻辑与 为 ,然后 再或上 继续做,分成 段查询。能做 ,跟暴力同分。

只需要知道可行性用 bitset 优化,预处理出 的幂次,然后查询时用 bitset 维护向量。能做 ,跟暴力同分。

再观察,猜测经过若干个空的行之后会出现长为 的循环节。发现一个状态 经过 个空行之后就能到达所有经过 个空行能到达的状态。所以 ,那只需要维护前 个转移矩阵的 的幂次。能做 ,跟暴力同分。

卡常方面,有值的状态不多,bitset 自带的 _Find_first() 和 _Find_next(i) 函数可以快速跳一个 bitset 为 的位置,常熟大大减小。然后发现瓶颈在于矩阵乘法,不妨只维护 的转移矩阵,稍微增大查询时向量乘法次数。就赢了!

复杂度 。

code

int n,m,k,l;
map<int,int> mp;
const int maxm=1<<13;
#define vec bitset<maxm>
struct mat{
	vec e[maxm];
	mat(){
		for(int i=0;i<l;i++)e[i].reset();
	}
	mat operator*(const mat&tmp)const{
		mat res;
		for(int i=0;i<l;i++){
			for(int k=e[i]._Find_first();k<l;k=e[i]._Find_next(k)){
				res.e[i]|=tmp.e[k];
			}
		}
		return res;
	}
}pw[30];
vec operator*(vec e,mat tmp){
	vec res;res.reset();
	for(int k=e._Find_first();k<l;k=e._Find_next(k)){
		res|=tmp.e[k];
	}
	return res;
}
vec res;
bitset<maxm> f[maxn+1];
vector<pii> que;
void work(){
	n=read();m=read();k=read();
	for(int i=1;i<=k;i++){
		int u=read(),v=read();
		mp[v]|=(1<<u-1);
	}
	if(n<=6)return sub1::sovle();
	l=1<<n;
	for(int s=0;s<l;s++){
		for(int i=0;i<=n;i++)f[i].reset();f[0][s]=1;
		for(int i=0;i<n;i++){
			for(int t=f[i]._Find_first();t<l;t=f[i]._Find_next(t)){
				if(t&(1<<i)){
					f[i+1][t^(1<<i)]=1;
				}
				else{
					if(i+1<n&&!(t&(1<<i+1))){
						f[i+2][t|(1<<i)]=1;
						f[i+2][t|(1<<i+1)]=1;
					}
					if(i&&!(t&(1<<i-1))){
						f[i+1][t|(1<<i)|(1<<i-1)]=1;
					}
					if(i+1<n&&(t&(1<<i+1))){
						f[i+2][t|(1<<i)]=1;
					}
				}
			}
		}
		pw[0].e[s]=f[n];
	}
	for(auto [p,s]:mp)que.pb({p,s});
	que.pb({m+1,l-1});
	int mx=0,B=6,k=29;
	for(int i=0;i<que.size()-1;i++)mx=max(mx,que[i+1].fi-que[i].fi);
	mx=min(mx,B);k=2;
	for(int i=1;i<=k;i++)pw[i]=pw[i-1]*pw[i-1];
	// return ;
	int lst=0;res[0]=1;
	for(auto[p,s]:que){
		int len=p-1-lst;
		len=min(len,len%B+B);
		while(len>=(1<<k))res=res*pw[k],len-=1<<k;
		for(int i=k;~i;i--)if(len&(1<<i)){
			res=res*pw[i];
		}
		for(int t=res._Find_first();t<l;t=res._Find_next(t)){
			if(s&t)res[t]=0;
		}
		if(!res.count()){puts("NO");return ;}
		for(int t=res._Find_first();t<l;t=res._Find_next(t)){
			res[t]=0,res[s|t]=1;
		}
		lst=p-1;
	}
	puts("YES");
}

upd

关于正解,其实也很脑瘫,你把两个有限制的行间的距离缩为 以内,总行数 ,就不用矩阵快速幂了,直接做原 dp 即可。复杂度 。