P8172
太良心了,搬题人!联考开了 1.5s 随便过。
这个复杂度不太对,需要卡常。
思路
为方便,交换一下定义,是一个 行 列的矩阵,列从 开始编号,。
考虑状压 ,设 表示已经填满了 行,第 行填了状态 。从 转移到 时,设 表示现在要填 ,第 行 的部分和第 行 部分并起来的状态为 。发现如果 和 都为空, 的三角无用,所以没有后效性。
发现 很像矩阵优化 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 即可。复杂度 。