@uoj - 272@【清华集训2016】石家庄的工人阶级队伍比较坚强
@description@
\(n = 3^m\) 个人在玩石头剪刀布, 一共有 t 轮游戏,每轮有 m 次石头剪刀布。
在同一轮的 m 次游戏中,每个人的决策必须是提前确定的,也就是说不能采用随机策略,也不能根据前若干局的结果决定下一局的决策; 这样,显然一共有 \(n = 3^m\) 种决策。
这 \(n = 3^m\) 个人会采取两两不同的决策。 为了方便表达,对于第 x(0≤x<n)个人,将 x 转换成三进制得到一个 m 位的数。 其中 0 表示剪刀,1 表示石头,
2 表示布。就得到了第 x 个人采取的策略。
由于编号是固定的,因此每个人在不同轮的 m 次游戏中都会采取同一套决策。
第 x 个人在最初时有一个分数 \(f_{0,x}\)。在第 i 轮中,他将和另一个人 y 进行 m 次的石头剪刀布的比赛。
我们用 W(x, y) 表示 x 在和 y 的 m 次比赛中赢的次数;用 L(x, y) 表示 x 在和 y 的 m 次比赛中输的次数。
在第 i 轮结束之后,第 x 个人分数是:
其中 u = W(x, y) = L(y, x), v = L(x, y) = W(y, x),平局不计入次数;$b_{u, v} 则是一个给定的评分数组。
注意即使 y 和 x 一样(自己转移到自己)也会乘上一个系数 \(b_{0, 0}\)(即自己跟自己打全为平局)。
显然随着轮数越来越多,分数也会越来越大,这个计分系统和我们平时用的计算机一样,也会溢出。当要储存的分数 f 大于等于 p 的时候,就会变成 f mod p。
B 君想知道 t 轮之后所有人的分数,也就是 \(f_{t, 0}, f_{t, 1}, \dots, f_{t, n-1}\)
G君:「诶,我发现这个数 p 有特殊的性质诶!不存在两个正整数,使得他们倒数的和等于 3/p!」
B君:「G君好棒!不过这个题怎么做呢?」
@solution@
对于单局游戏,尝试列一个表格(这里结果是相当于 A 而言的):
B\A | 0 | 1 | 2 |
---|---|---|---|
0 | 平 | 胜 | 负 |
1 | 负 | 平 | 胜 |
2 | 胜 | 负 | 平 |
发现这个结果和 A - B 在模 3 意义下的值有关:A - B = 0 时平局,A - B = 1 时 A 胜,A - B = 2 时 A 负。
进一步的,对于两个人 A, B 之间的 m 局游戏,A 的胜负局数只跟 \(A\ominus B\) 有关(其中 \(\ominus\) 表示的是不退位减法)。
如果 i 的三进制表达中包含 u 个 1,v 个 2,则记 \(g_i = b_{u, v}\)。
因此:\(f_{i, x} = \sum f_{i - 1, y}\times g_{x \ominus y}\),或者说反过来写有 \(f_{i, y\oplus z} = \sum f_{i - 1, y}\times g_{z}\)(其中 \(\oplus\) 表示的是不进位加法)。
然后就是一个 K 进制 fwt 模板题。可以将其看作 m 个变量,每个变量都做 3 维循环卷积。代 3 次单位根即可。写法和 2 进制 fwt 基本没有差别。
然后你也可以解释题目中那个迷惑的条件 \(\frac{1}{x} + \frac{1}{y} \not= \frac{3}{p}\)。这其实是为了保证 p 不是 3 的倍数(否则构造 \(x = y = \frac{2}{3}p\)),在做逆变换时保证了 3 存在逆元。
时间复杂度 \(O(n\log_3 n)\),不过常数大。
@accepted code@
#include <cstdio>
const int MAXN = 532000;
int pow3(int m) {
int n = 1; for(int i=1;i<=m;i++,n*=3);
return n;
}
int m, n, t, p;
inline int add(int x, int y) {return (x + y >= p ? x + y - p : x + y);}
inline int sub(int x, int y) {return (x - y < 0 ? x - y + p : x - y);}
inline int mul(int x, int y) {return 1LL * x * y % p;}
struct node{
int a[2]; node() {a[0] = a[1] = 0;}
node(int x, int y) {a[0] = x, a[1] = y;}
friend node operator + (node a, node b) {
return node(add(a.a[0], b.a[0]), add(a.a[1], b.a[1]));
}
friend node operator - (node a, node b) {
return node(sub(a.a[0], b.a[0]), sub(a.a[1], b.a[1]));
}
friend node operator * (node a, node b) {
node c; int t = mul(a.a[1], b.a[1]);
c.a[0] = sub(mul(a.a[0], b.a[0]), t);
c.a[1] = sub(add(mul(a.a[0], b.a[1]), mul(a.a[1], b.a[0])), t);
return c;
}
};
node npow(node a, int p) {
node ret = node(1, 0);
for(int i=p;i;i>>=1,a=a*a)
if( i & 1 ) ret = ret*a;
return ret;
}
node f[MAXN + 5], g[MAXN + 5], b[15][15];
node get(int x) {
int cnt[3] = {};
for(int i=0;i<m;i++)
cnt[x % 3]++, x /= 3;
return b[cnt[1]][cnt[2]];
}
int iv3, ivpw3;
node w1(node x) {return node(sub(0, x.a[1]), sub(x.a[0], x.a[1]));}
node w2(node x) {return node(sub(x.a[1], x.a[0]), sub(0, x.a[0]));}
void fwt(node *A, int n, int type) {
for(int s=3,t=1;s<=n;s*=3,t*=3) {
for(int i=0;i<n;i+=s) {
for(int j=0;j<t;j++) {
node x = A[i + j], y = A[i + j + t], z = A[i + j + 2*t];
if( type == 1 ) {
A[i + j] = x + y + z;
A[i + j + t] = x + w1(y) + w2(z);
A[i + j + 2*t] = x + w2(y) + w1(z);
}
else {
A[i + j] = x + y + z;
A[i + j + t] = x + w2(y) + w1(z);
A[i + j + 2*t] = x + w1(y) + w2(z);
}
}
}
}
if( type == -1 ) {
for(int i=0;i<n;i++)
A[i] = A[i] * node(ivpw3, 0);
}
}
void init() {
for(int i=1;i<=3;i++)
if( (1LL*i*p + 1) % 3 == 0 )
iv3 = ((1LL*i*p + 1) / 3) % p;
ivpw3 = 1; for(int i=0;i<m;i++) ivpw3 = mul(ivpw3, iv3);
}
int read() {
int x = 0; char ch = getchar();
while( ch > '9' || ch < '0' ) ch = getchar();
while( '0' <= ch && ch <= '9' ) x = 10*x + ch - '0', ch = getchar();
return x;
}
void write(int x) {
if( !x ) return ;
write(x / 10), putchar(x % 10 + '0');
}
int main() {
m = read(), t = read(), p = read(), n = pow3(m);
for(int i=0;i<n;i++) f[i].a[0] = read();
for(int i=0;i<=m;i++)
for(int j=0;j<=m-i;j++)
b[i][j].a[0] = read();
for(int i=0;i<n;i++) g[i] = get(i);
init(), fwt(f, n, 1), fwt(g, n, 1);
for(int i=0;i<n;i++) f[i] = f[i] * npow(g[i], t);
fwt(f, n, -1);
for(int i=0;i<n;i++)
if( f[i].a[0] ) write(f[i].a[0]), puts(""); else puts("0");
}
@details@
没错我就是那个被卡常数的人。。。
注意到三次单位根满足 \(1 + \omega + \omega^2 = 0\),即 \(\omega^2 = -\omega-1\)。因此将每个数存储 \(a + b\omega\) 的形式即可。
如果多存一个\(\omega^2\)的系数大概率就会像我一样被卡常了
然后在 fwt 的时候,与单位根的乘法拆成加减法会快很多(乘法果然是模运算下最慢运算符)。