题解 猪国杀

传送门

看到名字吓了一跳

考场上糊了一个DP,考完Yubai哥哥优化了一下,可以有60pts
\(dp[i][j][k][l]\) 为(DP一个递增序列)前 \(i\) 个数,当前这个数为 \(j\),这个数出现的数量为 \(k\),前 \(i\) 个数的和为 \(l\) 的方案数
于是按 \(l\)\(m\) 的大小关系分情况转移
最后每个递增序列乘上一个多重集排列就是贡献

然后正解:
又是神仙计数
考虑选中的牌与未选的牌的分界点,是所选的牌中的最大值
于是考虑 \(f(x)\) 为选的牌数 \(\geqslant x\) 的方案数
先放柿子:
image
它在做的事情时枚举最大值是什么,被选中了几次,然后考虑有 \(\geqslant i\) 个小于最大值的牌的选法
于是 \(x\) 其实相当于这里的 \(i+k\)
然后康康这个g怎么求
题解给了个容斥思路
枚举有多少个数大于 \(j\)
就是 \(\sum\limits_{t=0}^i \binom{i}{t}\binom{k-t*j}{i}\)
后一个组合数代表先将钦定的大于 \(j\) 的位置填满到 \(j\)
然后插 \(i-1\) 个板,但因为和可以小于 \(k\) 所以多插一个代表有哪些数值是多余的
这也是上面一项不-1的原因(多了一个代表空集的空隙)
复杂度证明要用到调和级数,我水了

  • 计数题要对每种权值计算权值×出现次数且发现不好计的时候试试换成对 \(\geqslant\) 每个数的进行计数
Code:
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 100010
#define ll long long
//#define int long long

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++)
inline int read() {
	int ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

int n, m, a;
const ll mod=998244353;
ll fac[N], inv[N], inv2[N];
inline void md(ll& a, ll b) {a+=b; a=a>=mod?a-mod:a;}
inline ll qpow(ll a, ll b) {ll ans=1; for (; b; a=a*a%mod,b>>=1) if (b&1) ans=ans*a%mod; return ans;}

namespace force{
	ll ans, pre;
	int sta[N], tem[N];
	void dfs(int u) {
		if (u>n) {
			for (int i=1; i<=n; ++i) tem[i]=sta[i];
			sort(tem+1, tem+n+1);
			ll cnt=0, v=m;
			for (int i=1; i<=n; ++i)
				if (v>=tem[i]) v-=tem[i], ++cnt;
				else break;
			ans=(ans+cnt*pre)%mod;
			return ;
		}
		for (int i=1; i<=a; ++i) {
			sta[u]=i;
			dfs(u+1);
		}
	}
	void solve() {
		pre=1;
		for (int i=1; i<=n; ++i) pre=pre*inv[a]%mod;
		dfs(1);
		printf("%lld\n", ans);
		exit(0);
	}
}

namespace task1{
	// 目前这样会算重,因为最后要乘的不是阶乘而是多重集排列
	// 一个可行的解决方案是转移的时候枚举这种数选了多少个转移
	// 但转移貌似变成 $O(n^6)$ 了……
	ll dp[55][55][55][55], ans, pre;
	void solve() {
		pre=1;
		for (int i=1; i<=n; ++i) pre=pre*inv[a]%mod;
		for (int i=0; i<=50; ++i) 
			for (int j=0; j<=50; ++j)
				for (int k=0; k<=50; ++k)
					dp[0][i][j][k]=1;
		for (int i=1; i<=n; ++i) {
			for (int j=1; j<=a; ++j) {
				for (int k=0; k<=i; ++k) {
					for (int l=0; l<=m; ++l) {
						if (k==0) {
							for (int t=max(j, l+1); t<=a; ++t) {
								md(dp[i][j][k][l], dp[i-1][t][0][l]);
							}
						}
						else {
							for (int t=j; t<=min(a, l); ++t) {
								md(dp[i][j][k][l], dp[i-1][t][k-1][l-t]);
							}
						}
						printf("dp[%d][%d][%d][%d]=%lld\n", i, j, k, l, dp[i][j][k][l]);
					}
				}
			}
		}
		for (int k=1; k<=n; ++k) {
			ans=(ans+dp[n][1][k][m]%mod*fac[n]%mod*pre%mod)%mod;
		}
		printf("%lld\n", ans);
		exit(0);
	}
}

namespace task{
	ll ans;
	inline ll C(int n, int k) {return n<k?0ll:fac[n]*inv2[k]%mod*inv2[n-k]%mod;}
	ll g(int i, int j, int k) {
		ll ans=0;
		for (int t=0; t<=i; ++t) ans=(ans+((t&1)?-1:1)*C(i, t)%mod*C(k-t*j, i)%mod)%mod;
		return ans;
	}
	void solve() {
		for (int i=0; i<=n; ++i) {
			for (int j=1; j<=a; ++j) {
				for (int k=1; k<=n-i&&k*j<=m; ++k) {
					ll tem=g(i, j-1, m-k*j)*C(n, i)%mod, sum=0;
					for (int t=k; t<=n-i; ++t) {
						sum=(sum+C(n-i, t)*qpow(a-j, n-i-t)%mod)%mod;
					}
					ans=(ans+tem*sum%mod)%mod;
				}
			}
		}
		printf("%lld\n", (ans*qpow(inv[a], n)%mod+mod)%mod);
		exit(0);
	}
}

signed main()
{
	freopen("legend.in", "r", stdin);
	freopen("legend.out", "w", stdout);

	n=read(); m=read(); a=read();
	fac[0]=fac[1]=1; inv[0]=inv[1]=1; inv2[0]=inv2[1]=1;
	for (int i=2; i<N; ++i) fac[i]=fac[i-1]*i%mod;
	for (int i=2; i<N; ++i) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
	for (int i=2; i<N; ++i) inv2[i]=inv2[i-1]*inv[i]%mod;
	// cout<<"inv: "; for (int i=1; i<=10; ++i) cout<<inv[i]<<' '; cout<<endl;
	// force::solve();
	// task1::solve();
	task::solve();

	return 0;
}
posted @ 2021-10-27 21:43  Administrator-09  阅读(6)  评论(0编辑  收藏  举报