• 【luogu P5363】移动金币(博弈论)(DP)(数位DP)(MTT)


    移动金币

    题目链接:luogu P5363

    题目大意

    给你一个 1*n 的棋盘,然后一开始有 m 个东西,每次两个人轮流操作,可以选一个东西往左移若干格。
    (不能飞出格子,不能越过别的东西)
    然后谁不能动就输了,然后问你有多少种初始情况先手必胜。

    思路

    首先考虑推出什么时候会赢。

    你可以把一个点往左移看做是它和它前面的点的距离变小,到它后面点的距离变大,然后你再把 (0) 也看做一个点(它不可以移),那如果所有相邻的距离都是 (0) 就输了。

    然后把一个变小若干,把旁边的变大若干,可以看做是把左边的若干个式子移动到右边,然后全部移动到最右边就输了。

    那就是一个阶梯博弈,就是要奇数位的石子个数异或和。
    那必胜是非 (0) 不好搞考虑求是 (0) 的。

    然后考虑 DP,就是要把 (n-m) 个石子放到 (m+1) 堆里面且偶数堆异或和是 (0)
    首先是暴力枚举每一堆放多少个石子状态时到第 (i) 堆一共用了多少式子当前异或值是多少,(mn^3)

    然后考虑异或,就每位每位搞,就每次看一位,看有多少堆里面放了。
    (f_{i,j}) 为当前处理到第 (i) 位,然后已经有了 (j) 的石子,然后枚举放多少个,(nmlog n)

    然后发现你对于 (N=n-m),对于它二进制的每一位,要么是在这个位数放了一个式子,要么是从低位的放了若干个进位过来的。
    那我们可以直接设 (f_{i,j}) 为处理到第 (i) 位,当前为还有 (j) 次可以选。
    然后因为如果 (j>m+1) 就直接无解(无论如何都放不完),所以 (j)(m) 级别的,(m^2log n)

    具体转移大概就是:(f_{i,2j+v-k}leftarrow f_{i+1,j}*p_k)(v)(N) 这一位是 (1) 还是 (0)(p_k)(k) 个分给 (m+1) 堆,偶数堆有偶数个的方案数)

    (p_k) 可以预处理,就直接暴力枚举给偶数堆的个数 (j),直接 (C_j^{leftlfloorfrac{m+1}{2} ight floor}cdot C_{k-j}^{m+1-leftlfloorfrac{m+1}{2} ight floor})
    (m+1-leftlfloorfrac{m+1}{2} ight floor=leftlfloorfrac{m+2}{2} ight floor)

    然后其实就可以了。


    但是还不够(至少在对于某题来讲)

    然后你发现转移的式子它其实可以看做是卷起来。
    (f_{i+1,j}) 放到 (a_{2j+v}) 的位置,(p_k) 放到 (b_{m+1-k}) 的位置。
    然后 (a)(b) 的卷积的第 (i) 位结果就是 (f_{i,i-(m-1)}) 了。

    然后你用任意模数 NTT 搞即可,这里用了 MTT 的方法。

    然后复杂度就是 (mlog mlog n) 了。

    代码

    普通版((m^2log n)

    #include<cstdio>
    #define ll long long
    #define mo 1000000009
    
    using namespace std;
    
    int n, m;
    ll jc[61], inv[61], p[61];
    ll f[21][61];
    
    ll C(int n, int m) {
    	if (n < m) return 0;
    	if (m < 0) return 0;
    	return jc[n] * inv[m] % mo * inv[n - m] % mo;
    }
    
    ll ksm(ll x, ll y) {
    	ll re = 1;
    	while (y) {
    		if (y & 1) re = re * x % mo;
    		x = x * x % mo;
    		y >>= 1;
    	}
    	return re;
    }
    
    void init() {
    	jc[0] = 1;
    	for (int i = 1; i <= m + 1; i++) jc[i] = jc[i - 1] * i % mo;
    	inv[m + 1] = ksm(jc[m + 1], mo - 2);
    	for (int i = m; i >= 0; i--) inv[i] = inv[i + 1] * (i + 1) % mo;
    	
    	int on = (m + 1) / 2, jn = (m + 2) / 2;
    	for (int k = 0; k <= m + 1; k++) {
    		for (int i = 0; i <= on; i += 2) {
    			p[k] = (p[k] + C(on, i) * C(jn, k - i) % mo) % mo;
    		}
    	}
    }
    
    int main() {
    	scanf("%d %d", &n, &m);
    	
    	init();
    	
    	f[20][0] = 1; int need = n - m;
    	for (int i = 19; i >= 0; i--) {
    		int hv = (need >> i) & 1;
    		for (int j = 0; j <= m + 1; j++)
    			for (int k = 0; k <= m + 1; k++) {
    				int to = 2 * j + hv - k;
    				if (to < 0 || to > m + 1) continue;
    				f[i][to] = (f[i][to] + f[i + 1][j] * p[k] % mo) % mo; 
    			}
    	}
    	
    	ll ans = 1;
    	for (int i = n - m + 1; i <= n; i++) ans = ans * i % mo;
    	ans = ans * inv[m];
    	printf("%lld", (ans - f[0][0] + mo) % mo);
    	
    	return 0;
    }
    

    MTT版((mlog mlog n)

    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<algorithm> 
    #define lim 32000
    #define ll long long
    #define mo 1000000009
    
    using namespace std;
    
    struct complex {
    	double x, y;
    	complex (double xx = 0, double yy = 0) {
    		x = xx; y = yy;
    	}
    };
    int n, m;
    ll jc[61], inv[61], p[61];
    ll f[21][61], ans[201], a[201], b[201];
    double Pi = acos(-1.0);
    
    complex operator +(complex x, complex y) {
    	return (complex){x.x + y.x, x.y + y.y};
    }
    
    complex operator -(complex x, complex y) {
    	return (complex){x.x - y.x, x.y - y.y};
    }
    
    complex operator *(complex x, complex y) {
    	return (complex){x.x * y.x - x.y * y.y, x.x * y.y + x.y * y.x};
    }
    
    ll C(int n, int m) {
    	if (n < m) return 0;
    	if (m < 0) return 0;
    	return jc[n] * inv[m] % mo * inv[n - m] % mo;
    }
    
    ll ksm(ll x, ll y) {
    	ll re = 1;
    	while (y) {
    		if (y & 1) re = re * x % mo;
    		x = x * x % mo;
    		y >>= 1;
    	}
    	return re;
    }
    
    void init() {
    	jc[0] = 1;
    	for (int i = 1; i <= m + 1; i++) jc[i] = jc[i - 1] * i % mo;
    	inv[m + 1] = ksm(jc[m + 1], mo - 2);
    	for (int i = m; i >= 0; i--) inv[i] = inv[i + 1] * (i + 1) % mo;
    	
    	int on = (m + 1) / 2, jn = (m + 2) / 2;
    	for (int k = 0; k <= m + 1; k++) {
    		for (int i = 0; i <= on; i += 2) {
    			p[k] = (p[k] + C(on, i) * C(jn, k - i) % mo) % mo;
    		}
    	}
    }
    
    struct MTT_work {
    	complex p1[201 << 2], p2[201 << 2], q[201 << 2];
    	int limit, l_size, an[201 << 2];
    	
    	void FFT(complex *now, int op) {
    		for (int i = 0; i < limit; i++)
    			if (i < an[i]) swap(now[i], now[an[i]]);
    		for (int mid = 1; mid < limit; mid <<= 1) {
    			complex Wn(cos(Pi / mid), op * sin(Pi / mid));
    			for (int R = (mid << 1), j = 0; j < limit; j += R) {
    				complex w(1, 0);
    				for (int k = 0; k < mid; k++, w = w * Wn) {
    					complex x = now[j + k], y = w * now[j + mid + k];
    					now[j + k] = x + y; now[j + mid + k] = x - y;
    				}
    			}
    		}
    	}
    	
    	void mul(int n, ll *x, int m, ll *y) {
    		limit = 1; l_size = 0;
    		while (limit <= n + m) {
    			limit <<= 1; l_size++;
    		}
    		for (int i = 0; i < limit; i++) p1[i] = p2[i] = q[i] = (complex){0, 0};
    		for (int i = 0; i <= n; i++)
    			p1[i] = (complex){x[i] / lim, x[i] % lim}, p2[i] = (complex){x[i] / lim, -x[i] % lim};
    		for (int i = 0; i <= m; i++)
    			q[i] = (complex){y[i] / lim, y[i] % lim};
    		for (int i = 0; i < limit; i++)
    			an[i] = (an[i >> 1] >> 1) | ((i & 1) << (l_size - 1));
    		FFT(p1, 1); FFT(p2, 1); FFT(q, 1);
    		for (int i = 0; i < limit; i++) q[i].x /= limit, q[i].y /= limit;
    		for (int i = 0; i < limit; i++) p1[i] = p1[i] * q[i], p2[i] = p2[i] * q[i];
    		FFT(p1, -1); FFT(p2, -1);
    		for (int i = 0; i <= n + m; i++) {
    			ll a1b1 = (ll)floor((p1[i].x + p2[i].x) / 2 + 0.5) % mo;
    			ll a1b2 = (ll)floor((p1[i].y + p2[i].y) / 2 + 0.5) % mo;
    			ll a2b1 = ((ll)floor(p1[i].y + 0.5) - a1b2) % mo;
    			ll a2b2 = ((ll)floor(p2[i].x + 0.5) - a1b1) % mo;
    			ans[i] = (a1b1 * lim * lim + (a1b2 + a2b1) * lim + a2b2) % mo;
    			ans[i] = (ans[i] + mo) % mo;
    		}
    	}
    }MTT;
    
    int main() {
    	scanf("%d %d", &n, &m);
    	
    	init();
    	
    	f[20][0] = 1; int need = n - m;
    	for (int i = 19; i >= 0; i--) {
    		int hv = (need >> i) & 1;
    //		for (int j = 0; j <= m + 1; j++)
    //			for (int k = 0; k <= m + 1; k++) {
    //				int to = 2 * j + hv - k;
    //				if (to < 0 || to > m + 1) continue;
    //				f[i][to] = (f[i][to] + f[i + 1][j] * p[k] % mo) % mo; 
    //			}
    		memset(a, 0, sizeof(a)); memset(b, 0, sizeof(b));
    		for (int j = 0; j <= m + 1; j++)//两倍加上进位
    			a[2 * j + hv] = f[i + 1][j];
    		for (int j = 0; j <= m + 1; j++)//反过来全部右移 m+1
    			b[m + 1 - j] = p[j];
    		MTT.mul((m + 1) * 2 + hv, a, m + 1, b);
    		for (int j = m + 1; j <= m + 1 + m + 1; j++)
    			f[i][j - (m + 1)] = ans[j];//因为 b 右移了所以要移回来 m+1 位
    	}
    	
    	ll answ = 1;
    	for (int i = n - m + 1; i <= n; i++) answ = answ * i % mo;
    	answ = answ * inv[m];
    	printf("%lld", (answ - f[0][0] + mo) % mo);
    	
    	return 0;
    }
    
  • 相关阅读:
    <整理> 在Bash中添加个人定制的命令
    <整理> linux常用命令及工具
    论文分享:目标检测-YOLO
    Siamese Attentional Keypoint Network for High Performance Visual Tracking--论文笔记
    ubuntu 相关软件设置
    anoconda 神经网络 相关操作
    转载:决策树算法梳理
    转载:XGBOOST算法梳理
    XGB算法梳理
    决策树算法梳理
  • 原文地址:https://www.cnblogs.com/Sakura-TJH/p/luogu_P5363.html
Copyright © 2020-2023  润新知