[报告]ZJU 3651 Cipher Lock
Abstract
ZJU 3651 Cipher Lock
组合计数 DP 矩阵乘法
Body
Source
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3651
Description
N个点围成一个环,每个点有两个属性(u, v),取值范围分别是[1, p]和[1, q]。已知其中8个点的属性,问有多少个环,满足相邻两点至少有一个属性值相同。
Solution
很明显的用矩阵加速的dp统计题。
一开始想错状态,把起点的情况也考虑进去,弄了个3x3({us,ut,其他}x{vs,vt,其他})=9维的矩阵,然后还要考虑vs,vt相等或者us,ut相等之类的情况,写了一下午,一脸血。
后来观察了下,发现合并后,其实跟起点的情况没有关系。
首先把点按照坐标排序,算相邻点之间的方案数,最后乘起来即可。
对于相邻的点,记f(n,{0,1},{0,1})表示从起点开始走n个点,u和终点的u是否相等,v和终点的v是否相等的方案数。转移比较容易。由于n很大所以要用矩阵乘法加速。
注意有点坐标相同的情况,特殊判断一下。
Code
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; const ll mod = 1234567890ll; struct sm { ll m[4][4]; void init() {memset(m, 0, sizeof m);} sm() {init();} void eye() {for (int i = 0; i < 4; ++i) m[i][i] = 1;} }; sm operator*(const sm &a, const sm &b) { sm res; for (int i = 0; i < 4; ++i) for (int j = 0; j < 4; ++j) for (int k = 0; k < 4; ++k) if (a.m[i][k] && b.m[k][j]) res.m[i][j] = (res.m[i][j]+a.m[i][k]*b.m[k][j])%mod; return res; } sm operator^(sm x, ll p) { sm res; res.eye(); for (; p; p>>=1) { if (p&1) res = res*x; x = x*x; } return res; } struct snode { ll x, u, v; void read() {cin>>x>>u>>v; x--; u--; v--;} bool operator<(const snode &rhs) const { return x < rhs.x; } }node[10]; ll N, p, q; ll solve() { int i, j, k; ll res = 1; sort(node, node+8); node[8] = node[0]; node[8].x += N; sm m; m.m[0][0]=m.m[0][1]=m.m[0][2]=m.m[1][3]=m.m[2][3] = 1; m.m[1][0]=m.m[1][1]=m.m[3][2] = (q-1)%mod; m.m[2][0]=m.m[2][2]=m.m[3][1] = (p-1)%mod; m.m[3][3] = p>1&&q>1?(p+q-3)%mod:0; //from i to i+1 for (i = 0; i < 8; ++i) { if (node[i].x==node[i+1].x) { if (node[i].u^node[i+1].u || node[i].v^node[i+1].v) return 0; else continue; } sm now = m^(node[i+1].x-node[i].x); j = ((node[i].u!=node[i+1].u)<<1)|(node[i].v!=node[i+1].v); res = (res*now.m[0][j])%mod; } res %= mod; if (res<0) res += mod; return res; } int main() { int i, j, k; while (cin>>N>>p>>q) { for (i = 0; i < 8; ++i) node[i].read(); cout<<solve()<<endl; } return 0; }