zoukankan      html  css  js  c++  java
  • @uoj


    @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 而言的):

    BA 0 1 2
    0
    1
    2

    发现这个结果和 A - B 在模 3 意义下的值有关:A - B = 0 时平局,A - B = 1 时 A 胜,A - B = 2 时 A 负。

    进一步的,对于两个人 A, B 之间的 m 局游戏,A 的胜负局数只跟 (Aominus B) 有关(其中 (ominus) 表示的是不退位减法)。

    如果 i 的三进制表达中包含 u 个 1,v 个 2,则记 (g_i = b_{u, v})

    因此:(f_{i, x} = sum f_{i - 1, y} imes g_{x ominus y}),或者说反过来写有 (f_{i, yoplus z} = sum f_{i - 1, y} imes g_{z})(其中 (oplus) 表示的是不进位加法)。

    然后就是一个 K 进制 fwt 模板题。可以将其看作 m 个变量,每个变量都做 3 维循环卷积。代 3 次单位根即可。写法和 2 进制 fwt 基本没有差别。

    然后你也可以解释题目中那个迷惑的条件 (frac{1}{x} + frac{1}{y} ot= frac{3}{p})。这其实是为了保证 p 不是 3 的倍数(否则构造 (x = y = frac{2}{3}p)),在做逆变换时保证了 3 存在逆元。

    时间复杂度 (O(nlog_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 + bomega) 的形式即可。
    如果多存一个(omega^2)的系数大概率就会像我一样被卡常了

    然后在 fwt 的时候,与单位根的乘法拆成加减法会快很多(乘法果然是模运算下最慢运算符)

  • 相关阅读:
    03人脉搜索:学会这一招,就能轻松找到90%的人的联系方式
    02 资源搜索-全面、快速查找全网你想要的任何信息、情报
    01信息搜索:全面、快速查找全网你想要的任何信息、情报.
    ansible笔记(12):handlers的用法
    ansible笔记(11):初识ansible playbook(二)
    ansible笔记(10):初识ansible playbook
    ansible笔记(9):常用模块之包管理模块
    ansible笔记(8):常用模块之系统类模块(二)
    ansible笔记(7):常用模块之系统类模块
    ansible笔记(6):常用模块之命令类模块
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/12569105.html
Copyright © 2011-2022 走看看