#10 CF708E & CF643F

这个系列终于上两位数了

Student's Camp

题目描述

点此看题

解法

首先考虑一个 \(O(n^5)\)\(dp\),设 \(f(i,l,r)\) 表示考虑到第 \(i\) 行,第 \(i\) 行剩下的格子是 \([l,r]\),并且前 \(i\) 行联通的概率。我们预处理出 \(D(i)={k\choose i}\cdot(\frac{a}{b})^i\cdot (1-\frac{a}{b})^{k-i}\),表示恰好吹倒 \(i\) 个格子的概率。

第一步优化可以考虑正难则反,我们用总数减去不合法的方案数来转移:

\[f(i,l,r)=D(l-1)\cdot D(m-r)\cdot\Big(\sum_{l'\leq r'} f(i-1,l',r')-\sum_{l'\leq r'<l} f(i-1,l',r')-\sum_{r<l'\leq r'}f(i-1,l',r')\Big) \]

那么显然可以使用前缀和优化,设 \(F(i)=\sum_{l'\leq r'}f(i-1,l',r')\)\(L(i,x)=\sum_{l'\leq r'<x}f(i-1,l',r')\)\(R(i,x)=\sum_{x<l'\leq r'} f(i-1,l',r')\),那么转移可以写成:

\[f(i,l,r)=D(l-1)\cdot D(m-r)\cdot(F(i-1)-L(i-1,l)-R(i-1,r)) \]

现在的时间复杂度已经优化到了 \(O(n^3)\),发现瓶颈在于状态量太大,考虑到答案只需要整体求和,我们可以把状态也定义成和式,设 \(S(i,r)=\sum_{l\leq r}f(i,l,r)\),考虑写出这个和式的转移:

\[S(i,r)=\sum_{l}D(l-1)\cdot D(m-r)\cdot(F(i-1)-L(i-1,l)-R(i-1,r)) \]

由于 \(r\) 是包含在状态里的(相当于已知),所以我们把和 \(r\) 有关的部分提出来:

\[S(i,r)=D(m-r)\cdot \Big((F(i-1)-R(i-1,r))\cdot\sum_{l\leq r}D(l-1)-\sum_{l\leq r} D(l-1)\cdot L(i-1,l)\Big) \]

那么我们用前缀和维护 \(\sum_{l\leq r}D(l-1)\)\(\sum_{l\leq r}D(l-1)\cdot L(i-1,l)\) 即可。代码实现中,我们可以计算 \(S\),然后得到 \(L\),再计算对应的前缀和即可,注意 \(R(i-1,r)\) 可以根据对称性换成 \(L(i-1,m-r)\),这是因为左右是等价的,所以翻折之后对应的值是相等的。

代码实现抄了别人的,其实根本不复杂,时间复杂度 \(O(n^2)\)

总结

对于瓶颈在状态定义的 \(dp\),可以考察答案的形式,改变状态的定义,在原状态基础上转移。

本题拆分法的应用也是一个亮点,在推有关左右端点的式子时可以考虑把他们拆开来。

#include <cstdio>
#include <iostream>
using namespace std;
#define int long long
const int M = 1505;
const int MOD = 1e9+7;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,a,b,k;
struct node
{
	int x;
	node(int X=0) : x(X) {}
	void operator = (int b) {x=b;}
	node operator + (node b) {return node((x+b.x)%MOD);}
	node operator - (node b) {return node((x-b.x+MOD)%MOD);}
	node operator * (node b) {return node(x*b.x%MOD);}
	node operator ^ (int b)
	{
		int r=1,a=x;
		while(b) {if(b&1) r=r*a%MOD;a=a*a%MOD;b>>=1;}
		return node(r);
	}
	node operator / (node b) {return (*this)*(b^(MOD-2));}
}x,p[M],q[M],f[M][M],s[M][M],g[M][M];
node C(int n,int m)
{
	node a=1,b=1;
	for(int i=1;i<=m;i++)
		a=a*(n-i+1),b=b*i;
	return a/b;
}
signed main()
{
	n=read();m=read();a=read();b=read();k=read();
	x=(node)a/b;
	for(int i=0;i<=min(m,k);i++)
		p[i]=C(k,i)*(x^i)*((node(1)-x)^(k-i));
	for(int i=1;i<=m;i++) q[i]=q[i-1]+p[i-1];
	f[0][m]=s[0][m]=node(1);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			f[i][j]=p[m-j]*(q[j]*(s[i-1][m]-s[i-1][m-j])-g[i-1][j]);
			s[i][j]=s[i][j-1]+f[i][j];
			g[i][j]=g[i][j-1]+p[j-1]*s[i][j-1];
		}
	printf("%lld\n",s[n][m].x);
}

Bears and Juice

题目描述

点此看题

解法

我自己有一个 \(dp\) 的想法,设 \(dp[x][y]\) 表示还剩 \(x\) 张床,还剩 \(y\) 天,桶的数量最多是多少。考虑一天的转移,考虑这些熊睡觉 \(/\) 不睡觉的状态可以帮助我们确定集合 \(S\)(酒就存在于集合 \(S\) 中),发现集合 \(S\) 的大小其实就是子问题。

那么我们考虑枚举有 \(i\) 头熊喝醉了,有 \(i\) 头熊喝醉的方案对应着相同大小的 \(S\),所以需要用乘法原理。而不同的 \(i\) 之间是独立的,所以用加法原理,那么转移:

\[dp[x][y]=\sum_{i=0}^{x} dp[x-i][y-1]\cdot{n-(p-x)\choose i} \]

暴力转移是 \(O(p^2q)\) 的,无法通过本题,优化可以见官方题解,反正我觉得有点难优化。


更好的方法是建立映射关系,把熊的状态的和酒的位置建立双射。那么我们要考察熊的状态是什么,对于每一个位置我们可以写出一个 \(q\times n\) 的矩阵,表示某一天某一头熊是(\(1\))否(\(0\))喝了这个酒。

然后考虑这个矩阵有什么要求呢?首先每一列最多有一个 \(1\),因为这头熊要么喝进医院,要么喝过之后不会再碰它了。其次最多有 \(\min(n-1,p)\)\(1\),要不然床位就可能满\(/\)熊可能都喝没了。那么对这个矩阵进行计数,我们枚举 \(1\) 的个数,确定列之后再确定行:

\[\sum_{i=0}^{\min(n-1,p)}{n\choose i}\cdot q^i \]

这个数量显然是位置数的上界,因为每个位置分配的矩阵不同。那么我们再证明这样做的充分的,考虑我们按照矩阵来安排熊去喝酒,得到每头熊是否喝进医院,和对应的天数就可以唯一确定这个位置。

显然可以 \(O(pq)\) 暴力计算,需要预处理组合数,我们可以上下暴力除 \(\tt gcd\)\(O(p^3\log p)\) 计算组合数。

#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
const int M = 205;
#define int unsigned int
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,p,q,ans,a[M];
int gcd(int a,int b) {return !b?a:gcd(b,a%b);}
int calc(int m)
{
	vector<int> a,b;int r=1;
	for(int i=n;i>n-m;i--) a.push_back(i);
	for(int i=1;i<=m;i++) b.push_back(i);
	for(int &x:a) for(int &y:b)
	{
		int d=gcd(x,y);
		x/=d;y/=d;
	}
	for(int &x:a) r*=x;
	return r;
}
signed main()
{
	n=read();p=min(read(),n-1);q=read();
	for(int i=0;i<=p;i++) a[i]=calc(i);
	for(int i=1;i<=q;i++)
	{
		int nw=0;
		for(int j=0,k=1;j<=p;j++,k*=i)
			nw+=a[j]*k;
		ans^=nw*i;
	}
	printf("%u\n",ans);
}
posted @ 2022-03-11 16:50  C202044zxy  阅读(260)  评论(0编辑  收藏  举报