【TCO 2013 WildCard】SemiMultiple

Description

定义一个非负数   是   长度下的   的半倍数,为   不是   的倍数,且   是   的倍数。
问有多少个   长度下的   的半倍数。

Difficulty

MainAlgorithm

容斥
DP

Complexity

Solution

比较难……
我们肯定是要对每个余数分别讨论的,可以用   的DP很轻松地把   以下的所有模   为  的数搞出来。
这样就能发现只有余数   或   时才有可能是半倍数的。
但是也有不合法的,当且仅当应该减去   的位都是0,应该加上   的位都是1。
对于每个模数都去DP这个不合法的是   的,显然不行。我们要继续挖掘性质。
我们发现,当   时,所有数都不合法。
当  是个偶数时,我们讨论第一位的取值。当第一位取   ,只要剩下的   位是   的倍数即可。当第一位取   ,只要剩下的   位是   的半倍数即可。
那么我们只需要解决   是个奇数的情况了。而且我们发现   在模奇数意义下在一个小环内!即实际上   存在循环节。
我们先将   的情况DP出来,我们可以直接利用这个DP的结果得到   的结果。需要做的只有将整个DP的答案左移即可。
实际上,当   时,只需要取出在   位处的DP值,并补上前   位的部分即可。
然后一个非常混乱的   的DP就能解决这个问题了。


我的做法稍有不同。

最后对于奇数部分,我们先DP出所有方案,为了减去不合法的,我们应当将DP反向倒退,由于此DP的特殊性,这一步相当于解一个方程,这个方程十分好解,于是就完成了。。


#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#define Rep(i, x, y) for (int i = x; i <= y; i ++)
#define Dwn(i, x, y) for (int i = x; i >= y; i --)
#define RepE(i, x) for(int i = pos[x]; i; i = g[i].nex)
using namespace std;
typedef long long LL;
const int N = 2005, mod = 1000000007, p2 = (mod + 1) >> 1;
LL p0, ans, f[N][N], h[N], g[N];
int d[N], n, m; bool vis[N];
class SemiMultiple {
public:
	void Find(int x, int y) {
		while (x --) {
			memset (vis, 0, sizeof(vis));
			Rep(i, 0, m - 1) if (!vis[i]) {
				LL sum = 0;
				for (int j = i; !vis[j]; j = (j + y) % m) {
					(sum += h[j]) %= mod, vis[j] = 1;
				} sum = sum * p2 % mod;
				bool fl = 0; int j = i;
				do {
					if (fl) sum = (sum - h[j] + mod) % mod;
					fl ^= 1; j = (j + y) % m;
				} while (j != i);
				// h[m+i-y] = sum;
				j = i; do {
					h[j] = (h[j] - sum + mod) % mod; sum = h[j]; j = (j + y) % m;
				} while (j != i);
			}
		}
	}
	int count(int n1, int m1) {
		n = n1, m = m1;
		while (n && m % 2 == 0) m /= 2, p0 ++, n --;
		int r = 1; f[0][0] = 1;
		Rep(i, 1, n) {
			d[r] ++;
			Rep(j, 0, m - 1) f[i][j] = f[i - 1][j];
			Rep(j, 0, m - 1) (f[i][(j + r) % m] += f[i - 1][j]) %= mod;
			r = r * 2 % m;
		} // f[i][j] choose any of them    mod m == j
		ans = p0 * f[n][0] % mod;
		Rep(i, 1, m - 1) if (d[i] || d[m - i]) {
			// int x = d[i];
			(ans += f[n][i]) %= mod;
			Rep(j, 0, m - 1) h[j] = f[n][j];
			Find(d[i], i), Find(d[m - i], m - i);
			Rep(t0, 1, d[m - i]) {
				Rep(j, 0, m - 1) g[(j + m - i) % m] = h[j];
				Rep(j, 0, m - 1) h[j] = g[j];
			}
			ans = (ans - h[i] + mod) % mod;
		}
		return ans;
	}
};


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值