题面传送门

我竟然独立搞出了这道黑题!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. mybatis学习笔记(1)基本环境

    1.pom引入 <dependencies> <dependency> <groupId>org.mybatis</groupId> <artif ...

  2. 【二食堂】Alpha- 发布声明

    MarkTexting Alpha版本发布声明 今日我们的MarkTexting正式上线了! 功能介绍 我们实现了一个简单的文本标注生成知识图谱的网站,在Alpha阶段,我们实现的功能有: 登陆注册 ...

  3. 极简实用的Asp.NetCore框架再新增商城模块

    概述 关于这个框架的背景,在前面我已经交代过了.不清楚的可以查看这个链接 1.极简实用的Asp.NetCore模块化框架决定免费开源了 2.极简实用的Asp.NetCore模块化框架新增CMS模块 算 ...

  4. 热身训练1 Game

    http://acm.hdu.edu.cn/showproblem.php?pid=5242 简要题意: 一棵树有n个节点,每个节点x有一个权值wi,我们要从根节点出发(不可回头),去收集每个节点的权 ...

  5. 关于QGIS的插件开发(C++)

    关于C++插件的开发材料较少,根据网上的指导,我采用了早期版本的插件模板生成的方法来创建QGIS的插件,其方法是从以前版本(2.18.25)里面拷贝插件模板的方法进行,具体的执行步骤为 1.拷贝文件 ...

  6. Python课程笔记(二)

    1.格式化输出 print("%d %d %s" % (15, 3.14, 12.8)) 对比C语言 printf("%d,%d,%s",15, 3.14, 1 ...

  7. 升级 dubbo 小心 default.version

    上周遇到个关于升级dubbo 2.6 到2.7的兼容性问题,差点造成线上故障,这里记录下,也给大家提个醒. 问题回放 有一个接口的提供方(dubbo 2.6.6)这么配置接口的版本号 <dubb ...

  8. HTML+CSS基础(HTML篇)

    引言 在日常开发Android中,很多时候会遇到和WebView打交道,对CSS HTML JS不是很清楚的话是完不成一些功能的,本篇开始学习HTML,文章的主要内容是总结了慕课网中,HTML+CSS ...

  9. Codeforces 1009E Intercity Travelling | 概率与期望

    题目链接 题目大意: 一个人要从$A$地前往$B$地,两地相距$N$千米,$A$地在第$0$千米处,$B$地在第$N$千米处. 从$A$地开始,每隔$1$千米都有$\dfrac{1}{2}$的概率拥有 ...

  10. Xtrabackup 全量备份脚本

    #!/bin/bash #备份文件的名字为当前主机的IP地址+tar.gz,例如172.16.103.1.tar.gz,且每次备份成功之后都会清空本地的备份目录. #相关目录 mkdir -p /xt ...