@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 个人分数是:

\[f_{i, x} = \sum_{0 \leq y < n} f_{i-1, y} b_{u, v} \]

其中 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 的时候,与单位根的乘法拆成加减法会快很多(乘法果然是模运算下最慢运算符)

posted @ 2020-03-25 20:13  Tiw_Air_OAO  阅读(141)  评论(0编辑  收藏  举报