CF856C Eleventh Birthday

CF856C Eleventh Birthday
终于过了QWQ

题目大意

就是给你n个数,然后求按任意顺序摆放形成的数中, 可以被11整除的有多少个

题解

有一点小学奥数基础的可以很容易发现
在这里插入图片描述
即一个数如果可以被11整除,那他的奇数位和偶数位之和是相等的
举几个个简单的例子
121 = 11 ∗ 11     奇 数 位 之 和 位 1 + 1 = 2 , 偶 数 位 之 和 位 2 , 相 等 , 所 以 这 个 数 可 以 被 11 整 除 121 = 11 * 11 \ \ \ 奇数位之和位1 + 1 = 2, 偶数位之和位2,相等,所以这个数可以被11整除 121=1111   1+1=2211

13112 = 1192 ∗ 11     奇 数 位 之 和 位 1 + 1 + 2 = 4 , 偶 数 位 之 和 位 3 + 1 = 4 , 相 等 , 所 以 这 个 数 可 以 被 11 整 除 13112 = 1192 * 11 \ \ \ 奇数位之和位1 + 1 + 2= 4, 偶数位之和位3 + 1 = 4,相等,所以这个数可以被11整除 13112=119211   1+1+2=43+1=411
反之如果和不相等即不能被11整除

可以把问题转换为,奇数位的数字贡献位正的,偶数的贡献位负,然后加起来为0的即是11的倍数

然后我们可以把所有的数按照 奇数位数 的和 偶数位数 的分类‘

发现可以分开考虑,因为偶数位的插在两个奇数位中间不会影响到答案,前后的奇偶性还是没有变

然后设位数位奇数位的数的贡献为 a [ 1... n 1 ] a[1...n1] a[1...n1],位数为偶数位的数的贡献为 b [ 1... n 2 ] b[1...n2] b[1...n2]
先处理位数为奇数位的情况
假设第一位是奇数位,奇数位开始的贡献为正,偶数位开始的贡献为负

f [ i ] [ j ] [ k ] 表 示 前 i 个 位 数 为 奇 数 位 的 数 , 有 j 个 数 的 开 头 是 偶 数 位 , 贡 献 为 k 的 个 数 f[i][j][k]表示前i个位数为奇数位的数,有j个数的开头是偶数位,贡献为k的个数 f[i][j][k]ijk

易 得 f [ i ] [ j ] [ k ] = f [ i − 1 ] [ j ] [ k − a [ i ] ] + f [ i − 1 ] [ j − 1 ] [ k + a [ i ] ] 易得f[i][j][k] = f[i-1][j][k-a[i]] + f[i-1][j-1][k+a[i]] f[i][j][k]=f[i1][j][ka[i]]+f[i1][j1][k+a[i]]

位数为偶数的同理,

g [ i ] [ j ] [ k ] = g [ i − 1 ] [ j ] [ k − b [ i ] ] + g [ i − 1 ] [ j − 1 ] [ k + b [ i ] ] g[i][j][k] = g[i-1][j][k-b[i]] + g[i-1][j-1][k+b[i]] g[i][j][k]=g[i1][j][kb[i]]+g[i1][j1][k+b[i]]

然后就到了最恶心的合并

发现位数为奇数的只有 f [ n 1 ] [ n 1 / 2 ] [ 0...10 ] f[n1][n1/2][0...10] f[n1][n1/2][0...10]

这个应该很容易证明吧

首先枚举位数为偶数的开头是偶数位的个数i,贡献为j
g [ n 2 ] [ i ] [ j ] ∗ f [ n 1 ] [ n 1 / 2 ] [ ( 11 − j ) m o d    11 ] g[n2][i][j] * f[n1][n1/2][(11 - j) \mod 11] g[n2][i][j]f[n1][n1/2][(11j)mod11]
奇偶性相同的数可以按照开头的位数是奇数还是偶数来互相交换,其他数的位数奇偶性不受影响
( n 1 / 2 ) ! ∗ ( n 1 − n 1 / 2 ) ! ∗ i ! ∗ ( n 2 − i ! ) (n1/2)! * (n1 - n1/2)! * i! * (n2 - i!) (n1/2)!(n1n1/2)!i!(n2i!)
最后考虑怎么把位数为偶数的插进去
贡献为正的 n 2 − i n2 - i n2i个只能插在 n 1 / 2 n1/2 n1/2个之后,或再第一个之前,即有 n 1 / 2 + 1 n1/2 + 1 n1/2+1个位置可以插
即把 n 2 − i n2-i n2i个球放在 n 1 / 2 + 1 n1/2 + 1 n1/2+1个盒子里,盒子可以空着
p = n 2 − i , q = n 1 / 2 + 1 p = n2 - i, q = n1/2 + 1 p=n2i,q=n1/2+1 C [ p + q ] [ q − 1 ] C[p+q][q - 1] C[p+q][q1](C表示组合数)
偶数同理

然后把这一大坨东西乘起来求个和就行了

看代码吧

code:

#include<bits/stdc++.h>
#define int long long
#define N 2005
#define mod 998244353
using namespace std;
int n, a[N], b[N], n1, n2, f[2][N][13], g[2][N][13], c[N][N], pw[N], t;
int calc(int n, int m){
	if(m == 0) return (n == 0);//注意边界
	return pw[n] * c[n + m - 1][m - 1] % mod;
}
signed main(){
	pw[0] = 1; c[0][0] = 1;
	for(int i = 1; i <= 2000; i ++) pw[i] = pw[i - 1] * i % mod, c[i][0] = 1;//预处理阶乘和组合数
	for(int i = 1; i <= 2000; i ++)
		for(int j = 1; j <= 2000; j ++)
			c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
	scanf("%lld", &t);
	while(t --){
		scanf("%lld", &n);
		n1 = n2 = 0;
		for(int i = 1; i <= n; i ++){
			int x, p = 0;
			scanf("%lld", &x);
			for(int j = x; j; j /= 10) p ^= 1;
			if(p) a[++ n1] = x % 11; else b[++ n2] = x % 11;//a表示位数为奇数的,b表示位数为偶数的,把它们的贡献处理出来
		}
		memset(f, 0, sizeof f), memset(g, 0, sizeof g);
		f[0][0][0] = g[0][0][0] = 1;
		for(int i = 1; i <= n1; i ++){//dp位数为奇数的情况
			memset(f[i&1], 0, sizeof f[i&1]);
			for(int j = 0; j <= i; j ++)
				for(int k = 0; k < 11; k ++){
					int p = (k - a[i] + 11) % 11, q = (k + a[i]) % 11;
					f[i&1][j][k] = (f[i&1][j][k] + f[(i - 1)&1][j][p]) % mod;
					if(j) f[i&1][j][k] = (f[i&1][j][k] + f[(i - 1)&1][j - 1][q]) % mod;
				}
		}
			
		for(int i = 1; i <= n2; i ++){//dp位数为偶数的情况
			memset(g[i&1], 0, sizeof g[i&1]);
			for(int j = 0; j <= i; j ++)
				for(int k = 0; k < 11; k ++){
					int p = (k - b[i] + 11) % 11, q = (k + b[i]) % 11;
					g[i&1][j][k] = (g[i&1][j][k] + g[(i - 1)&1][j][p]) % mod;
					if(j) g[i&1][j][k] = (g[i&1][j][k] + g[(i - 1)&1][j - 1][q]) % mod;
				}
		}
		int ans = 0;
		for(int i = 0; i <= n2; i ++)
			for(int j = 0; j < 11; j ++){
				ans = (ans + g[n2&1][i][j] * f[n1&1][n1 / 2][(11 - j) % 11] % mod * pw[n1 / 2] % mod * pw[n1 - n1 / 2] % mod * calc(i, n1 - n1 / 2) % mod * calc(n2 - i, n1 / 2 + 1) % mod) % mod;//算贡献,上面写的
			}
		printf("%lld\n", ans);	
	}
	
	return 0;
}

坑点

要注意内存,需要滚掉一维

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值