P3702 [SDOI2017]序列计数 题解

本文介绍了解决SDOI2017竞赛中“序列计数”问题的方法,采用正难则反策略,通过计算总方案数减去全为合数的情况。利用动态规划和卷积技巧完成高效求解。

P3702 [SDOI2017]序列计数

P3702 [SDOI2017]序列计数

首先这个真的要骂一下自己,这个第一步显然就是正难则反。如果直接考虑进行 D p \tt Dp Dp 是否包含质数不是很方便我们可以直接使用容斥来做。

我们考虑正难则反进行计算,也就是计算总方案减去全部是合数的方案。

d p ( i , j , k ) dp(i, j, k) dp(i,j,k) 表示考虑到第 i i i 位,总和 m o d    p = j \mod p = j modp=j 是否有质数的方案。

之后考虑转移本质上就是枚举每一个数然后将之前的数加上这个数的贡献。

也就是 d p ( i , j ) → d p ( i + 1 , j + c ) dp(i, j) \to dp(i + 1, j + c) dp(i,j)dp(i+1,j+c) 。我们把这个 j j j 当做上指标那么就是一个卷积的形式。

对于 n n n 1 0 9 10^9 109 级别我们直接进行快速幂即可,至于卷积可以直接暴力循环卷积。

#include <bits/stdc++.h>
using namespace std;

//#define Fread
#define Getmod

#ifdef Fread
char buf[1 << 21], *iS, *iT;
#define gc() (iS == iT ? (iT = (iS = buf) + fread (buf, 1, 1 << 21, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
#define getchar gc
#endif // Fread

template <typename T>
void r1(T &x) {
	x = 0;
	char c(getchar());
	int f(1);
	for(; c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
	for(; '0' <= c && c <= '9';c = getchar()) x = (x * 10) + (c ^ 48);
	x *= f;
}

template <typename T,typename... Args> inline void r1(T& t, Args&... args) {
    r1(t);  r1(args...);
}

#ifdef Getmod
const int mod  = 20170408;
template <int mod>
struct typemod {
    int z;
    typemod(int a = 0) : z(a) {}
    inline int inc(int a,int b) const {return a += b - mod, a + ((a >> 31) & mod);}
    inline int dec(int a,int b) const {return a -= b, a + ((a >> 31) & mod);}
    inline int mul(int a,int b) const {return 1ll * a * b % mod;}
    typemod<mod> operator + (const typemod<mod> &x) const {return typemod(inc(z, x.z));}
    typemod<mod> operator - (const typemod<mod> &x) const {return typemod(dec(z, x.z));}
    typemod<mod> operator * (const typemod<mod> &x) const {return typemod(mul(z, x.z));}
    typemod<mod>& operator += (const typemod<mod> &x) {*this = *this + x; return *this;}
    typemod<mod>& operator -= (const typemod<mod> &x) {*this = *this - x; return *this;}
    typemod<mod>& operator *= (const typemod<mod> &x) {*this = *this * x; return *this;}
    int operator == (const typemod<mod> &x) const {return x.z == z;}
    int operator != (const typemod<mod> &x) const {return x.z != z;}
};
typedef typemod<mod> Tm;
#endif

//#define int long long
const int maxn = 3e2 + 5;
const int maxm = 2e7 + 5;

int n, m, p;

int pr[maxm], vis[maxm], tot;
void init(int x) {
    for(int i = 2; i <= x; ++ i) {
        if(!vis[i]) pr[++ tot] = i;
        for(int j = 1, k; k = i * pr[j], k <= x && j <= tot; ++ j) {
            vis[k] = 1;
            if(!(i % pr[j])) break;
        }
    }
}

Tm f[maxn], g[maxn];
Tm ansf[maxn], ansg[maxn];

Tm res[maxn];

void mul(Tm *a,Tm *b) {
    for(int i = 0; i < p; ++ i) for(int j = 0; j < p; ++ j)
        res[i + j] += a[i] * b[j];
    for(int i = 0; i < p; ++ i) a[i] = res[i] + res[i + p], res[i] = res[i + p] = Tm(0);
}

signed main() {
//    freopen("S.in", "r", stdin);
//    freopen("S.out", "w", stdout);
    int i, j;
    r1(n, m, p);
    init(m);
    f[1] = g[1] = ansf[0] = ansg[0] = 1;
    Tm ans(0);
    for(i = 2; i <= m; ++ i) {
        f[i % p] += 1;
        if(vis[i]) g[i % p] += 1;
    }
    while(n) {
        if(n & 1) mul(ansf, f), mul(ansg, g);
        n >>= 1;
        mul(f, f), mul(g, g);
    }
//    printf("%d %d\n", ansf[0].z, ansg[0].z);
    ans = ansf[0] - ansg[0];
    printf("%d\n", ans.z);
	return 0;
}

下面是题目,代码超时一个点,请你解释为什么超时。 翻译成英文。 题目描述 徐老师有一个包含 n 个整数的序列 a,分别为 \(a_1, a_2, a_3 \ldots a_n\) 他可以选择一个正整数 k 进行一个特殊的操作 —— \(a \% k\) 也就是将 a 序列中的所有数字 \(\% k\) 现在徐老师想知道,如果他可以任意选择 k 进行 \(a \% k\),并且不限操作次数 最终可能产生多少种不同的序列? 输入格式 输入第一行包含一个整数 n 表示数字个数 输入第二行包含 n 个整数,分别为 \(a_1, a_2, a_3 \ldots a_n\) 输出格式 输出一个答案表示不同的序列个数,由于答案可能很大,请你将答案对 \(10^9 + 7\) 取模 数据范围 对于所有的数据满足:\(0 \leq n, a_i \leq 500\) 其中数据点分别满足以下情况 | 测试点 | 特殊性质 | | ------- | --------------- | | 1 ~ 2 | \(n = 2\) | | 3 ~ 5 | \(a_i \leq 20\) | | 6 ~ 10 | \(a_i = i\) | 样例输入1 ``` 3 1 3 4 ``` 样例输出1 ``` 6 ``` 样例解释1 可以执行 \(a \% 5\),得到序列 \(\{1, 3, 4\}\) 可以执行 \(a \% 4\),得到序列 \(\{1, 3, 0\}\) 可以执行 \(a \% 3\),得到序列 \(\{1, 0, 1\}\) 可以执行 \(a \% 2\),得到序列 \(\{1, 1, 0\}\) 可以执行 \(a \% 1\),得到序列 \(\{0, 0, 0\}\) 可以依次执行 \(a \% 4, a \% 3\),得到序列 \(\{1, 0, 0\}\) 样例输入2 ``` 10 1 2 3 4 5 6 7 8 9 10 ``` 样例输出2 ``` 256 ``` 代码: #include <bits/stdc++.h> using namespace std; constexpr int MOD = 1000000007; constexpr int N = 507; class mod { private: int n; vector<int> sequence; int minValue; void init() { minValue = INT_MAX; for (int num : sequence) { if (num < minValue) { minValue = num; } } } int state[N][N]; public: mod(int size, const vector<int> &seq) : n(size), sequence(seq) { init(); } void process() { if (n == 2) { two(); } else if (minValue <= 20) { s(); } else { gen(); } } private: void two() { memset(state, 0, sizeof(state)); state[sequence[0]][sequence[1]] = 1; for (int modulus = 500; modulus >= 1; --modulus) { for (int i = 0; i <= 500; ++i) { for (int j = 0; j <= 500; ++j) { if (state[i][j]) { state[i % modulus][j % modulus] = 1; } } } } int res = 0; for (int i = 0; i <= 500; ++i) { for (int j = 0; j <= 500; ++j) { res += state[i][j]; } } cout << res << "\n"; } void s() { set<string> st; vector<int> t(n); string s(n, 'A'); for (int mask = 0; mask < (1 << 20); ++mask) { for (int i = 0; i < n; ++i) { t[i] = sequence[i]; } for (int bit = 20; bit >= 1; --bit) { if (mask & (1 << (bit - 1))) { for (int idx = 0; idx < n; ++idx) { t[idx] %= bit; } } } for (int idx = 0; idx < n; ++idx) { s[idx] = '0' + t[idx]; } st.insert(s); } cout << st.size() << "\n"; } int calc(int x) { memset(state, 0, sizeof(state)); int remaining = n - x; state[0][0] = 1; for (int i = 1; i <= remaining; ++i) { for (int j = 0; j < x; ++j) { state[i][0] = (state[i][0] + state[i - 1][j]) % MOD; } for (int j = 1; j < x; ++j) { state[i][j] = state[i - 1][j - 1]; } } int total = 0; for (int i = 0; i < x; ++i) { total = (total + state[remaining][i]) % MOD; } return total; } void gen() { int res = 2; for (int i = 2; i <= n; ++i) { res = (res + calc(i)) % MOD; } cout << res; } }; int main() { int n; cin >> n; vector<int> a(n); for (int i = 0; i < n; ++i) { cin >> a[i]; } mod b(n, a); b.process(); return 0; }
最新发布
09-17
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值