题解 P5188 【[COCI2009-2010#4] PALACINKE】

posted on 2022-07-25 20:12:26 | under 题解 | source

做法:矩阵优化 DP + 容斥原理。

矩阵优化 DP

先不要考虑商品,写个不管约束条件的 DP。令 \(f_{t,u}\) 表示在 \(t\) 时刻安娜在结点 \(u\) 上的方案数。初始时有 \(f_{0,1}=1,f_{0,u\neq 1}=0\)。考虑转移,如果存在一条单向边 \((u,v)\),则我们能用 1 分钟(不买东西)或者 2 分钟(买东西)经过它,则

\[f_{t,v}=\sum\limits_{(u,v)\in E}{(f_{t-1,u}+f_{t-2,u})} \]

这就是朴素的 DP。发现奇妙的 \(N\leq 25,T\leq 10^9\),套路地想到矩阵乘法。这里的 DP 需要用到 \(t-1\)\(t-2\) 两个时刻,因此矩阵至少要存下前两个时刻的 DP 值。题目要求至少 \(t\) 分钟内回到 \(1\) 的答案,因此我们还需要一个 \(ans\) 记录。我们可以构造这样的矩阵:

\[I_t=\begin{bmatrix} f_{t-1,1},f_{t-1,2},\cdots,f_{t-1,n},f_{t,1},f_{t,2},\cdots,f_{t,n},ans \end{bmatrix} \]

表示 \(t\) 时刻的 DP 值,我们希望找到一个方阵 \(X\) 使 \(I_tX=I_{t+1}\)

首先把 \(I_{t,1,n+1\sim2n}\) 的值转移到 \(I_{t+1,1,1\sim n}\),这部分值不变都是 \(f_t\),在 \(X\) 的"左下角"填一排对角线 \(1\)

然后我们要推 \(f_{t+1}\) 的 DP 值,它依赖于 \(f_t,f_{t-1}\)(务必区分 \(f_t,f_{t-1}\) 转移的含义,\(f_t\) 是直接经过商店不买东西,\(f_{t-1}\) 是进入商店买东西),如果存在一条边 \((u,v)\in E\),就将 \(X_{u,v+n},X_{u+n,v+n}\) 的值加一,使它转移。给一个具体的例子,感性理解一下:

(关于如何想出这一个矩阵,个人理解是把矩阵乘法结果中第 \(i\) 行第 \(j\) 列看作是,\(a\) 矩阵的第 \(i\) 行顺时针旋转 \(90^\circ\),与 \(b\) 矩阵的第 \(j\) 列,重合在一起,算乘法的和。这样的方式可能能帮助你理解。)

还有一个 \(ans\),我们在最右下角 \(X_{2n+1,2n+1}\) 放一个 \(1\)(可以理解为继承上一次),再在 \(X_{n+1,2n+1}\) 放一个 \(1\)(可以理解为加上 \(f_{t,1}\) 的方案数)。于是我们有了 \(I_tX=I_{t+1}\),快速幂优化 \(I_1X^t=I_{t+1}\) 的转移即可。

容斥原理

回归题目,它要我们求出四种商品全买的方案数。直接求不好做,考虑合法方案数 + 不合法方案数 = 总方案数,总方案数显然是安娜在图上随便走的方案数,所以第一步把题意转化成不合法方案数。

\(B,J,M,P\) 分别为强制不买 B,J,M,P 的方案数,不合法的方案数就是 \(|B\cup J\cup M\cup P|\),发现这是个容斥原理,那么有

\[\begin{aligned} |B\cup J\cup M\cup P|&=|B|+|J|+|M|+|P|\\ &-|B\cap J|-|B\cap M|-|B\cap P|-|J\cap M|-|J\cap P|-|M\cap P|\\ &+|B\cap J\cap M|+|B\cap M\cap P|+|B\cap J\cap P|+|B\cap J\cap M|\\ &+|B\cap J\cap M\cap P|. \end{aligned}\]

由于这是 \(\cap\)(交),把它理解成 \(\land\)(并),我们对于这当中的每个集合,强制不买那些商品(但是可以经过有那些商品的店而不进去买),按照上文的矩阵优化递推出合法方案数,最后就能得到我们想要的不合法的方案数。

实现

  • 判断二进制数 \(t,s\) 在子集意义下是否有 \(t\subseteq s\),可以用 (~s&t)==0 实现。
  • 构造 \(I_1\) 矩阵时暴力计算 \(f_{1,u}\) 的方案数。
  • 观察到这题模数很小,那么矩阵可以开 int,算完一个值统一取模,以减少常数(需要跑 \(16\) 次矩阵快速幂)。
  • 务必搞清楚容斥系数!
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
int popcount(int x){return (x&1)+(x>>1&1)+(x>>2&1)+(x>>3&1);}
const int P=5557; 
template<int N,int M,class T=int> struct graph{
    int head[N+10],nxt[M*2+10],cnt;
    struct edge{
        int u,v;T w;
        edge(int u=0,int v=0,T w=0):u(u),v(v),w(w){}
    } e[M*2+10];
    graph(){memset(head,cnt=0,sizeof head);}
    edge operator[](int i){return e[i];}
    void add(int u,int v,T w=0){e[++cnt]=edge(u,v,w),nxt[cnt]=head[u],head[u]=cnt;}
    void link(int u,int v,T w=0){add(u,v,w),add(v,u,w);}
};
template<int N,int M,class T=LL> struct matrix{
	T a[N+3][M+3];
	matrix(bool f=0){
		memset(a,0,sizeof a);
		if(f) for(int i=1;i<=N;i++) a[i][i]=1;
	}
	T* operator[](int i){return a[i];}
	void print(int n,int m){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				printf("%d%c",a[i][j]," \n"[j==m]);
			}
		}	
	}
};
template<int N,int M,int R,class T=LL> matrix<N,R,T> operator*(matrix<N,M,T> a,matrix<M,R,T> b){
	matrix<N,R,T> c=0;
	for(int i=1;i<=N;i++){
		for(int j=1;j<=R;j++){
			for(int k=1;k<=M;k++){
				c[i][j]+=a[i][k]*b[k][j];
			}
			c[i][j]%=P;
		}
	}
	return c;
}
template<int N,class T=LL> matrix<N,N,T> operator^(matrix<N,N,T> a,LL b){
	matrix<N,N,T> ans=1;
	for(;b;b>>=1,a=a*a) if(b&1) ans=ans*a;
	return ans;
}
int n,m,t,tb[128];
char buf[110];
graph<30,510,int> g;
matrix<60,60,int> o;
matrix<1,60,int> e;
int has(char *s){int res=0;for(int i=1,len=strlen(s+1);i<=len;i++) res+=tb[s[i]];return res;}
void binprt(int x){for(int i=3;i>=0;i--) putchar((x>>i&1)+'0');}
int solve(int sta){
	o=0,e=0;
	e[1][1]=1,o[n*2+1][n*2+1]=o[n+1][n*2+1]=1;//e={0,1}
	for(int i=1;i<=n;i++) o[i+n][i]=1;
	for(int i=1;i<=g.cnt;i++){
		//(u,v)=>f[n][u]=f[n-1][v]+f[n-2][v]
		int u=g[i].u,v=g[i].v;
		o[u+n][v+n]++;
		if(u==1) e[1][v+n]++;
		if((~sta&g[i].w)==0) o[u][v+n]++;
	}
	return (e*(o^t))[1][n*2+1];
}
int main(){
//	#ifdef LOCAL
//	 	freopen("input.in","r",stdin);
//	#endif
	tb['B']=1,tb['J']=2,tb['M']=4,tb['P']=8;
	scanf("%d%d",&n,&m);
	for(int i=1,u,v;i<=m;i++) scanf("%d%d%s",&u,&v,buf+1),g.add(u,v,has(buf));
	scanf("%d",&t);
	int all=solve(15),res=0;
	for(int i=1;i<=15;i++) res+=solve(15-i)*(popcount(i)&1?1:-1);//,printf("ban=%d,ans%c=%d\n",i,"-+"[popcount(i)&1],solve(15-i));
//	printf("all=%d,res=%d\n",all,res);
	printf("%d\n",((all-res)%P+P)%P);
	return 0;
}
/*
t in s => ~s&t==0
*/

复杂度:\(O(2^kn^3\log T)\),其中 \(k=4\) 为商品种类数。

posted @ 2022-11-12 14:46  caijianhong  阅读(80)  评论(0编辑  收藏  举报