洛谷 P3600 - 随机数生成器(期望 dp)
我竟然独立搞出了这道黑题!incredible!
u1s1 这题是我做题时间跨度最大的题之一……
首先讲下我四个月前想出来的 \(n^2\log n\) 的做法吧。
记 \(f(a)=\max\limits_{i=1}^q\min\limits_{j=l_i}^{r_i}a_j=x\)
首先期望转概率,设 \(p_i\) 表示 \(f(a)=x\) 的概率,答案即为 \(\sum p_i\times i\)。
注意到这题直接求 \(f(a)=x\) 的概率不是特别容易,故考虑换个思路,求出 \(f(a)\geq x\) 的概率 \(t_x\) 然后求个差分即可。是我们的任务转化为求 \(t_x\) 的值。
记序列中 \(<x\) 的数为 \(0\),\(\ge x\) 的数为 \(1\),那么原题等价于这样一个问题,有一个长度为 \(n\) 的随机 \(01\) 序列,每一位生成 \(0\) 的概率为 \(p_0=\dfrac{x-1}{m}\),生成 \(1\) 的概率为 \(p_1=\dfrac{m-x+1}{m}\),给定 \(q\) 个区间 \([l_i,r_i]\),问有多大概率满足这 \(q\) 个区间中至少存在一个区间中全是 \(1\)。
这个问题可以用 \(dp\) 求解,设 \(dp_{i,j}\) 表示当前填好了前 \(i\) 位,上一个 \(0\) 的位置为 \(j\),并且到现在为止不存在全为 \(1\) 的区间的概率。
考虑转移,记 \(mx_R\) 表示 \(\max\limits_{i=1}^ql_i(r_i=R)\),分三种情况转移:
- \(j<mx_i\),那么显然已经存在全 \(1\) 的区间了(\([mx_i,j]\)),故 \(dp_{i,j}=0\) 并让 \(t_x\) 加上 \(dp_{i-1,j}\times p_1\)。
- \(mx_i\le j<i\),显然 \(dp_{i,j}=dp_{i-1,j}\times p_1\)。
- \(j=i\),考虑枚举在 \(i\) 前面上一个为 \(0\) 的位置 \(k\),那么有 \(dp_{i,j}=\sum\limits_{k=0}^{i-1}dp_{i-1,k}\times p_0\)。
这样暴力转移复杂度是 \(n^2\) 的,不过考虑借鉴 CF115E 的套路,可以建一棵线段树在枚举 \(i\) 的过程中实时维护 \(dp_{i,j}\) 的值。具体来说,对于 \(j<mx_i\) 的部分就查询 \([0,mx_i-1]\) 的和 \(sum\),将 \(sum\times p_1\) 累加入答案中并将 \([0,mx_i-1]\) 全部赋上 \(0\);对于 \(mx_i\le j<i\) 的部分令线段树上 \([mx_i,j)\) 位置上的值乘上 \(p_1\);对于 \(j=i\) 的部分就查询一遍 \([0,i-1]\) 的和 \(sum\) 并在线段树上 \(i\) 位置的值赋上 \(sum\times p_1\),这样即可实现 \(\mathcal O(\log n)\) 转移,DP 的复杂度也就降到了 \(n\log n\),再加上外层求解 \(t_x\) 所枚举的 \(x\) 可知总复杂度为 \(n^2\log n\)。
代码:
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
typedef pair<int,int> pii;
typedef long long ll;
const int MAXN=2e3+5;
const int MOD=666623333;
int n,m,q,l[MAXN+5],r[MAXN+5],inv,p[MAXN+5],ans[MAXN+5];
int qpow(int x,int e){
int ret=1;
for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
return ret;
}
int add(int x,int y){return ((x+=y)>=MOD)?(x-MOD):x;}
struct node{int l,r,val,lz;} s[MAXN*4+5];
void build(int k,int l,int r){
s[k].l=l;s[k].r=r;s[k].val=0;s[k].lz=1;if(l==r) return;
int mid=(l+r)>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
}
void pushdown(int k){
if(!s[k].lz){
s[k<<1].lz=0;s[k<<1|1].lz=0;
s[k<<1].val=0;s[k<<1|1].val=0;
s[k].lz=1;return;
}
if(s[k].lz^1){
s[k<<1].lz=1ll*s[k<<1].lz*s[k].lz%MOD;
s[k<<1].val=1ll*s[k<<1].val*s[k].lz%MOD;
s[k<<1|1].lz=1ll*s[k<<1|1].lz*s[k].lz%MOD;
s[k<<1|1].val=1ll*s[k<<1|1].val*s[k].lz%MOD;
s[k].lz=1;
}
}
void modify(int k,int l,int r,int x){
if(l>r) return;
if(l<=s[k].l&&s[k].r<=r){
s[k].lz=1ll*s[k].lz*x%MOD;
s[k].val=1ll*s[k].val*x%MOD;return;
} pushdown(k);int mid=(s[k].l+s[k].r)>>1;
if(r<=mid) modify(k<<1,l,r,x);
else if(l>mid) modify(k<<1|1,l,r,x);
else modify(k<<1,l,mid,x),modify(k<<1|1,mid+1,r,x);
s[k].val=add(s[k<<1].val,s[k<<1|1].val);
}
void update(int k,int ind,int x){
if(s[k].l==s[k].r){s[k].val=x;return;}
pushdown(k);int mid=(s[k].l+s[k].r)>>1;
if(ind<=mid) update(k<<1,ind,x);
else update(k<<1|1,ind,x);
s[k].val=add(s[k<<1].val,s[k<<1|1].val);
}
int query(int k,int l,int r){
if(l>r) return 0;
if(l<=s[k].l&&s[k].r<=r) return s[k].val;
pushdown(k);int mid=(s[k].l+s[k].r)>>1;
if(r<=mid) return query(k<<1,l,r);
else if(l>mid) return query(k<<1|1,l,r);
else return add(query(k<<1,l,mid),query(k<<1|1,mid+1,r));
}
void solve(int x){
int p1=1ll*(m-x+1)*inv%MOD,p0=1ll*(x-1)*inv%MOD;
modify(1,0,n,0);update(1,0,1);
for(int i=0;i<=n;i++){
ans[x]=(ans[x]+query(1,0,p[i]-1))%MOD;modify(1,0,p[i]-1,0);
update(1,i+1,1ll*query(1,p[i],i)*p0%MOD);modify(1,p[i],i,p1);
}
}
int main(){
scanf("%d%d%d",&n,&m,&q);inv=qpow(m,MOD-2);
for(int i=1;i<=q;i++) scanf("%d%d",&l[i],&r[i]),p[r[i]]=max(p[r[i]],l[i]);
build(1,0,n);for(int i=m;i;i--) solve(i);int ret=0;
for(int i=1;i<=m;i++) ret=(ret+1ll*(ans[i]-ans[i+1]+MOD)*i%MOD)%MOD;
printf("%d\n",ret);
return 0;
}
/*
4 3 2
1 3
2 4
*/
然鹅当我写好了代码,有十足的信心切掉这道题的时候……
…………TLE 70?
当时还以为是自己常数写大了,花了九牛二虎之力卡常却无果,xtbz
于是这道题就光荣地在我【尝试过的题】中躺了四个月。
这周三的时候又重新翻到了这个题,顺带着把 \(n^2\) 的算法想出来了。
于是现在我才知道当时平方对数的算法过不了是出题人故意卡的
注意到在之前平方对数的算法中,我们对 \(t_x\) 的定义为 \(f(a)\geq x\) 的概率,如果换成 \(f(a)\le x\) 的概率会有什么区别呢?
这次,我们记序列中 \(\le x\) 的数为 \(1\),\(>x\) 的数为 \(0\),那么求解 \(f(a)\le x\) 的部分等价于如下的问题:有一个长度为 \(n\) 的随机 \(01\) 序列,每一位生成 \(0\) 的概率为 \(p_0=1-\dfrac{x}{m}\),生成 \(1\) 的概率为 \(p_1=\dfrac{x}{m}\),给定 \(q\) 个区间 \([l_i,r_i]\),问有多大概率满足这 \(q\) 个区间中每一个区间中都存在至少一个 \(1\)
还是考虑 \(dp\),记 \(dp_i\) 为当前考虑到第 \(i\) 位,且第 \(i\) 位是 \(1\) 的概率。
考虑转移,我们枚举上一个 \(1\) 的位置 \(j\),就有 \(dp_i=\sum\limits_{j=0}^{i-1}dp_j\times p_0^{i-j-1}\times p_1\)。
但事实上这个转移方程是错误的,因为并不是所有的 \(j\) 都可以转移,比方说 \(i=4,j=1,l_1=2,r_1=3\),那么 \([l_1,r_1]\) 中所有数都是 \(0\),不符合要求。
那么问题就来了,究竟什么样的 \(j\) 才能转移到 \(i\) 呢?
显然 \(j\) 可以转移到 \(i\) 当且仅当不存在 \(i\in[1,q]\) 使得 \([l_i,r_i]\subseteq(j,i)\),并且我们还可以发现一件事,那就是如果 \(j\) 可以转移到 \(i\),并且 \(j<i-1\),那么 \(j+1\) 也一定可以,故合法的决策点是一个右端点为 \(i-1\) 的区间,记 \(i\) 对应决策点区间的左端点为 \(L_i\),那么上述方程式可改写为 \(dp_i=\sum\limits_{j=L_i}^{i-1}dp_j\times p_0^{i-j-1}\times p_1\)。
那么怎么求出 \(L_i\) 呢?记 \(mx_R\) 表示 \(\max\limits_{i=1}^ql_i(r_i=R)\)。那么我们只需检验是否存在某个 \(k\in(j,i)\) 使得 \(mx_k>j\) 即可,我们还可以注意到 \(L_i>L_{i-1}\),也就是 \(L_i\) 单调递增。故可用 two pointers+ST 表在 \(n\log n\) 预处理、\(\mathcal O(n)\) 求解的时间内求出 \(L_i\),具体来说,ST 表维护 \(\max\limits_{i=l}^rmx_i\),然后检验 \(\max\limits_{k=j+1}^{i-1}mx_k>j\) 即可。
求出 \(L_i\) 之后转移就变得异常容易了。我们可将转移方程式进一步改写为 \(dp_i=\sum\limits_{j=L_i}^{i-1}\dfrac{dp_j}{p_0^j}\times p_0^{i-1}\times p_1\),注意到前面 \(\dfrac{dp_j}{p_0^j}\) 只与 \(j\) 有关,后面 \(p_0^{i-1}\times p_1\) 只与 \(i\) 有关,故可以维护 \(\dfrac{dp_j}{p_0^j}\) 的前缀和,这样即可实现 \(\mathcal O(1)\) 转移。
时间复杂度 \(n^2\)。
终于把自爆的心头之恨化解掉了,爽
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;
namespace fastio{
#define FILE_SIZE 1<<23
char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
inline void putc(char x){(*p3++=x);}
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=0;
while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(neg) x=(~x)+1;
}
template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=2e3;
const int LOG_N=11;
const int MOD=666623333;
int n,m,q,inv,mx[MAXN+5],st[MAXN+5][LOG_N+2],pre[MAXN+5];
int pw[MAXN+5],ipw[MAXN+5],dp[MAXN+5],sum[MAXN+5],ans[MAXN+5];
int qpow(int x,int e){
int ret=1;
for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
return ret;
}
int query_mx(int l,int r){
if(l>r) return 0;int k=31-__builtin_clz(r-l+1);
return max(st[l][k],st[r-(1<<k)+1][k]);
}
int solve(int x){
int p0=1ll*(m-x)*inv%MOD,p1=1ll*x*inv%MOD,ip0=qpow(p0,MOD-2);
pw[0]=1;for(int i=1;i<=n;i++) pw[i]=1ll*pw[i-1]*p0%MOD;
ipw[0]=1;for(int i=1;i<=n;i++) ipw[i]=1ll*ipw[i-1]*ip0%MOD;
memset(dp,0,sizeof(dp));memset(sum,0,sizeof(sum));
dp[0]=sum[0]=1;
for(int i=1;i<=n;i++){
dp[i]=1ll*(sum[i-1]-sum[pre[i]-1]+MOD)*pw[i-1]%MOD*p1%MOD;
sum[i]=(sum[i-1]+1ll*dp[i]*ipw[i])%MOD;
} int ret=0,mn=query_mx(1,n);
for(int i=mn;i<=n;i++) ret=(ret+1ll*dp[i]*pw[n-i])%MOD;
return ret;
}
int main(){
scanf("%d%d%d",&n,&m,&q);inv=qpow(m,MOD-2);
for(int i=1,l,r;i<=q;i++) scanf("%d%d",&l,&r),chkmax(mx[r],l);
for(int i=1;i<=n;i++) st[i][0]=mx[i];
for(int i=1;i<=LOG_N;i++) for(int j=1;j+(1<<i)-1<=n;j++)
st[j][i]=max(st[j][i-1],st[j+(1<<i-1)][i-1]);
for(int i=1,j=0;i<=n;i++){
while(j<i&&query_mx(j+1,i-1)>j) ++j;
pre[i]=j;
} int res=0;
for(int i=1;i<m;i++) ans[i]=solve(i);ans[m]=1;
for(int i=1;i<=m;i++) res=(res+1ll*(ans[i]-ans[i-1]+MOD)*i)%MOD;
printf("%d\n",res);
return 0;
}
洛谷 P3600 - 随机数生成器(期望 dp)的更多相关文章
- 洛谷P3600 随机数生成器(期望dp 组合数)
题意 题目链接 Sol 一条重要的性质:如果某个区间覆盖了另一个区间,那么该区间是没有用的(不会对最大值做出贡献) 首先不难想到枚举最终的答案\(x\).这时我们需要计算的是最大值恰好为\(x\)的概 ...
- 洛谷P3600随机数生成器——期望+DP
原题链接 写到一半发现写不下去了... 所以orz xyz32768,您去看这篇题解吧,思路很清晰,我之前写的胡言乱语与之差距不啻天渊 #include <algorithm> #incl ...
- luogu P3600 随机数生成器【dp】
把期望改成方案数最后除一下,设h[i]为最大值恰好是i的方案数,那么要求的就是Σh[i]*i 首先包含其他区间的区间是没有意义的,用单调栈去掉 然后恰好不好求,就改成h[i]表示最大值最大是i的方案数 ...
- [洛谷P5147]随机数生成器
题目大意:$$f_n=\begin{cases}\frac{\sum\limits_{i=1}^nf_i}n+1&(n>1)\\0&(n=1)\end{cases}$$求$f_n ...
- 洛谷P3306 随机数生成器
题意:给你一个数列,a1 = x,ai = (A * ai-1 + B) % P,求第一个是t的是哪一项,或者永远不会有t. 解:循环节不会超过P.我们使用BSGS的思想,预处理从t开始跳√P步的,插 ...
- Luogu P3600 随机数生成器
Luogu P3600 随机数生成器 题目描述 sol研发了一个神奇的随机数系统,可以自动按照环境噪音生成真·随机数. 现在sol打算生成\(n\)个\([1,x]\)的整数\(a_1...a_n\) ...
- 洛谷 P5279 - [ZJOI2019]麻将(dp 套 dp)
洛谷题面传送门 一道 dp 套 dp 的 immortal tea 首先考虑如何判断一套牌是否已经胡牌了,考虑 \(dp\).我们考虑将所有牌按权值大小从大到小排成一列,那我们设 \(dp_ ...
- P3600 随机数生成器
本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000 作者博客:http://www.cnblogs.com/ljh2000-jump/ ...
- 洛谷2344 奶牛抗议(DP+BIT+离散化)
洛谷2344 奶牛抗议 本题地址:http://www.luogu.org/problem/show?pid=2344 题目背景 Generic Cow Protests, 2011 Feb 题目描述 ...
随机推荐
- [Beta]the Agiles Scrum Meeting 10
会议时间:2020.5.25 21:00 1.每个人的工作 今天已完成的工作 成员 已完成的工作 issue yjy 暂无 tq 暂无 wjx 实现创建.显示博客作业功能 增加博客作业功能 dzx 实 ...
- [软软软]技术博客-Commitizen优化git commit
工具介绍 commitizen/cz-cli是一个规范git commit的工具,使用它代替git commit能够方便有效地写好提交的log,使得团队项目的版本信息更清晰. 安装 (全局安装) np ...
- BUAA2020软工作业(一)——谈谈我和计算机的缘分
项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 第一次作业-热身! 我在这个课程的目标是 进一步提高自己的编码能力,工程能力 这个作业在哪个具体方 ...
- xshell几款绝佳配色方案
NO.1 [mycolor] text(bold)=e9e9e9 magenta(bold)=ff00ff text=00ff80 white(bold)=fdf6e3 green=80ff00 re ...
- Noip模拟12 2021.7.12
T1 interval 亏得昨天晚上改掉了T3并且理解了单调栈,今天一扫这题目就知道要用啥了. 先预处理出以a[i]为最大值的最大左右区间.然后再将a[i]取%!!!是的,要不然会影响单调栈的使用.. ...
- 2021.9.13考试总结[NOIP模拟52]
T1 路径 考虑每一位的贡献,第$i$位每$2^i$个数会变一次,那么答案为$\sum_{i=1}^{log_2n} \frac{n}{2^i}$. $code:$ 1 #include<bit ...
- VS2017+QT5.12.10+QGIS3.16环境搭建及开发全流程
题记:大力发展生产力,助力高效采集.(转载请注明出处https://www.cnblogs.com/1024bytes/p/15477374.html) 本篇随笔分为五个部分: 一.获取QGIS3.1 ...
- 玩转C语言链表-链表各类操作详解
链表概述 链表是一种常见的重要的数据结构.它是动态地进行存储分配的一种结构.它可以根据需要开辟内存单元.链表有一个"头指针"变量,以head表示,它存放一个地址.该地址指向一个元素 ...
- reorder-list leetcode C++
Given a singly linked list L: L 0→L 1→-→L n-1→L n, reorder it to: L 0→L n →L 1→L n-1→L 2→L n-2→- You ...
- Codeforces Round #742 (Div. 2)题解
链接 \(A,B\)题签到,就完了. \(C\)题,考虑进位时多进一位,由于是隔一位进的,所以可以发现奇数位和偶数位是相互独立的,那么我们就把奇数位和偶数位单独拉出来组成数字例如:34789,我们单独 ...