#10 CF708E & CF643F
这个系列终于上两位数了。
Student's Camp
题目描述
解法
首先考虑一个 的 ,设 表示考虑到第 行,第 行剩下的格子是 ,并且前 行联通的概率。我们预处理出 ,表示恰好吹倒 个格子的概率。
第一步优化可以考虑正难则反,我们用总数减去不合法的方案数来转移:
那么显然可以使用前缀和优化,设 ,,,那么转移可以写成:
现在的时间复杂度已经优化到了 ,发现瓶颈在于状态量太大,考虑到答案只需要整体求和,我们可以把状态也定义成和式,设 ,考虑写出这个和式的转移:
由于 是包含在状态里的(相当于已知),所以我们把和 有关的部分提出来:
那么我们用前缀和维护 和 即可。代码实现中,我们可以计算 ,然后得到 ,再计算对应的前缀和即可,注意 可以根据对称性换成 ,这是因为左右是等价的,所以翻折之后对应的值是相等的。
代码实现抄了别人的,其实根本不复杂,时间复杂度
总结
对于瓶颈在状态定义的 ,可以考察答案的形式,改变状态的定义,在原状态基础上转移。
本题拆分法的应用也是一个亮点,在推有关左右端点的式子时可以考虑把他们拆开来。
#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
题目描述
解法
我自己有一个 的想法,设 表示还剩 张床,还剩 天,桶的数量最多是多少。考虑一天的转移,考虑这些熊睡觉 不睡觉的状态可以帮助我们确定集合 (酒就存在于集合 中),发现集合 的大小其实就是子问题。
那么我们考虑枚举有 头熊喝醉了,有 头熊喝醉的方案对应着相同大小的 ,所以需要用乘法原理。而不同的 之间是独立的,所以用加法原理,那么转移:
暴力转移是 的,无法通过本题,优化可以见官方题解,反正我觉得有点难优化。
更好的方法是建立映射关系,把熊的状态的和酒的位置建立双射。那么我们要考察熊的状态是什么,对于每一个位置我们可以写出一个 的矩阵,表示某一天某一头熊是()否()喝了这个酒。
然后考虑这个矩阵有什么要求呢?首先每一列最多有一个 ,因为这头熊要么喝进医院,要么喝过之后不会再碰它了。其次最多有 个 ,要不然床位就可能满熊可能都喝没了。那么对这个矩阵进行计数,我们枚举 的个数,确定列之后再确定行:
这个数量显然是位置数的上界,因为每个位置分配的矩阵不同。那么我们再证明这样做的充分的,考虑我们按照矩阵来安排熊去喝酒,得到每头熊是否喝进医院,和对应的天数就可以唯一确定这个位置。
显然可以 暴力计算,需要预处理组合数,我们可以上下暴力除 来 计算组合数。
#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);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
2021-03-11 [NOI2007] 货币兑换