标签:表示 line 下标 max string rebuild long char pair
期望dp,min_max容斥
喂鸽子.
n粒玉米,m只鸽子,每粒玉米造成的饱食度不同,求把所有鸽子都喂饱的期望时间.
把所有鸽子都喂饱,就等价于把最后的一只鸽子喂饱的期望时间,也就是喂饱时间的期望最大值.
这里有个min_max容斥,问题就变成了求一个集合内部第一只被喂饱的鸽子的期望时间.
设\(p\)表示只喂一只鸽子,喂恰好\(i\)次时恰好喂饱的概率.
设\(q\)表示只喂一只鸽子,喂\(i\)次都没有喂饱的概率.
\(q=1-sum\ p\).
枚举集合,发现鸽子等价,所以只需要枚举集合大小.
发现如果把\(p,q\)变成多项式的形式,大小为\(i\)的集合就是\(P*Q*Q*Q...\),\(P\)卷上\(i-1\)个\(Q\).
可是发现因为我们在求\(P,Q\)的时候没有考虑顺序,因此每个集合内部相当于已经乘过了a!,而我们此时要把他们打乱顺序,而内部打乱顺序其实是无用的.
所以用指数型生成函数EGF,在系数上\(*finv[i]\)最后取出时\(*fac[i]\)就好了.
再次发现\(P\)不能够完全被打乱顺序,因为我们需要求出恰好第i次喂食把第一只鸽子喂饱的方案.
所以把我们不知道第一只鸽子第几次喂食就吃饱的那个最后一次吃给它抽出来,保证不参与乱序,必须是最后一次.
然后统计答案的时候有很多的系数.
枚举\(j\)表示最后的投食数量.
\(*\frac{1}{i^j}\)因为每次选中的概率为\(\frac{1}{i}\).
\(*j\)因为期望=概率*实际值.
\(*\frac{m}{i}\)考虑只考虑i只鸽子可是实际情况是每次都会在n只里面选,因此这是总方案的\(\frac{i}{m}\),所以乘上.
\(*i\),枚举选中的是哪个鸽子.
#include<cstdio>
#define ll long long
using namespace std;
const int N=10005;
const int mod=998244353;
int mo(int a){return a>=mod?a-mod:a;}
inline int rd(register int x=0,register char ch=getchar(),register int f=0){
while(ch<'0'||ch>'9') f=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-48,ch=getchar();
return f?-x:x;
}
int n,m,K;
int a[N],f[2][N],p[N],q[N],fac[N],inv[N],finv[N],P[N],Q[N],t[N];
int C(int x,int y){return x<y||y<0?0:1LL*fac[x]*finv[y]%mod*finv[x-y]%mod;}
int main(){
fac[0]=fac[1]=inv[0]=inv[1]=finv[0]=finv[1]=1;
for(int i=2;i<N;++i) fac[i]=1LL*fac[i-1]*i%mod,inv[i]=1LL*inv[mod%i]*(mod-mod/i)%mod,finv[i]=1LL*finv[i-1]*inv[i]%mod;
n=rd();m=rd();K=rd(); for(int i=1;i<=n;++i) a[i]=rd();
f[0][0]=1;q[0]=1;
for(int i=1;i<=K;++i){
for(int j=0;j<K;++j)f[i&1][j]=0;
for(int j=0;j<K;++j)for(int k=1;k<=n;++k)
if(j+a[k]<K) f[i&1][j+a[k]]=mo(f[i&1][j+a[k]]+1LL*f[i&1^1][j]*inv[n]%mod);
else p[i]=mo(p[i]+1LL*f[i&1^1][j]*inv[n]%mod);
q[i]=mo(q[i-1]-p[i]+mod);
}
for(int i=0;i<=K;++i) P[i]=1LL*p[i+1]*finv[i]%mod,Q[i]=1LL*q[i]*finv[i]%mod;
int ans=0;
for(int i=1;i<=m;++i){
for(int j=0,J=i*K;j<=J;++j) t[j]=0;
for(int j=0,J=(i-1)*K;j<=J;++j)if(P[j])for(int k=0;k<=K;++k)t[j+k]=mo(t[j+k]+1LL*P[j]*Q[k]%mod);
if(i!=1) for(int j=0,J=i*K;j<=J;++j) P[j]=t[j];
int ret=0;
for(int j=1,J=i*K,pw=inv[i];j<=J;++j,pw=1LL*pw*inv[i]%mod) ret=mo(ret+1LL*P[j-1]*fac[j-1]%mod*j%mod*pw%mod);
ans=mo(ans+(i&1?1LL:mod-1LL)*ret%mod*C(m,i)%mod*m%mod);
}
printf("%d\n",ans);
return 0;
}
分块维护凸包.
其实有一个很简单的做法就是线段树维护,区间加等差数列,但不知道为什么没有人打,然后我很懒也没有打.
这种题都能用dp来做...
设\(dp[i][j]\)为\(c=i,p=j\)的最大贡献.
那么转移就是把一些看广告的变成了收费的.
即那些\(b_k=i\)的人的\(a_k\)会为\(p<=a_k\)的\(p\)做贡献.
换句话说,\([1,a_k]\)区间加等差数列(p).
考虑维护凸包,然后用斜率为0的直线切出来的就是贡献最大的值.
为什么呢,因为发现区间加等差数列只会让所有的斜率都+1,可是并不会改变斜率大小关系.
换句话说,凸包形态不变.
所以分块维护凸包,维护答案的位置.
又因为斜率是单增的,所以答案的位置一定是单增的,用单调指针每次斜率增加的时候扫一下就好了.
#include<cstdio>
#include<algorithm>
#include<iostream>
#define ll long long
using namespace std;
const int N=1e5+50;
inline int rd(register int x=0,register char ch=getchar(),register int f=0){
while(ch<'0'||ch>'9') f=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-48,ch=getchar();
return f?-x:x;
}
int n,W,blo,num,m;
int L[350],R[350],bel[N],tag[N],sta[350][350],Ans[350];
pair<int,int>b[N];
struct point{
ll x,y;
friend point operator - (point a,point b){return (point){a.x-b.x,a.y-b.y};}
}a[N];
ll cross(point a,point b){return a.x*b.y-a.y*b.x;}
double slope(point a,point b){return 1.0*(a.y-b.y)/(a.x-b.x);}
void Rebuild(int k){
for(int i=L[k];i<=R[k];++i) a[i].y+=1LL*i*tag[k];tag[k]=0;
sta[k][0]=0;
for(int i=L[k];i<=R[k];++i){
while(sta[k][0]>1&&cross(a[i]-a[sta[k][sta[k][0]]],a[sta[k][sta[k][0]-1]]-a[sta[k][sta[k][0]]])>0) --sta[k][0];
sta[k][++sta[k][0]]=i;
}
Ans[k]=1;
while(Ans[k]<sta[k][0]&&slope(a[sta[k][Ans[k]]],a[sta[k][Ans[k]+1]])>0) ++Ans[k];
}
int main(){
n=rd();W=rd();
for(int i=1;i<=n;++i) b[i].second=rd(),b[i].first=rd(),m=m>b[i].second?m:b[i].second;
for(blo=1;blo*blo<m;++blo);
num=m/blo+(m%blo!=0);
for(int i=1;i<=m;++i) a[i].x=i;
for(int i=1;i<=num;++i){
L[i]=(i-1)*blo+1,R[i]=min(m,i*blo);
for(int j=L[i];j<=R[i];++j) bel[j]=i;
Rebuild(i);
}
sort(b+1,b+n+1);
for(int i=1,j=1;i<=b[n].first+1;++i){
while(j<=n&&b[j].first<i){
int sc=b[j++].second;
if(!sc) continue;
for(int k=1;k<bel[sc];++k){
tag[k]++;
while(Ans[k]<sta[k][0]&&slope(a[sta[k][Ans[k]]],a[sta[k][Ans[k]+1]])+tag[k]>0) ++Ans[k];
}
for(int k=L[bel[sc]];k<=sc;++k) a[k].y+=k;
Rebuild(bel[sc]);
}
ll ans=0;
for(int k=1;k<=num;++k) ans=max(ans,a[sta[k][Ans[k]]].y+1LL*tag[k]*sta[k][Ans[k]]);
printf("%lld ",ans+1LL*W*i*(n-j+1));
}
return 0;
}
把下标变成\((x+y,x-y)\)后,每次移动都会必然造成两维的同时改变.
因此可以单独考虑每一维了.
考虑每次的位置都可以变成形如\(pos=\frac{t}{L}S[L]+S[t\%L]\)的形式.
变成三元组的话就是(a,b,c).
这样的话按照c升序,然后每段能够列出一个恒等式,解出最后的\(S[L]\)的范围,取一个合法的范围再构造就好了.
\(sam+set+BIT\)
求终点在\([L,R]\)范围内的前缀的最长公共后缀.
考虑建sam.
然后考虑答案就是\(max _{i,j \in [L,R]} lca(s[i],s[j])\).
这样点对显然是\(O(n^2)\)的就死死了.
考虑最小表示这些点对无非就是一个更小的区间.
考虑枚举lca,然后用set进行启发式合并,每次新增的点和前驱后继加入结构体.
然后把这些贡献和询问按L降序排序,用BIT更新答案,单调指针扫.
#include<cstdio>
#include<cstring>
#include<set>
#include<iostream>
#include<algorithm>
#define itset set<int>::iterator
using namespace std;
const int N=3e5+50;
inline int rd(register int x=0,register char ch=getchar(),register int f=0){
while(ch<'0'||ch>'9') f=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-48,ch=getchar();
return f?-x:x;
}
int n,m,lst,cnt,ptr;
int fa[N],ch[N][2],len[N],buc[N],rk[N],ret[N],c[N];
set<int>se[N];
char s[N];
struct node{int x,y,v;}q[N],t[N*40];
bool cmp(node a,node b){return a.x>b.x;}
void add(int p,int v){
for(;p<=n;p+=p&-p) c[p]=c[p]>v?c[p]:v;
}
int ask(int p,int a=0){
for(;p;p-=p&-p) a=a>c[p]?a:c[p];
return a;
}
void extend(int c,int u){
int p=lst,np;np=lst=++cnt;
len[np]=len[p]+1;
for(;p&&!ch[p][c];p=fa[p])ch[p][c]=np;
if(!p) fa[np]=1;
else{
int q=ch[p][c];
if(len[q]==len[p]+1) fa[np]=q;
else{
int nq=++cnt;
len[nq]=len[p]+1;fa[nq]=fa[q];fa[np]=fa[q]=nq;
ch[nq][0]=ch[q][0],ch[nq][1]=ch[q][1];
for(;p&&ch[p][c]==q;p=fa[p])ch[p][c]=nq;
}
}
se[np].insert(u);
}
int main(){
n=rd();m=rd();scanf("%s",s+1);
lst=cnt=1;for(int i=1;i<=n;++i) extend(s[i]-'0',i);
for(int i=1;i<=cnt;++i) buc[len[i]]++;
for(int i=1;i<=n;++i) buc[i]+=buc[i-1];
for(int i=1;i<=cnt;++i) rk[buc[len[i]]--]=i;
for(int i=cnt;i>=2;--i){
int x=rk[i],p=fa[rk[i]];
if(se[x].size()>se[p].size()) swap(se[x],se[p]);
for(itset it=se[x].begin();it!=se[x].end();++it){
itset pos=se[p].lower_bound(*it);
if(pos!=se[p].end()) t[++ptr]=(node){*it,*pos,len[p]};
if(pos!=se[p].begin()) t[++ptr]=(node){*--pos,*it,len[p]};
}
for(itset it=se[x].begin();it!=se[x].end();++it) se[p].insert(*it);
}
for(int i=1;i<=m;++i) q[i].v=i,q[i].x=rd(),q[i].y=rd();
sort(q+1,q+m+1,cmp);
sort(t+1,t+ptr+1,cmp);
for(int i=1,j=1;i<=m;++i){
while(j<=ptr&&t[j].x>=q[i].x) add(t[j].y,t[j].v),++j;
ret[q[i].v]=ask(q[i].y);
}
for(int i=1;i<=m;++i) printf("%d\n",ret[i]);
return 0;
}
标签:表示 line 下标 max string rebuild long char pair
原文地址:https://www.cnblogs.com/hzoi2018-xuefeng/p/12515191.html