闲话 22.10.26
虽然但是今天又是自由时间
于是摆 写了点题
调了一上午分治 FFT
好吧最后还是贺了(
今日是sandom的生日!
祝她生日快乐!
蛋糕好吃的
所以对于一个度数极小的多项式 \(F(x)\),有没有什么方式求得
其中 \(m\le 10^{18}\) ?
请各位帮我解惑
好耶生活水平再次提升(
但是有一件事情 某些人在使用novelai获得高质量图片
但我既没n卡也没条件
行吧
杂题
有 \(n\) 个品种的物品,每个物品的价格为 \(c_i\),每次你可以从你拥有的物品中挑取部分去商店去兑换,也可以什么都不拿。兑换有如下限制:
你拿去兑换的物品(不妨设拿去兑换的物品可空集合为 \(S\)),都要换成集合 \(S\) 所没有的。比如 \((a,b) \to (v,a)\) 就是不合法的。
你拿去兑换的物品集 \(S_1\) 里的价值和 \(\mathit{cost}_1\) 与你兑换回来的物品集 \(S_2\) 里的价值和 \(\mathit{cost}_2\) 需要满足:\(\mathit{cost}_1 + d \ge \mathit{cost}_2\)。
每天只能去兑换一次。问通过兑换可以获得的物品集的价值和最大值,以及所需的兑换天数。
\(n \le 50, a_i \le 10^4\)。
贪心水题。
有一个比较显然的贪心策略,即每次都换取价值和最大的物品,这样能使得兑换天数最小。假设当前持有物品的价值和为 \(C\),则由题意可以知道一次换取后可以持有的物品集合价值和范围为 \([C, C + d]\)。
考虑如何优化贪心。
我们首先需要知道有哪些价值和能被选择,这可以通过一次 01 背包在 \(O(n^2m)\) 的时间复杂度内得到。
然后从价值和为 0 的状态开始求解答案。若当前状态为 \(C\),则只需倒序扫描 \([C+1,C+d]\) 范围内的元素可及性,若存在一个价值和可以被选择,则其一定能通过当前状态兑换得到,因此更新答案继续进行即可。反之当前状态一定是兑换次数最少且价值和最大的状态,输出扫描次数及当前状态即可。
第二部分的复杂度大致是 \(O(\frac {nm}d)\) 的,然而不是很会证。大佬请来证
因此总时间复杂度 \(O(n^2m)\)。实现很简单。
code
#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for (register int i = (a), i##_ = (b) + 1; i < i##_; ++i)
#define pre(i,a,b) for (register int i = (a), i##_ = (b) - 1; i > i##_; --i)
const int N = 1e4 + 10;
int n, d, bnd, a[51];
int f[N * 51];
signed main() {
cin >> n >> d; rep(i,1,n) cin >> a[i];
sort(a+1, a+1+n);
f[0] = 1; bnd = 0;
rep(i,1,n) {
pre(j,bnd,0) if (f[j]) f[j + a[i]] = 1;
bnd += a[i];
}
int lst, now = 0;
rep(i,1,n<<1) {
lst = now;
pre(j,lst+d,lst) if (f[j]) { now = j; break; }
if (now == lst) {
cout << now << ' ' << i-1 << endl;
return 0;
}
}
}
给定 \(n\) 个点,每个点有黑白两种颜色。有一部分未确定颜色的点,你可以把它任意涂成黑色或白色。你可以在这个图上任意加入一些边(当然不能加入重边或自环),要求加入的边必须从编号小的点指向编号大的点。
我们称一条好的路径为经过的点为黑白相间的路径,如果一个图好的路径的总数 \(\text{mod } 2=p\),那么我们称这个图为好的图。现在给定你 \(n\) 个点的颜色,求这 \(n\) 个点能组成的好的图的个数,答案对 \(10^9 + 7\) 取模。
\(n \le 5\times 10^6\)。
给数据范围加了 5 个 0 后是 dp 好题。
套路按编号顺序加点。
我们设 \(f_{i,j,k,l}\) 表示加入了 \(i\) 个节点,当前节点中有 \(j\) 个偶数条路径以该点结束的黑色节点,当前节点中有 \(k\) 个偶数条路径以该点结束的白色节点,当前节点中有 \(l\) 个奇数条路径以该点结束的黑色节点。分别记作偶黑点,偶白点,奇黑点。剩下的叫奇白点。
你发现这玩意的状态似乎是 \(O(n^4)\) 的。但没关系,我们接着推。
然后讨论当前加入一个白点。
- 当前节点成为奇白点。这时需要奇数个奇黑点和任意偶黑点和当前点链接。考虑首先拿出一个奇黑点,则我们从剩下的奇黑点中选择任意个都可以通过控制这个点是否选择来保证选出节点的数量是奇数个,因此这部分的系数是 \(2^{l-1}\)。偶黑点可以任意选择,因此这部分的系数为 \(2^k\)。选择白点对路径条数的奇偶性没有贡献,因此乘法原理可得系数为 \(2^j \times 2^k \times 2^{l-1} \times 2^{i-1-j-k-l} = 2^{i-2}\)。
- 当前节点成为偶白点。这时需要偶数个奇黑点和任意偶黑点和他链接。做法和上方类似,答案也是 \(2^{i-2}\)。
- 当前不存在奇黑点。你发现上述的所有讨论都没法成立。这时我们只能将这个白点变成偶白点,因此贡献为 \(2^{i-2} \times 2 = 2^{i-1}\)。
黑点同理。
这样做的复杂度是 \(O(n^4)\) 的。
考虑进一步压缩状态。
我们发现,偶数节点对答案的贡献可以忽略,真正控制贡献情况的是奇白/黑点的存在与否。
我们考虑设 \(f_{i,j,k,l}\) 表示 \(i\) 个节点,总路径数的奇偶性是 \(j\),奇白点的存在性是 \(k\),奇黑点的存在性是 \(k\)。
这样状态数就被压缩为了 \(O(n)\)。
然后考虑转移。
重复上面关于白点的讨论,我们能得到
- 当前存在奇黑点。因此该白点可以选择成为奇黑点或偶黑点。两种转移的系数均为 \(2^{i-2}\)。
当成为偶黑点时,啥都没变。因此得到转移\[f_{i,j,k,l} \leftarrow 2^{i-2}\times f_{i-1,j,k,l} \]当成为奇黑点时,状态中 \(k\) 定为 \(\text{true}\),且路径奇偶性反转。因此得到转移\[f_{i,\text{!}j,1,l} \leftarrow 2^{i-2}\times f_{i-1,j,k,l} \] - 当前不存在奇黑点。该白点只能成为偶黑点。状态里啥都没变。系数为 \(2^{i-1}\)。\[f_{i,j,k,l} \leftarrow 2^{i-1}\times f_{i-1,j,k,l} \]
转移即可。时间复杂度 \(O(n)\)。
code
#include <bits/stdc++.h>
using namespace std;
#define inline __attribute__((always_inline))
#define rep(i,a,b) for (register int i = (a), i##_ = (b) + 1; i < i##_; ++i)
#define pre(i,a,b) for (register int i = (a), i##_ = (b) - 1; i > i##_; --i)
const int N = 55, mod = 1e9 + 7;
int n, p, a[N], f[N][2][2][2], qp[N]; // i个点,路径条数 & 1,奇路径白点数>0,奇黑点数>0
inline int add(int a, int b) { return (a += b) >= mod ? a - mod : a; }
signed main() {
cin >> n >> p; qp[0] = 1;
rep(i,1,n) cin >> a[i], qp[i] = (qp[i-1] << 1) % mod;
f[0][0][0][0] = 1;
rep(i,1,n) rep(j,0,1) rep(k,0,1) rep(l,0,1) {
int cont = 1ll * f[i-1][j][k][l] * qp[i - 2] % mod;
if (a[i] != 0) {
if (k) {
f[i][j][k][l] = add(f[i][j][k][l], cont);
f[i][j^1][k][l|1] = add(f[i][j^1][k][l|1], cont);
} else {
f[i][j^1][k][l|1] = add(f[i][j^1][k][l|1], i == 1 ? f[i-1][j][k][l] : add(cont, cont));
}
}
if (a[i] != 1) {
if (l) {
f[i][j][k][l] = add(f[i][j][k][l], cont);
f[i][j^1][k|1][l] = add(f[i][j^1][k|1][l], cont);
} else {
f[i][j^1][k|1][l] = add(f[i][j^1][k|1][l], i == 1 ? f[i-1][j][k][l] : add(cont, cont));
}
}
}
cout << add(add(f[n][p][0][0], f[n][p][1][0]), add(f[n][p][0][1], f[n][p][1][1]));
}
这里放一下 \(O(\frac {n \log^2 n}{\log \log n})\) 的分治 FFT。
本质上这个东西的核心是平衡思想,小范围暴力大范围正常(
所以改一下 \(\text{plain(F, G, len)}\) 就可以自如适配了(
不知道什么时候会写一下挑战多项式。
板子
#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for (register int i = (a), i##_ = (b) + 1; i < i##_; ++i)
#define pre(i,a,b) for (register int i = (a), i##_ = (b) - 1; i > i##_; --i)
const int N = 3e5 + 10, mod = 998244353, g = 3, B = 5; typedef unsigned long long ull;
int n;
int pool[N << 4], F[N], G[N];
int qp(int a, int b) {
int ret = 1;
while (b) {
if (b & 1) ret = 1ll * ret * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
} return ret;
}
int limit, trs[N], w[N], lgv[N];
void init(int n) {
limit = 1; while (limit < n) limit <<= 1;
for (int i = 1; i < limit; ++ i) trs[i] = (trs[i >> 1] >> 1) | (( i & 1 ) ? limit >> 1 : 0);
for (int i = 1; i < limit; i <<= 1) {
w[i] = 1; int w0 = qp(g, (mod - 1) / (i << 1));
rep(j,1,i-1) w[i + j] = 1ll * w[i + j - 1] * w0 % mod;
}
rep(i,2,limit) lgv[i] = lgv[i >> 1] + 1;
}
void DFT(int A[], int len) {
int t = lgv[limit / len];
static ull tmp[N];
rep(i,0,len-1) tmp[trs[i] >> t] = A[i];
for (int i = 1; i < len; i <<= 1) for (int j = 0; j < len; j += i << 1) for (int k = 0; k < i; ++ k) {
int v = 1ll * tmp[i + j + k] * w[i + k] % mod;
tmp[i + j + k] = tmp[j + k] + mod - v; tmp[j + k] += v;
}
for (int i = 0; i < len; ++ i) A[i] = tmp[i] % mod;
}
void IDFT(int A[], int len) {
reverse(A+1, A+len); DFT(A, len);
int iv = mod - (mod - 1) / len;
for (int i = 0; i < len; ++ i) A[i] = 1ll * A[i] * iv % mod;
}
void plain(int F[], const int G[], int l, int r) {
if (!l) F[l] = 1;
rep(i,l+1,r) rep(j,l,i-1) F[i] = (F[i] + 1ll * F[j] * G[i - j]) % mod;
}
int *_f[20][1 << B], *_g[20][1 << B];
void dac_FFT(int F[], int G[], int l, int r, int dep) {
if (r - l + 1 <= 128) { plain(F, G, l, r); return; }
static int tmp[N];
int d = 1 << ((dep - 1) * B);
for (int i = 0; ; ++ i) {
int L = l + i * d, R = min(r, L + d - 1);
if (i) {
fill(tmp, tmp + (d << 1), 0);
for (int j = 0; j < i; ++ j) for (int k = 0; k < (d << 1); ++ k)
tmp[k] = (tmp[k] + 1ll * _f[dep][j][k] * _g[dep][i-j][k]) % mod;
IDFT(tmp, d << 1);
rep(j,L,R) F[j] = (F[j] + tmp[j - L + d]) % mod;
}
dac_FFT(F, G, L, R, dep - 1);
if (R == r) return;
fill(_f[dep][i], _f[dep][i] + (d << 1), 0);
copy(F+L, F+R+1, _f[dep][i]);
DFT(_f[dep][i], d << 1);
}
}
void dac(int * F, int * G, int n) {
fill(F, F+n, 0);
if (n <= 128) { plain(F, G, 0, n-1); return ; }
int len = 1, dep = 0;
while (len < n) len <<= B, dep ++; len >>= B;
int *p = pool;
rep(i,1,dep) {
int d = 1 << ((i-1) * B), tn = min((1 << B) - 1, (n - 1) / d);
rep(j,1,tn) {
int L = (j - 1) * d + 1, R = min(n - 1, (j + 1) * d - 1);
_f[i][j-1] = p, p += (d << 1);
_g[i][j] = p, p += (d << 1);
fill(_g[i][j], _g[i][j] + (d << 1), 0);
copy(G+L, G+R+1, _g[i][j] + 1);
DFT(_g[i][j], d << 1);
}
}
dac_FFT(F, G, 0, n-1, dep);
}
signed main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n; init(n);
for (int i = 1; i < n; ++ i) cin >> G[i];
dac(F, G, n);
for (int i = 0; i < n; ++ i) cout << F[i] << ' ';
}
以下是博客签名,与正文无关。
请按如下方式引用此页:
本文作者 joke3579,原文链接:https://www.cnblogs.com/joke3579/p/chitchat221026.html。
遵循 CC BY-NC-SA 4.0 协议。
请读者尽量不要在评论区发布与博客内文完全无关的评论,视情况可能删除。