题解 P6734 【「Wdsr-2」阴阳玉】

囧仙

2020-07-18 14:20:29

Solution

## 题目大意 有两个黑点,围成了一个圈。你可以通过以下操作扩充这个圈。 - 在两个点之间加入一个白点,并翻转这两个点的颜色。 - 删除一个白点,同时翻转它相邻的两个点的颜色。 翻转的含义是,将白色的点变成黑色,将黑色的点变成白色。 每次操作时,都必须保证**操作后点的个数不小于 $2$** 。你可以进行无数次操作。现在经过若干次操作后,得到了一个大小为 $n$ 的圈。接着**任取**某个位置断开,会形成一个链。 同时,可能有 $m$ 个约束条件。第 $i$ 个约束条件 $(p_i,c_i)$ ,表示这条链的第 $p_i$ 个点应该是 $c_i$ 颜色的。其中,$c_i=0$ 表示该点为黑色, $c_i=1$ 表示该点为白色。 询问最多可以得到多少个满足条件的不同的链。由于可能结果太大,你只需要输出它对 $998244353$ 取模的结果。 两个链不同,当且仅当存在一个位置的点颜色不同。 ## 题解 ### $\textbf{Subtask 1}\ n,m\le 16$ 直接暴力操作,并判断是否符合条件即可。复杂度$\mathcal O(2^n\times n)$ 。 ### $\textbf{Subtask 2}\ n\le 10^6,m\le 5\times 10^3$ 首先,让我们证明一个小结论:**黑点的个数总为偶数个**。 这个结论非常简单。考虑每次加点操作,如果左右两个点同色,那么黑点个数的变化量为$\pm2$;如果异色,那么相当于交换了两点,数量不变。由于初始时候,黑点数量为 $0$ ,因此任何时候黑点的数量都应该是偶数。 ![pic1.JPG](https://i.loli.net/2020/07/16/zP1nierojSaHZkN.jpg) 不妨设当前一共有 $p$ 个黑点,则这些黑点将圆弧划分为了 $p$ 段。我们对其进行染色。(如图 $1$ ) 我们对圆弧进行染色:白色、黑色、白色……由于只有偶数段圆弧,所以每两个弧之间的颜色不同。 我们将黑色弧上的白点赋值为 $+1$ ,白色弧上的白点赋值为 $-1$ (如图 $2 $)。统计所有白点的权值和 $S$ 。由于黑白弧可以交换,所以白点的权值和亦能为 $-S$ 。下面讨论两种操作对 $S$ 的影响。 - 当加入点的两侧都是黑点时,黑点变为白点。由于该操作不会影响其他点的权值,且这三个点权值相同。于是 $S\gets S\pm 3$ 。 - 当加入的点两侧都是白点时,白点变为黑点。显然,新加入的白点的权值与原先两点的权值相反。因此,$S\gets S\pm 3$。 - 当加入的点两侧的点的显色相异时,不妨设其中白点权值为 $a$ 。插入后,原先两侧的点位置交换,原来的白点交换后权值变为了 $-a$。新的点权值与该白点相同,亦为 $-a$ 。于是,$S\gets S-3\times a$。 对于删点操作,由于其是加点操作的互逆操作,因此 $S$ 的变化量也为 $3$ 的倍数。可以这样简要证明: 假设删去一个白点后,重新在该位置重新加入白点,此时 $S$ 不变;而加入白点,$S$ 的变化为 $3$ 的倍数,因此删点操作, $S$ 的变化量亦为 $3$ 的倍数。 初始时,$S=\pm 2$ 。因此终止状态的权值和 $S'$ 必有 : $$S'\not \equiv 0 \pmod 3$$ 在开头,我们已经证明黑点的个数必为偶数。下证任何一个符合这两个条件的状态,都能由初始状态转移而来。 由于 $S' \not \equiv 0 \pmod 3$ ,因此无论何时都有至少一个白点。我们不断删去白点,直到只剩下两个点,其中也必有白点。同时,由于黑点个数为偶数,所以只有可能有 $0$ 个黑点,即初始状态。因此,从任何满足这两个条件的状态都能仅通过删点回到初始状态。又由于加点操作是删点的逆操作,因此我们只需要按照删点的顺序倒过来操作,就一定能获得该状态。 于是,我们可以用 $\rm dp$ 计算出方案总数。 由于最终我们会给圆环断链,因此就相当于提前给大小为 $n$ 的圆环的每个点编了号。不妨任取一个点开始,编为 $1,2,3\cdots$ 我们设计状态 $dp_{i,j,k}$,表示前 $i$ 个点,黑点个数$\mod 2=j$ 且 $S\mod 3=k$ 的方案总数。于是我们能够得到转移方程: $$dp_{i,j,k}=\begin{cases} dp_{i-1,0,(k+2)\mod 3}+dp_{i-1,1,k} & j=0\cr dp_{i-1,1,(k+1)\mod 3}+dp_{i-1,0,k} & j=1\end{cases}$$ 由于黑白可以交换,所以我们可以钦定第一个点处在白弧上。每讨论一个黑点,黑白弧的情况就会交换。初始时,$dp_{1,0,1}=dp_{1,1,0}=1$,其他的 $i=1$ 的情况都为 $0$ 。答案为 $dp_{n,0,1}+dp_{n,0,2}$。 关于限制条件的问题,我们只需要计算到限制点的时候,舍去不合法的转移方法。如限制点 $a$ 为白点,我们就要舍弃点 $a-1$ 转移到黑点的情况;限制点为白点同理可推。 ### $\textbf{Subtask 3}\ n\le 10^9,m=0$ 如果你已经写出了上面两个$\rm Subtask$的任意一种,经过简单打表,你会发现,设 $f(x)$ 为 $n=x,m=0$ 时的结果,有: $$f(x)=\begin{cases}1 & x=2 \cr f(x-1)\times 2+1 & x\text{为奇数} \cr f(x-1)\times 2-1 &x\text{为偶数}\end{cases}$$ 至于这个式子怎么计算,最简单的方法,就是直接矩阵乘法……当然,你也可以考虑分治。 首先我们能够发现, $$\begin{aligned}f(2k)&=f(2k-1)\times 2-1\cr&=(f(2(k-1))\times 2+1)\times 2-1\cr&=f(2(k-1))\times 4+1\end{aligned}$$ 不妨设 $g(x)=f(2\times x)$ 。则 $g(1)=1,g(n)=4\times g(n-1)-1$。 假设我们要计算 $g(x)$ 用 $g(2)$ 表示的结果,那么我们只需要计算出 $g(\lfloor\frac{x}{2}\rfloor)$ 用 $g(2)$ 表示的结果,稍加修改就能推出用 $g(\lfloor\frac{x}{2}\rfloor)$ 表示 $g(x)$ 的结果。 当然,如果你数学功底足够好,你或许能直接推导出通项公式( **时间复杂度**:$\mathcal O(\log_2n)$。 ### $\textbf{Subtask 4}\ n\le 10^{18},m\le 5\times 10^3$ 让我们回到 $\text{Subtask 2}$ 的做法。 很显然,每个点的状态只与上个点有关,因此我们能够压缩掉第一维。 我们能够发现,该式可以矩阵加速递推。具体而言,我们设计一个 $1\times 6$ 的向量矩阵,其中第$(j\times 3+k+1)$ 列表示$dp_{i,j,k}$;然后设计一个转移矩阵,使用矩阵快速幂进行加速转化。 初始矩阵: $$\begin{bmatrix}0 & a & 0 & b & 0 & 0\end{bmatrix}$$ 其中,当点 $1$ 限制为白点时, $a=1$ ,否则 $a=0$ ; 其中,当点 $1$ 限制为黑点时, $b=1$ ,否则 $b=0$ 。 转移矩阵: $$\begin{bmatrix} 0 & 1 & 0 & 1 & 0 & 0 \cr 0 & 0 & 1 & 0 & 1 & 0 \cr 1 & 0 & 0 & 0 & 0 & 1 \cr 1 & 0 & 0 & 0 & 0 & 1 \cr 0 & 1 & 0 & 1 & 0 & 0 \cr 0 & 0 & 1 & 0 & 1 & 0 \cr \end{bmatrix}$$ $\rm PS:$实际计算转移矩阵的时候,完全可以直接用 $\rm dp$ 式子生成,而不需要手推。 **时间复杂度**:$\mathcal O(m\times k^3\times \log _2 \frac{n}{m})$。其中,$k$为矩阵大小,即 $k=6$ 。 ## 参考代码 $\text{Subtask 1}$,暴力代码: ```cpp #include<bits/stdc++.h> #define up(l,r,i) for(int i=l;i<=r;i++) #define dn(l,r,i) for(int i=l;i>=r;i--) using namespace std; typedef long long LL; const int INF =2147483647; int qread(){ int w=1,c,ret; while((c=getchar())> '9'||c< '0') w=(c=='-'?-1:1); ret=c-'0'; while((c=getchar())>='0'&&c<='9') ret=ret*10+c-'0'; return ret*w; } map<int,int> T; bool chk(int n,int t){ dn(n-1,2,i){ int x=(t&-t),p=T[x]; if(x==0) return false; if(p==i) t^=1,t^=(1<<i-1),t-=1<<p; else if(p==0) t^=2,t^=(1<<i ),t>>=1; else{ t^=1<<p-1,t^=1<<p+1; t=(t&(1<<p)-1)|(t>>p)<<p-1; } } return t==3; } int cnt(int n,int t){ int c=n; up(0,n-1,i){ if(t&(1<<i)) --c; } return c; } #define pi first #define ci second int ans; pair<int,int> P[20]; bool made_by_joesSR=true; int main(){ up(0,20,i) T[1<<i]=i; int n=qread(),m=qread(),p=(1<<n)-1; up(1,m,i){ int x=qread(),y=qread(); P[i].pi=x,P[i].ci=y; } up(0,p,i){ up(1,m,j){ if(!!(i&(1<<P[j].pi-1))!=P[j].ci) goto nxt; } if(cnt(n,i)%2==0&&chk(n,i)) ++ans; nxt:; } printf("%d\n",ans); return 0; } ``` $\text{Subtask 1\~ 3}$,普通$\rm dp$。 ```cpp #include<bits/stdc++.h> #define up(l,r,i) for(int i=l;i<=r;i++) #define dn(l,r,i) for(int i=l;i>=r;i--) using namespace std; typedef long long LL; const int INF =2147483647; int qread(){ int w=1,c,ret; while((c=getchar())> '9'||c< '0') w=(c=='-'?-1:1); ret=c-'0'; while((c=getchar())>='0'&&c<='9') ret=ret*10+c-'0'; return ret*w; } const int MAXC =6+3,MAXM=5e3+3; int n,m,p=1,A[MAXC],B[MAXC]; const int MOD =998234353; #define dp(x,y) ((x)*3+(y)) #define pi first #define ci second pair<int,int> P[MAXM]; bool made_by_joesSR=true; int main(){ n=qread(),m=qread(); up(1,m,i) P[i].pi=qread(),P[i].ci=qread(); sort(P+1,P+1+m); if(P[1].pi==1){ ++p; if(P[1].ci==0) A[dp(1,0)]=1; else A[dp(0,1)]=1; } else A[dp(1,0)]=A[dp(0,1)]=1; up(2,n,i){ bool a=true,b=true; if(P[p].pi==i){if(P[p].ci==0) a=false; else b=false;++p;} up(0,1,j) up(0,2,k){ B[dp(j,k)]=(A[dp(j,(k+2-j)%3)]*a+A[dp(!j,k)]*b)%MOD; } memcpy(A,B,sizeof(B)); } printf("%d\n",(A[dp(0,1)]+A[dp(0,2)])%MOD); return 0; } ``` $\text{Subtask 3}$,分治。 ```cpp #include<bits/stdc++.h> #define up(l,r,i) for(int i=l;i<=r;i++) #define dn(l,r,i) for(int i=l;i>=r;i--) using namespace std; typedef long long LL; const int INF =2147483647; LL qread(){ LL w=1,c,ret; while((c=getchar())> '9'||c< '0') w=(c=='-'?-1:1); ret=c-'0'; while((c=getchar())>='0'&&c<='9') ret=ret*10+c-'0'; return ret*w; } const int MOD=998234353; void calc(LL n,int &a,int &b){ if(n==1){a=1,b=0;return;} LL m=n/2; int p=0,q=0; calc(m,p,q); q=((LL)p*q+q)%MOD,p=(LL)p*p%MOD; LL t=2ll*m-1; while(t<n){ p=(LL)p*4%MOD,q=((LL)4*q+1)%MOD; ++t; } a=p,b=q; } LL n,m; bool made_by_joesSR=true; int main(){ n=qread(),m=qread(); if(m) puts("-1"),exit(0); if(n%2==0){ int a,b; calc(n/2,a,b); printf("%d\n",(a+b)%MOD); } else{ int a,b; calc(n/2,a,b); printf("%d\n",(2ll*(a+b)+1)%MOD); } return 0; } ``` $\text{Subtask 4}$,矩阵优化 $dp$ ```cpp #include<bits/stdc++.h> #define up(l,r,i) for(int i=l;i<=r;i++) #define dn(l,r,i) for(int i=l;i>=r;i--) using namespace std; typedef long long LL; const int INF =2147483647; typedef unsigned long long u64; u64 qread(){ u64 w=1,c,ret; while((c=getchar())> '9'||c< '0') w=(c=='-'?-1:1); ret=c-'0'; while((c=getchar())>='0'&&c<='9') ret=ret*10+c-'0'; return ret*w; } const int MOD =998234353; const int MAXC=6+3,MAXN=5e3+3; struct mtx{ int dt[MAXC][MAXC],a,b; mtx(int x=0,int y=0):a(x),b(y){memset(dt,0,sizeof(dt));} mtx operator *(mtx t){ int n=a,p=b,m=t.b; mtx r(n,m); up(0,p-1,i) up(0,n-1,j) up(0,m-1,k) r.dt[j][k]=((LL)r.dt[j][k]+(LL)dt[j][i]*t.dt[i][k])%MOD; return r; } }o(1,6),oo(6,6); mtx pwr(mtx x,u64 y){ mtx r=x,t=x; --y; while(y){if(y&1) r=r*t; t=t*t,y>>=1;} return r; } #define pi first #define ci second #define dp(x,y) ((x)*3+(y)) u64 n,m,lst,T[MAXC]; bool made_by_joesSR=true; pair<u64,bool> P[MAXN]; int main(){ up(0,1,i) up(0,2,j){ oo.dt[dp(!i,j)][dp(i,j)]=oo.dt[dp(i,(j+2-i)%3)][dp(i,j)]=1; } n=qread(),m=qread(); up(1,m,i) P[i].pi=qread(),P[i].ci=qread(); sort(P+1,P+1+m); if(P[1].pi==1){ if(P[1].ci==0) o.dt[0][dp(1,0)]=1; else o.dt[0][dp(0,1)]=1; } else o.dt[0][dp(1,0)]=o.dt[0][dp(0,1)]=1; lst=1; up(1,m,i){ if(P[i].pi==1) continue; if(lst<P[i].pi-1) o=o*pwr(oo,(P[i].pi-1)-lst); memset(T,0,sizeof(T)); bool a=true,b=true; if(P[i].ci==0) a=false; else b=false; up(0,1,j) up(0,2,k){ T[dp(j,k)]=(o.dt[0][dp(j,(k+2-j)%3)]*a+o.dt[0][dp(!j,k)]*b)%MOD; } up(0,5,j) o.dt[0][j]=T[j]; lst=P[i].pi; } if(lst!=n) o=o*pwr(oo,n-lst); printf("%d\n",(o.dt[0][dp(0,1)]+o.dt[0][dp(0,2)])%MOD); return 0; } ```