来自学长的馈赠2 社论

T1 代码
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;
const int N = 2e5 + 7, P = 1e9 + 7;
inline void fstmod(int& x){if (x >= P) x -= P;}
namespace NumberTheory
{
	inline int qpow(int a, int n)
	{
		int ans = 1;
		while (n)
		{
			if (n & 1) ans = 1ll * ans * a % P;
			a = 1ll * a * a % P; n >>= 1;
		} return ans;
	}
	inline int inv(int x){return qpow(x, P-2);}
}
vi conv(const vi& a, const vi& b)
{
	assert(a.size() == b.size());
	int n; vi c(n = a.size());
	for (int i=0; i<n; i++)
		for (int j=0; j<n; j++) fstmod(c[i*j%n] += 1ll * a[i] * b[j] % P);
	return c;
}
vi qpow(vi a, int n)
{
	vi ans(a.size()); ans[1] = 1;
	while (n)
	{
		if (n & 1) ans = conv(ans, a);
		a = conv(a, a); n >>= 1;
	} return ans;
}
int n, m, mod;
vi a;
int main()
{
	scanf("%d%d%d", &n, &m, &mod); a.resize(mod); int inv2 = NumberTheory :: inv(n);
	for (int i=0, x; i<n; i++) scanf("%d", &x), ++a[x %= mod];
	for (int i=0; i<mod; i++) a[i] = 1ll * a[i] * inv2 % P;
	a = qpow(a, m);
	int ans = 0;
	for (int i=0; i<mod; i++) ans = (ans + 1ll * i * a[i] % P) % P;
	printf("%d\n", ans);
	return 0;
}
T2 代码
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e5 + 7;
int n, a[N], b[N];
vector<int> g[N];
inline void addedge(int u, int v){g[u].emplace_back(v);}
inline void ade(int u, int v){addedge(u, v); addedge(v, u);}
namespace encode
{
	int dfn[N], idf[N], dep[N], siz[N], cc, leaf;
	void dfs(int u, int fa)
	{
		dfn[idf[u] = ++cc] = u; siz[u] = 1; dep[u] = dep[fa] + 1;
		for (int v : g[u])
		{
			if (v == fa) continue;
			dfs(v, u); siz[u] += siz[v];
		}
		if (siz[u] == 1) leaf = u;
	}
	bool vis[N];
	int dist[N], s[N];
	inline int sum(int l, int r){return s[r] - s[l-1];}
	inline int subtreesum(int u){return sum(idf[u], idf[u]+siz[u]-1);}
	inline int bfs(int u)
	{
		memset(vis, false, sizeof vis);
		memset(dist, 0, sizeof dist);
		queue<int> q; q.push(u);
		while (!q.empty())
		{
			int u = q.front(); q.pop();
			if (vis[u]) continue;
			vis[u] = true;
			for (int v : g[u])
			{
				if (vis[v]) continue;
				dist[v] = dist[u] + 1; q.push(v);
			}
		}
		int ans = 0;
		for (int i=1; i<=n; i++) ans += a[i] * dist[i];
		return b[u] = ans;
	}
	inline void dfs2(int u, int fa)
	{
		for (int v : g[u])
		{
			if (v == fa) continue;
			int add, sub;
			if (dep[u] > dep[v]){add = subtreesum(u); sub = sum(1, cc) - add;}
			else{sub = subtreesum(v); add = sum(1, cc) - sub;}
			b[v] = b[u] + add - sub; dfs2(v, u);
		}
	}
	inline void main()
	{
		memset(b, 0, sizeof b); 
		for (int i=1; i<=n; i++) scanf("%d", a+i);
		cc = 0; dfs(1, 0);
		for (int i=1; i<=n; i++) s[i] = s[i-1] + a[dfn[i]];
		bfs(leaf); dfs2(leaf, 0);
		for (int i=1; i<=n; i++) printf("%d ", b[i]);
		puts("");
	}
}
namespace decode
{
	ll s[N], dif[N], sum;
	inline void dfs1(int u, int fa)
	{
		for (int v : g[u])
		{
			if (v == fa) continue;
			dif[v] = b[v] - b[u]; sum += dif[v];
			dfs1(v, u);
		}
	}
	inline void dfs2(int u, int fa)
	{
		a[u] = s[u];
		for (int v : g[u])
		{
			if (v == fa) continue;
			s[v] = (s[1] - dif[v]) / 2;
			a[u] -= s[v];
			dfs2(v, u);
		}
	}
	inline void main()
	{
		sum = 0;
		for (int i=1; i<=n; i++) scanf("%d", b+i);
		dfs1(1, 0); s[1] = (sum + (b[1] << 1)) / (n-1); dfs2(1, 0);
		for (int i=1; i<=n; i++) printf("%d ", a[i]);
		puts("");
	}
}
inline void solve()
{
	scanf("%d", &n);
	for (int i=1, u, v; i<n; i++) scanf("%d%d", &u, &v), ade(u, v);
	int type; scanf("%d", &type);
	if (type) decode :: main();
	else encode :: main();
	do g[n].clear(); while (n--);
}
int main()
{
	int T; scanf("%d", &T);
	while (T--) solve();
	return 0;
}
T3 代码
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 2e5 + 7, P = 1e9 + 7;
int n, type, fac[N], ifac[N];
inline int qpow(int a, int n)
{
	int ans = 1;
	while (n)
	{
		if (n & 1) ans = 1ll * ans * a % P;
		a = 1ll * a * a % P; n >>= 1;
	} return ans;
}
inline int inv(int x){return qpow(x, P-2);}
inline void init()
{
	fac[0] = ifac[0] = 1;
	for (int i=1; i<N; i++) fac[i] = 1ll * fac[i-1] * i % P;
	ifac[N-1] = inv(fac[N-1]);
	for (int i=N-2; i>=1; i--) ifac[i] = 1ll * ifac[i+1] * (i+1) % P;
}
inline int binom(int n, int m){return n < m ? 0 : 1ll * fac[n] * ifac[m] % P * ifac[n-m] % P;}
inline void fstmod(int& x){if (x >= P) x -= P;}
namespace Subtask0
{
	void main()
	{
		int ans = 0;
		for (int k=0; k<=n; k+=2)
			ans = (ans + 1ll * binom(k, k>>1) * binom(n-k, (n-k)>>1) % P * binom(n, k) % P) % P;
		printf("%d\n", ans);
	}
}
namespace Subtask1
{
	int f[N];
	inline void init()
	{
		f[0] = 1;
		for (int i=1; i<N; i++) f[i] = 1ll * f[i-1] * ((i << 2) - 2) % P * inv(i+1) % P;
	}
	void main(){printf("%d\n", f[n >> 1]);}
}
namespace Subtask2
{
	int dp[N];
	void main()
	{
		dp[0] = 1; n >>= 1;
		for (int i=1; i<=n; i++)
			for (int j=0; j<i; j++)
				fstmod(dp[i] += 4ll * dp[j] * Subtask1 :: f[i-j-1] % P);
		printf("%d\n", dp[n]);
	}
}
namespace Subtask3
{
	void main()
	{
		int ans = 0;
		for (int k=0; k<=n; k+=2)
			ans = (ans + 1ll * Subtask1 :: f[k >> 1] * Subtask1 :: f[(n-k) >> 1] % P * binom(n, k) % P) % P;
		printf("%d\n", ans);
	}
}
int main()
{
	init(); Subtask1 :: init();
	scanf("%d%d", &n, &type);
	if (type == 0) Subtask0 :: main();
	if (type == 1) Subtask1 :: main();
	if (type == 2) Subtask2 :: main();
	if (type == 3) Subtask3 :: main();
	return 0;
}
T4 代码
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 55, P = 998244353;
int n, lim, dp[N][N][N * N];
inline void fstmod(int& x){if (x >= P) x -= P;}
inline void chksum(int& a, const int& b){fstmod(a += b);}
int main()
{
	scanf("%d%d", &n, &lim);
	dp[1][0][0] = 1;
	for (int i=2; i<=n; i++)
		for (int j=0; j<=n-i+1; j++)
			for (int k=0; k<=lim; k++)
			{
				if (!dp[i-1][j][k]) continue;
				int _ = dp[i-1][j][k];
				if (j)
				{
					chksum(dp[i][j-1][k+2*i], 1ll * _ * j % P);    // case 1
					chksum(dp[i][j][k+i], 2ll * _ * j % P);        // case 2
					chksum(dp[i][j+1][k], 1ll * _ * j % P);        // case 3
				}
				chksum(dp[i][j][k+i], 2ll * _ % P);                // case 4
				chksum(dp[i][j+1][k], 2ll * _  % P) ;              // case 5
			}
	int ans = 0;
	for (int i=0; i<=lim; i++) chksum(ans, dp[n][0][i]);
	printf("%d\n", ans);
	return 0;
}

\(mod\) 三个字母太长了,先改成 \(P\) .

\(dp_{i,j}\)\(i\) 次操作后 \(x=j\) 的概率,于是可以直接转移,矩阵快速幂优化即可做到 \(O(P^3\log m)\) .

但是这个看起来和题解一模一样,于是我们换一个做法 .


定义 Dirichlet 循环卷积为:

\[(a*b)_k=\sum_{ij\equiv k\pmod n}a_ib_j \]

其中 \(a,b\) 是两个长度为 \(n\) 的序列 .

\(c_i\) 表示 \(a_i\)\(i\) 出现的次数,即 \(\displaystyle c_i=\sum_{k=1}^n[a_i=k]\) .

我们维护(迫真)一个序列 \(\{P\}\) . 其中 \(P_i\) 表示操作若干次后 \(x=i\) 的概率 .

那么我们考虑把 \(\{P\}\) 往后推一次操作,根据操作的定义我们可以发现这个就是等价于把 \(\{P\}\) 卷上 \(\{c\}\)(Dirichlet 循环卷积意义下的)

显而易见 Dirichlet 循环卷积有结合律,于是直接快速幂即可 .

可以发现这个做法等价于 DP,但是时间复杂度是 \(O(P^2\log P)\) 的 .

如果可以做 Dirichlet 的 exp/ln 大概就可以优化到 \(O(P^2)\) 了罢

附:artalter 的做法我不是很懂,但是应该也是本质相同的 .

附附:标程做法大概是用原根把乘变加然后就变成普通循环卷积了吧(可以 DFT)……然而出题人自己整了个模数 \(10^9+7\) /kx .

\(t=0\)

这个是平凡的,首先 BFS 一下随便求出一个点的 \(b\) 值,然后考虑移动一步产生的贡献即可 .

\(t=1\)

先随便钦定一个根,方便计算 .

做一些约定:

  • \(u\) 的父亲记作 \(\operatorname{father}(u)\) .
  • \(u\) 的子树点权和记作 \(\displaystyle\operatorname{sum}(u)=\sum_{v\in\operatorname{subtree}(x)}a_v\) .

考虑对 \(\{b\}\) 做一次差分:

\[\begin{aligned}b_u-b_{\operatorname{father}(u)}&=\sum_{k=1}^na_k(\operatorname{dist}(k,u) - \operatorname{dist}(k,\operatorname{father}(u))\\&=\operatorname{sum}(root)-2\operatorname{sum}(u)\end{aligned} \]

然后手解一下就好,\(O(n)\) .

注意:

  • 多组数据要清空 .
  • 答案不超过 int 不代表中间结果不超过 int .

\(\displaystyle C_k=\dfrac{\dbinom{2n}n}{n+1}\) 为卡特兰数第 \(k\) 项 .

\(typ=0\)

枚举走多少步横着的,则得方案数

\[\sum_{i=0}^n\dbinom ni\dbinom i{i/2}\dbinom{n-i}{(n-i)/2} \]

暴力算,\(O(n)\) .

\(typ=1\)

答案是卡特兰数 \(C_n\) .

\(typ=2\)

放递推式:

\[F_i=4\sum_{j<i}F_{i-j}C_{j-1} \]

于是答案是 \(F_{n/2}\) .

\(O(n^2)\) 暴力算即可 .

\(typ=3\)

类似 \(typ=0\),枚举走多少步横着的,则得方案数

\[\sum_{i=0}^n\dbinom niC_{i/2}C_{(n-i)/2} \]

Bonus

这个是可以 \(O(n\log n)\) 求一行的 .

首先把模数变成 \(998244353\),比较亲民 .


\(typ=1\) 直接 \(O(n)\) 递推 .

其他情况:

  • \(typ=2\):半在线卷积(或者求逆).
  • \(typ=0,3\):二项卷积 .

于是就 \(O(n\log n)\) 了,结束 .

upd. 其实任意模数二项卷积的话复杂度大概也是可以干到 \(O(n\log n)\) 的 .

DP搬运工1

牛子 DP

定义 \(dp_{i,j,k}\) 表示已经填了 \([1,i]\),目前有 \(j\) 个空位,max 和为 \(k\) 的方案数 .

转移直接讨论:

  1. 有空位,直接插进去(e.g. 1304 -> 1324).
  2. 有空位,插到边上(e.g. 1004 -> 1204).
  3. 有空位,插到中间(e.g. 1004 -> 10204).
  4. 直接插到两侧(e.g. 123 -> 4123).
  5. 插到两侧中间加一空位(e.g. 123 -> 40123).

具体柿子见代码 .

时间复杂度 \(O(n^4)\) .

posted @ 2022-07-21 20:09  Jijidawang  阅读(213)  评论(4编辑  收藏  举报
😅​