abc321记录

发布时间 2023-09-25 17:55:36作者: Lucky_Luo

SuntoryProgrammingContest2023(AtCoder Beginner Contest 321) - AtCoder

D

题意:给定常数 \(k\) 和长度为 \(n\) 的数组 \(a\) 和长度为 \(m\) 的数组 \(b\),求 \(\sum_{i=1}^n\sum_{j=1}^mmin\{a_i+b_j,k\}\)

数据范围:\(n,m \le 2\times10^5\)

tag : 二分 前缀和

枚举 \(a_i\),在 \(b\) 上二分 \(k-a_i\),找到的位置前面的前缀和后面的全是 \(k\)

E

题意:多个询问,每个询问给定 \(n\) 个点,根为 \(1\) 的一棵树,节点 \(u\) 的父亲为 \(\left\lfloor\frac{u}2\right\rfloor\),求与节点 \(v\) 距离为 \(k\) 的点的个数。

数据范围:\(k \le n \le 10^{18}\)

tag : 完全二叉树 树形dp

考虑类似换根dp的过程,设 \(g(u,k)\)\(u\) 子树中距离为 \(k\) 的点,这在完全二叉树上显然可以快速计算,计算当前点的答案 \(f(u, k)\) 时实际上是父亲的答案减去在自己子树里多算的部分,\(\begin{cases}f(u,k)=g(u,k)+f(fa,k-1)-g(u,k-2)\\f(1,k)=g(1,k)\end{cases}\),因为树高 \(O(\log n)\),直接向上暴力计算,注意最底层不满的 corner case 。

F

题意:\(n\) 个操作,每个操作在可重集 \(S\) 中加入或删除一个元素 \(k\) ,求每次操作后选取一个子集 \(S'\) 使得 \(\sum_{k\in S'}=m\) 的方案数。

数据范围:\(n,m \le 5000\)

tag : 背包 线段树分治

回退背包板子,注意加入顺序从大到小,回退顺序从小到大。但是赛时线段树分治直接艹过去了

G

题意:给定长度为 \(m\),值域为 \([1, n]\) 的数组 \(a, b\),对于每个 \(i\) 都对应一个互不相同的 \(j\),代表在图上编号为 \(a_i\) 的点向编号为 \(b_j\) 的点连一条双向边,求在所有的连边方式中图的连通块个数的期望。

数据范围:\(m\le10^5,n\le17\)

tag : 状压dp,容斥

赛后补题,参考了一定的lg题解。

显然总的连边方案数是确定的 \(m!\),那么考虑拆分每个连通块的贡献,计算每个连通块在所有连边方式中出现的次数。

注意到 \(n \le17\),考虑状压,设 \(f_S\) 表示点集 \(S\) 只有内部连边并形成至少一个连通块的方案数,\(g_S\) 表示点集 \(S\) 只有内部连边并形成恰好一个连通块的方案数,显然 \(f\) 是易求的,设 \(cnta_S,cntb_S\) 分别表示 \(S\) 的元素在 \(a, b\) 中出现的次数,当 \(cnta_S=cntb_S\) 时,\(S\) 内部可以随便连边,否则,必然有一条边连接 \(S\) 内部的点和外部的点,所以有:\(f_S=[cnta_S=cntb_S]\times cnta_S!\)

接下来考虑通过 \(f\) 求出 \(g\),显然可以容斥:\(S\) 形成恰好一个连通块的方案数等于形成至少一个连通块的方案数减形成多于一个连通块的方案数。而多于一个连通块的方案数显然等于一个子集形成恰好一个连通块的方案数乘以它关于 \(S\) 的补集形成至少一个连通块的方案数。这就可以写成转移方程了:\(g_S=f_S-\sum_{T\subsetneq S}g_T \times f_{S-T}\)

求完 \(g\) ,统计答案时还要乘上 \(S\) 外部随便连边的方案数:\(ans=\frac{\sum g_S \times (m-cnta_S)!}{m!}\)

贴个代码

#include <bits/stdc++.h>
#define pb push_back
#define rg register
#define il inline
using namespace std;
typedef long long ll;
//char buf[1<<21],*p1=buf,*p2=buf;
//#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
mt19937_64 rnd(chrono::steady_clock::now().time_since_epoch().count());
const int N = 1<<17, mo = 998244353;
int i, j, k, l, r, m, n, ans;
int a[N], b[N], fc[N], f[N], g[N];
il int read()
{
    int x = 0, flag = 1;
    char ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') flag = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
    return x * flag;
}
int qp(int a, int b=mo-2)
{
	int res = 1;
	for (a%=mo ; b; b>>=1, a=1ll*a*a%mo) if (b&1) res = 1ll * res * a % mo;
	return res;
}
signed main()
{
//	ios::sync_with_stdio(false);
//	cin.tie(0), cout.tie(0);
	n = read(), m = read();
	for (fc[0]=i=1; i<=m; i++) fc[i] = 1ll * i * fc[i-1] % mo;
	for (i=1; i<=m; i++) a[read()-1]++;
	for (i=1; i<=m; i++) b[read()-1]++;
	for (k=1; k<(1<<n); k++)
	{
		int sa = 0, sb = 0, p = -1;
		for (i=0; i<n; i++) if (k>>i&1) sa += a[i], sb += b[i], p = ~p ? p : i;
		if (sa != sb) continue;
		g[k] = f[k] = fc[sa];
		for (i=k; i; i=i-1&k) if (i!=k && i>>p&1) g[k] = (g[k] - 1ll * g[i] * f[i^k] % mo + mo) % mo;
		ans = (ans + 1ll * g[k] * fc[m-sa] % mo) % mo;
	}
	printf("%d", 1ll*ans*qp(fc[m])%mo);
    return 0;
}