闲话 22.12.11

闲话

我发现我浏览器抽风所以我随时保存

上面那个是我改标题为[闲话+日期]前的暂标题/占位符
以前的暂标题就很暂(
有好奇的读者可以猜一猜

就是 今天看到了……忘记了 但是点进了 ARC106D
然后就浅做了一下

又是荒废(?)网络流的一天呢(
推流jjdw的鲜花

感觉这两天下载上传资料电脑卡顿程度直线上升
《感觉是该清灰了》
《其实我的浏览器经常有黑屏啦倒是 我也不知道为什么》
《点开我的浏览器的时候有一定的概率》
《得按照一定的顺序 : (》
《这不是挺好的嘛》
妈妈生的(即答

杂题

ARC106D

给定长度为 \(n\) 的序列 \(a\),以及一个整数 \(k\)

对于每个 \(1\le x \le k\),求出如下式子的值:

\[\sum_{l=1}^{n-1}\sum_{r=l+1}^n \left(a_l + a_r\right)^ x \]

答案对 \(998244353\) 取模。

\(2\le n \le 2\times 10^5,\ 1 \le k \le 300, \ 1\le a_i \le 10^8\)

不难发现,

\[\sum_{l=1}^{n}\sum_{r=1}^n \left(a_l + a_r\right)^x = 2\sum_{l=1}^{n-1}\sum_{r=l+1}^n \left(a_l + a_r\right)^x +\sum_{l=1}^{n}\left(a_l + a_l\right)^x \]

于是可以计算

\[\sum_{l=1}^{n}\sum_{r=1}^n \left(a_l + a_r\right)^x \]

减去

\[\sum_{l=1}^{n}\left(a_l + a_l\right)^x \]

后除二就是答案。

根据二项式定理,可以得到

\[\begin{aligned} &\sum_{l=1}^{n}\sum_{r=1}^n \left(a_l + a_r\right)^x \\ = \ & \sum_{l=1}^{n}\sum_{r=1}^n\sum_{i=1}^x \binom{x}{i} a_l^i a_r^{x-i} \\ = \ & \sum_{i=1}^x \sum_{l=1}^{n}\sum_{r=1}^n\frac{a_l^i}{i!} x!\frac{a_r^{x-i}}{(x - i)!} \\ = \ & \sum_{i=1}^x x!\left(\sum_{l=1}^{n}\frac{a_l^i}{i!}\right) \left(\sum_{r=1}^n\frac{a_r^{x-i}}{(x - i)!}\right) \end{aligned}\]

由于 \(x\) 的范围小,可以支持 \(O(k^2)\) 的复杂度,我们可以枚举 \(x\)\(i\) 合并答案。

对每个 \(1\le m\le k\) 预处理

\[\sum_{l=1}^{n}\frac{a_l^m}{m!} \]

即可做到 \(O(k^2)\) 合并答案。合并答案也可应用卷积,但复杂度瓶颈不在这里。

朴素合并的总时间复杂度为 \(O(nk + k^2)\)

code
#include <bits/stdc++.h>
using namespace std;
template <typename T> void get(T & x) {
	x = 0; char ch = getchar(); bool f = false; while (ch < '0' or ch > '9') f = f or ch == '-', ch = getchar();
	while ('0' <= ch and ch <= '9') x = (x << 1) + (x << 3) + ch - '0', ch = getchar(); f && (x = -x); 
} template <typename T, typename ... Args> void get(T & a, Args & ... b) { get(a); get(b...); }
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
const int N = 4e5 + 10;
int n, k, sum[305], a[N], v[305];

signed main() {
	get(n, k); rep(i,1,n) get(a[i]);
    init_fac(305);
    v[0] = n;
    rep(j,1,n) {
        int t1 = 1, t2 = 1;
        rep(i,1,k) {
            t1 = mul(t1, a[j]), t2 = mul(t2, 2 * a[j]);
            v[i] = add(v[i], mul(t1, ifc[i]));
            sum[i] = add(sum[i], t2);
        }
    }
    rep(x,1,k) {
        int ans = 0;
        rep(i,0,x) ans = add(ans, mul(v[i], v[x - i]));
        ans = mul(ans, fac[x]);
        ans = sub(ans, sum[x]);
        cout << mul(ans, inv2) << '\n';
    }
}



ARC106E

你有 \(n\) 个朋友,他们会来你家玩,第 \(i\) 个人 \(1\sim A_i\) 天来玩,然后 \(A_i+1\sim 2A_i\) 天不来,然后 \(2A_i+1\sim 3A_i\) 天又会来,以此类推。

每天你会选一个来玩的人,给他颁个奖,如果没人来玩,你就不颁奖。你要给每个人都颁 \(K\) 个奖,问至少需要多少天。

\(1\le n\le 18, \ 1\le k \le 10^5, \ 1\le A_i \le 10^5\)

二分答案转化为判定。

判定的话考虑建二分图。假设当前的天数是 \(t\),左部点是 \(t\) 天,右部点是 \(n \times k\) 个人对应的奖。每个人和他会来玩的那些天连边,流就是奖。最后看是不是能流满即可。
我们发现,这就是在寻找二分图完全匹配。由于人数少,考虑把一天来的人压成状态。施 Hall 定理,我们只需要考虑每个状态 \(i\) 能和多少天连边,也就是有多少天对应的状态和它有交。假设这个值是 \(s\),则该状态满足要求当且仅当 \(s \ge \text{popcount}(i) \times k\)。所有状态满足要求则 \(t\) 满足要求。
考虑求有多少天的状态是状态 \(i\) 的补集的子集,再用 \(t\) 减去就是状态 \(i\)\(s\)。应用 SOS DP 可以得到答案。

二分边界?容易发现 \(2nk\) 一定满足要求。
总时间复杂度 \(O(n2^n\log (nk))\)

code
#include <bits/stdc++.h>
using namespace std; using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long; 
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
mt19937 rnd(chrono::steady_clock::now().time_since_epoch().count());
template <typename T> T rand(T l, T r) { return uniform_int_distribution<T>(l, r)(rnd); }
template <typename T> void get(T & x) {
	x = 0; char ch = getchar(); bool f = false; while (ch < '0' or ch > '9') f = f or ch == '-', ch = getchar();
	while ('0' <= ch and ch <= '9') x = (x << 1) + (x << 3) + ch - '0', ch = getchar(); f && (x = -x); 
} template <typename T, typename ... Args> void get(T & a, Args & ... b) { get(a); get(b...); }
#define iot ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
template <typename T1, typename T2> T1 min(T1 a, T2 b) { return a < b ? a : b; }
template <typename T1, typename T2> T1 max(T1 a, T2 b) { return a > b ? a : b; }
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define ufile(x) 
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
#define Aster(i, s) for (int i = head[s], v; i; i = e[i].next)
#define all(s) s.begin(), s.end()
#define pcnt(x) __builtin_popcount(x)
#define eb emplace_back
#define pb pop_back
#define em emplace
const int N = 1e7 + 10;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int n, k, m, a[N], f[N];

int g[N];
bool check(int d) {
    rep(i,0,(1<<n)-1) g[i] = 0;
    rep(i,1,d) g[f[i]] ++;
    rep(i,0,n-1) rep(j,0,(1<<n)-1) {
        if (!(j >> i & 1)) 
            g[j | (1 << i)] += g[j];
    } 
    rep(i,0,(1<<n)-1) if (d - g[(1 << n) - 1 - i] < pcnt(i) * k) return false;
    return true;
}

signed main() {
	get(n, k); rep(i,1,n) get(a[i]);
    m = 2 * n * k;
    rep(i,1,m) rep(j,1,n) if (((i + a[j] - 1) / a[j]) & 1) f[i] |= 1 << j - 1;
    int l = 1, r = m, mid;
    while (l <= r) {
        mid = l + r >> 1;
        if (check(mid)) r = mid - 1;
        else l = mid + 1;
    }
    cout << r + 1 << '\n';
}



ARC106F

\(n\) 个点,每个点有 \(a_i\) 个孔,每次可以选择两个不同点,连接两个未被连接过的孔,有多少中方案使得最后形成一棵树。
答案对 \(998244353\) 取模。

\(2\le n \le 2\times 10^5, \ 1\le d_i \le 998244352\)

\(\text{Solution 1}\) 生成法计数

官方题解采用的方法。

首先可以发现只有当 \(\sum d_i \ge 2(n - 1)\) 时有解。

随后考虑如何生成所有可能的方案。我们称一个联通的子图为一个分量,最开始有 \(n\) 个分量。
最开始在每个分量上选取一个特殊点。随后按如下方式操作:

  • 将分量数量减小到 \(2\)
  1. 选择一个非特殊点 \(a\)
  2. 选择一个不与点 \(a\) 位于同一分量中的特殊点 \(b\)
  3. 连接 \(a,b\)。新分量的特殊点是其中一个距离 \(a,b\) 最近的可连边的点。
  • 连接最后的两个分量上的特殊点。

最开始选择特殊点的方案数是 \(\prod_{i=1}^n d_i\)。记 \(S = \sum_{i=1}^n d_i\),第 \(i\) 次选择时非特殊点数量为 \((S - n - (i-1))\) 个,能连接的联通块数是 \((n - i)\) 个,自然导出了方案数

\[\prod_{i=1}^n d_i\times \prod_{i=1}^{n-2} (n - i) (S - n - (i - 1)) = \prod_{i=1}^n d_i\times (n - 1)!\times \prod_{i=0}^{n-3} (S - n - i) \]

考虑生成方式,我们发现同一棵树会以不同的加边顺序在答案中被统计 \((n-1)!\) 次,因此最终答案为

\[\prod_{i=1}^n d_i\times \prod_{i=0}^{n-3} (S - n - i) \]

\(\text{Solution 2}\) 生成函数

首先固定每个点 \(i\) 在最终生成的树中的度数 \(r_i\)

由 prufer 序列导出结论,一棵满足上述度数条件的树的计数是

\[\frac{(n - 2)!}{ \prod_{i=1}^n (r_i - 1)! } \]

证明考虑计数 prufer 序列,元素 \(i\) 在其中出现了 \(r_i - 1\) 次。

每个点在连接时是有序的,因此在度数固定为 \(r\) 的情况下,贡献是

\[\frac{(n - 2)!}{ \prod_{i=1}^n (r_i - 1)! }\times \prod_{i=1}^n A_{d_i}^{r_i} \]

可以发现 \(\sum r_i = 2(n - 1)\)。因此不妨从卷积的角度入手,枚举 \(r_i\) 的取值。

发现 \((n - 2)!\) 是所有可能都会贡献的,因此只需要生成剩余部分即可。

\(F_i(x)\) 为点 \(i\) 对答案的贡献的生成函数,有

\[\begin{aligned} F_i(x) &= \sum_{k=0}^{\infty} \frac{A_{d_i}^k}{(k - 1)!}x^k \\ & = \sum_{k=0}^{\infty} \binom{d_i} k k \ x^k \\ & = d_i \sum_{k=1}^{\infty} \binom{d_i - 1} {k - 1}\ x^k \\ & = d_i x \sum_{k=0}^{\infty} \binom{d_i - 1} {k} \ x^k \\ & = d_i x (1 + x)^{d_i - 1} \end{aligned}\]

可以写出答案

\[\begin{aligned} & (n - 2)!\times [x^{2(n-1)}] \prod_{i=1}^nF_i(x) \\ = \ & (n - 2)!\times [x^{2(n-1)}] \prod_{i=1}^n d_i x (1 + x)^{d_i - 1} \\ = \ & (n - 2)!\times\prod_{i=1}^n d_i \times [x^{n - 2}] \prod_{i=1}^n (1 + x)^{d_i - 1} \end{aligned}\]

\(S = \sum_{i=1}^n d_i\),有

\[\begin{aligned} & (n - 2)!\times\prod_{i=1}^n d_i \times [x^{n - 2}] \prod_{i=1}^n (1 + x)^{d_i - 1} \\ = \ & (n - 2)!\times\prod_{i=1}^n d_i \times [x^{n - 2}] (1 + x)^{S - n} \\ = \ & (n - 2)!\times\prod_{i=1}^n d_i \times \binom{S - n}{n-2} \\ = \ & \prod_{i=1}^n d_i \times (S - n)^{\underline{n - 2}} \\ = \ & \prod_{i=1}^n d_i\times \prod_{i=0}^{n-3} (S - n - i) \end{aligned}\]

两种做法得到了相同的答案。
总时间复杂度为 \(O(n)\)

个人认为 \(\text{Solution 2}\) 更简单易懂一些。

code
#include <bits/stdc++.h>
using namespace std; 
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
const int mod = 998244353;
int n, prod = 1, sum, tmp;
signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n; 
    rep(i,1,n) {
        cin >> tmp; 
        prod = 1ll * prod * tmp % mod, sum += tmp;
        if (sum >= mod) sum -= mod;
    }
    rep(i,0,n-3) prod = 1ll * prod * (sum - n - i) % mod;
    cout << prod << '\n';
}
posted @ 2022-12-11 20:39  joke3579  阅读(46)  评论(0编辑  收藏  举报