题解:P15362 [CTS 2026] 谜题 II
wangziyue_AK · · 题解
题意:
给定一张
观察与基本结构:
首先普通 DAG 难以直接计数,考虑把限制容斥掉一部分,此时我们希望得到一些树状结构。容斥一条边经典的就是没有边减去反向边,考虑我们使用这种操作来转化。
众所周知
考虑
基本形态一:对于割边没有这条边时没有贡献,模
基本形态二:对于形如
基本形态三:成环一定无解,所以容斥后出现环的情况可以直接忽略。
上手容斥:
我们容易计算拓扑序的形态是一棵固定方向(根向或叶向)的树。以下我们尝试把这张图中所有的横向边向右指,每个点有且仅有一条出边,此时构成一棵根向树。
我们尝试一个一个
使用上述三种基本形态加上基本的容斥思想可以完成特殊性质 B。整理后结论为:若
对于相邻两列纵向边方向相同的情况,考虑将左侧边变为删去加反向,删去后本方格横向边均为割边,可以直接指向右,反向即为特殊性质 B 的转移。此时记录上下两个从左侧接来的子树大小即可,因为有贡献的情况左边的点一定全部能联通到右边,所以只要记上方点接的子树大小即可。直接转移可以做到
优化:
考虑什么情况下一列上下两个点接的子树大小都不为
此时复杂度是
代码:
//#include "puzzle.h"
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned int uint;
typedef pair<int,int> pii;
#define fi first
#define se second
typedef double db;
#define pb push_back
#define eb emplace_back
#define bcnt __builtin_popcount
const int inf=1e9+7;
const int N=6e5+5;
const int mod=998244353;
//const int mod=1e9+7;
int n,m,t,pre[35][N];
uint a[N],b[N],c[N],dp[N][2],S;
char s[N];
uint sol(){
dp[1][0]=S;
for(int o=0;o<t;o++){
pre[o][1]=1;uint x=(1u<<o);
for(int i=1;i<m;i++) pre[o][i+1]=((b[i+1]&x)==(b[i]&x)?pre[o][i]:i+1);
}
// for(int o=0;o<t;o++){
// puts("pre:");
// for(int i=1;i<=m;i++) printf("%d ",pre[o][i]);
// puts("");
// }
for(int i=1;i<m;i++){
int s=i*2-1;
uint lim=(b[i+1]^a[i]);
for(int j=s;j;j=((j-1)&s)){
int x=j-1,y=(s^j);
if(x>=y){
int p=((x-y)>>1)+1;
dp[i+1][1]^=(dp[p][0]&lim);
}else{
int p=((y-x)>>1)+1;
dp[i+1][1]^=(dp[p][1]&lim);
}
}
lim=(S^(b[i+1]^c[i]));
for(int j=s;j;j=((j-1)&s)){
int y=j-1,x=(s^j);
if(x>=y){
int p=((x-y)>>1)+1;
dp[i+1][0]^=(dp[p][0]&lim);
}else{
int p=((y-x)>>1)+1;
dp[i+1][0]^=(dp[p][1]&lim);
}
}
for(int o=0;o<t;o++){
uint x=(1u<<o);x^=S;
for(int j=pre[o][i];j<pre[o][i+1];j++) dp[j][0]&=x,dp[j][1]&=x;
}
}
//for(int i=1;i<=m;i++) printf("xy:%d %d\n",dp[i][0],dp[i][1]);
uint ans=0;
for(int i=m;i;i--){
for(int o=0;o<2;o++){
for(int j=0;j<t;j++){
int x=(m-i)+(o^1)*(i*2-2)+((b[m]>>j)&1),y=(m*2-1-x);
if(!(x&y)) ans^=(dp[i][o]&(1u<<j));
}
}
}
//printf("ans:%d\n",ans);
for(int i=1;i<=m;i++) dp[i][0]=dp[i][1]=0;
return ans;
}
unsigned puzzle(int T,int k,vector<unsigned> A,vector<unsigned> B,vector<unsigned> C){
unsigned int ans=0;
n=k,m=(1<<k),t=T;
for(int j=0;j<t;j++) S|=(1u<<j);
//printf("S:%u\n",S);
for(int j=0;j<m-1;j++) a[j+1]=A[j];
for(int j=0;j<m;j++) b[j+1]=B[j];
for(int j=0;j<m-1;j++) c[j+1]=C[j];
ans=sol();
return ans;
}