题面传送门

我竟然独立搞出了这道黑题!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)的更多相关文章

  1. 洛谷P3600 随机数生成器(期望dp 组合数)

    题意 题目链接 Sol 一条重要的性质:如果某个区间覆盖了另一个区间,那么该区间是没有用的(不会对最大值做出贡献) 首先不难想到枚举最终的答案\(x\).这时我们需要计算的是最大值恰好为\(x\)的概 ...

  2. 洛谷P3600随机数生成器——期望+DP

    原题链接 写到一半发现写不下去了... 所以orz xyz32768,您去看这篇题解吧,思路很清晰,我之前写的胡言乱语与之差距不啻天渊 #include <algorithm> #incl ...

  3. luogu P3600 随机数生成器【dp】

    把期望改成方案数最后除一下,设h[i]为最大值恰好是i的方案数,那么要求的就是Σh[i]*i 首先包含其他区间的区间是没有意义的,用单调栈去掉 然后恰好不好求,就改成h[i]表示最大值最大是i的方案数 ...

  4. [洛谷P5147]随机数生成器

    题目大意:$$f_n=\begin{cases}\frac{\sum\limits_{i=1}^nf_i}n+1&(n>1)\\0&(n=1)\end{cases}$$求$f_n ...

  5. 洛谷P3306 随机数生成器

    题意:给你一个数列,a1 = x,ai = (A * ai-1 + B) % P,求第一个是t的是哪一项,或者永远不会有t. 解:循环节不会超过P.我们使用BSGS的思想,预处理从t开始跳√P步的,插 ...

  6. Luogu P3600 随机数生成器

    Luogu P3600 随机数生成器 题目描述 sol研发了一个神奇的随机数系统,可以自动按照环境噪音生成真·随机数. 现在sol打算生成\(n\)个\([1,x]\)的整数\(a_1...a_n\) ...

  7. 洛谷 P5279 - [ZJOI2019]麻将(dp 套 dp)

    洛谷题面传送门 一道 dp 套 dp 的 immortal tea 首先考虑如何判断一套牌是否已经胡牌了,考虑 \(dp\)​​​​​.我们考虑将所有牌按权值大小从大到小排成一列,那我们设 \(dp_ ...

  8. P3600 随机数生成器

    本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000 作者博客:http://www.cnblogs.com/ljh2000-jump/ ...

  9. 洛谷2344 奶牛抗议(DP+BIT+离散化)

    洛谷2344 奶牛抗议 本题地址:http://www.luogu.org/problem/show?pid=2344 题目背景 Generic Cow Protests, 2011 Feb 题目描述 ...

随机推荐

  1. 4.7 80--删除排序数组中的重复项 II

    因为python的list可以直接del List[index],因此直接使用了暴力方法,判断是否重复了两次,是的话直接使用del. 在转向使用Java时,因为暴力方法的局限,一直在找怎样对Java的 ...

  2. 什么,你还使用 webpack?别人都在用 vite 搭建项目了

    一.vite 到底是干嘛的? vite 实际上就是一个面向现代浏览器,基于 ES module 实现了一个更轻快的项目构建打包工具. vite 是法语中轻快的意思. vite 的特点: 1.轻快的冷服 ...

  3. [技术博客]Django框架-后端的搭建

    目录 Django框架-后端的搭建 前言 环境的部署 项目的创建 app的使用 创建app 修改配置文件 app中数据表的构建 前端接口 接口的路径 运行服务器 验证后端 Django框架-后端的搭建 ...

  4. python3中的bytes和string

    原文链接:https://www.cnblogs.com/abclife/p/7445222.html python 3中最重要的新特性可能就是将文本(text)和二进制数据做了更清晰的区分.文本总是 ...

  5. Linux 系统 Oracle 11g 修改监听端口

    1.查看监听:lsnrctl status 2.停止监听:lsnrctl stop 3.修改oracle安装目录的下的配置文件listener.ora:一般路径为自己Oracle安装目录+/app/o ...

  6. PWN学习之格式化字符串漏洞

    目录 PWN学习之格式化字符串漏洞 格式化输出函数 格式化字符串漏洞 漏洞利用 使程序崩溃 栈数据泄露 任意地址内存泄漏 栈数据覆盖 任意地址内存覆盖 PWN学习之格式化字符串漏洞 格式化输出函数 可 ...

  7. sprint boot 手动快速创建web应用(2)

    1.打开Eclipse新建maven项目 2.导入maven依赖 <parent> <groupId>org.springframework.boot</groupId& ...

  8. CURD系统怎么做出技术含量--怎样引导面试

    引子 很多朋友可能会因为自己做的工作不是特别核心或者业务简单而引起面试中没有自信.但是很多公司面试的时候是可以接受面试者之前岗位的并发量.交易量低一些的.比如我们要招聘和我们交易量同等级或者以上的出来 ...

  9. springboot单元测试 JUnit5

    JUnit5简介 Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库 JUnit 5官方文档 作为最新版本的JUnit框架,JUnit5与之前版本的JUnit框架有很 ...

  10. go struct结构

    p.p1 { margin: 0; font: 12px ".PingFang SC"; color: rgba(69, 69, 69, 1) } span.s1 { font: ...