省选模拟(41-45)
Posted hzoi2018-xuefeng
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了省选模拟(41-45)相关的知识,希望对你有一定的参考价值。
省选模拟41
1.要换换名字
bfs自动机(误)+最大流
用霍尔定理或者感性理解可以发现,一个人最多只需要n个名字就一定会有合法解.
所以不需要(2^{100})记录所有的子序列,找到最小的n个就可以了.
具体找法:维护(nxt[i][j][k])为第i个串j位置后第一个k字符的出现位置,可以倒推.
然后bfs找到字典序最小的n个串.
然后二分最大的串长,网络流判断是否满流就行.
构建方案的话如果网络流用的是trie上的节点就很方便,只需要再维护一下这个点的字符代表以及父节点.
2.动态半平面交
可持久化线段树+树上数颜色
考场看到这个题目凉了一大半...
维护以深度建立的可持久化线段树.
查询时只需要查询(dep[u]+d)深度的线段树上(u)的(dfs)区间.
考虑怎么处理点权的lcm问题.
发现点权的lcm实际就是对每个质因子的幂次取(max).
换句话说,假如(p^1,p^2,p^3)的贡献都是(p),其实把(p^3)分解成这3种颜色是一样的.
那么问题变成了求树上每种颜色的出现了.
我们用一种树链的并做法.
之前我们提到过.用set维护的dfs序可以求出所有路径*2的长度,等于把set上的点组成一个环每两点间的距离之和.
这里再次得到一种做法.
总的路径等于dfs序上每个点到自己与前一个点的lca的距离.
把路径求并就可以做到不重不漏了.
然后相当于差分,x处乘,lca处除掉.
查询子树积就是自己的值.
3.获取名额
?
首先x和a都除掉(maxa)保证不用高精度了.
计算无法获取省选名额的概率.
[1-prod_{i=l}^r(1-frac{a_i}{x})]
[1-exp(sum_{i=l}^r(ln(1-frac{a_i}{x})))]
[ln(1-x)=sum_{i=1}^{+∞}-frac{x^i}{i!}]
所以需要处理(a)的幂次的前缀和.
用泰勒展开的话20次左右就可以了.
考虑如果说(frac{a}{x})很大的话泰勒展开就会不精准.
所以准备一个阈值0.5.
预处理区间a的最大值.
如果说(frac{max a}{x}>0.5)直接((1-frac{a}{x}))计算,并继续递归左右部分.
否则直接泰勒展开这个区间.
因为每次分治((1-frac{a}{x})<0.5),所以每次精度翻倍,递归次数不会超过log次.
省选模拟42
1.coin
期望dp
最爱期望,最恨期望.
首先可以发现期望的线性性.
从而可以转为求出每种假币被拿走的期望人数.
记 f(i,j) 表示恰好有 j 个人最喜欢的假币种类是 i 的概率,可以用 dp 计算。
设 h(i,j,k) 表示前 k 个人恰好有 j 个喜爱的假币种类是 i 的概率
那么 (h(i,j,k) = h(i,j?1,k?1)? pij/1000 + h(i,j ?1,k)?(1? pij/1000),f(i,j) = h(i,j,n)。)
记 g(i,j) 表示第 i 种假币携带 j 枚,会被拿走的期望个数。
(g(i,j) = ∑_{0≤k≤m}min(k,j)?f(i,k) = [∑ k≤j k?f(i,k)] + j ? ∑ j<k≤m f(i,k))
假设确定了每枚假币的携带数量 wi,那么 E =∑g(i,wi),现在就是要让 E 最大。
考虑记 dp[i][j] 表示决策前 i 种假币中共选取了 j 枚作礼物,所收获的最大期望。
(dp[i][j] = max_k {dp[i?1][j ?k] + g(i,k)}) 复杂度 (O(n^2m))
分析一下 (?g(i,j) = g(i,j)?g(i,j ?1)) 的特性,(?g(i,j) =∑_{j ≤ k ≤ m}f(i,j))
你会发现? g(i,j) 首先是非负的,其次是单调不升的
也就是说针对第 i 种假钞而言,拿 x + 1 枚一定比 x 收益大
但是从 x + 1 枚变成 x + 2 枚增加的收益比从 x 枚增加到 x + 1 枚是要少的。
摒弃第二部分的 dp,先假设所有假钞都选了 0 份,接着贪心得选择一种假钞,将其数量 +1 使得收益增幅最大,重复这个操作 n 次。
计算 g 的复杂度也是 (O(n^2m)),注意到如果第 i 种假钞当前只选了 a 份,那么 (g(i,a + 2),g(i,a + 3)..) 是不用计算的
g(i,x) 需要用到时从 g(i,x?1) 可以 O(n) 推得,即只有 O(n) 个 g(i,j) 需要计算.
总时间复杂度 (O(nm + n^2))。
#include<bits/stdc++.h>
using namespace std;
int n,m,P[3333][333],al[3333];double p[3333][333],ans;
priority_queue<pair<double,int> >q;map<int,double>M[303][3003];
double f(int k,int c,int n){
while(!P[n][k]&&n)n--;
if(c==0||!n)return 0;
if(M[k][c].find(n)!=M[k][c].end())return M[k][c][n];
return M[k][c][n]=(1+f(k,c-1,n-1))*p[n][k]+f(k,c,n-1)*(1-p[n][k]);
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)scanf("%d",&P[i][j]),p[i][j]=P[i][j]/1000.;
for(int i=1;i<=m;++i)q.push(make_pair(f(i,1,n),i));
for(int i=1;i<=n;++i){
ans+=q.top().first;int k=q.top().second;q.pop();
al[k]++;q.push(make_pair(f(k,al[k]+1,n)-f(k,al[k],n),k));
}printf("%.10lf
",ans);
}
2.B
矩阵树定理
发现进行不超过k次操作等价于是完全图的生成树中非原树边(<=k)个.
变元矩阵树定理,高消或者拉格朗日插值插出i条新边的生成树个数就行.
3.battery
(2-sat)
1.每种炮台只有横向/竖向两种形态.
2.每个空地最多被横着竖着各一个炮台打中.
所以是2-sat.
两个炮台的关系是(a|b).
p.s.dfs的时候记得从下边来的方向是向上的!
省选模拟43
1.选拔赛
dp
设(T(L,R))表示前k个(>=L)且后n-k个(<=R)的方案数.
那么我们离散化所有和后答案就是所有的(T(x,x)-T(x+1,x)).
考虑怎么求出(T(L,R))来.
字太多了直接粘了
2.跳跃
倍增
首先处理出来(L[i][j] R[i][j])表示j位置走(2^i)步的最向左右的位置.
这是相互可以转移的.
然后是比较奇妙的操作了.
二分答案mid.用(mid-1 check).
枚举左端点x,从x跳mid步的位置都要染色.
但是每次取极值并不一定是最优解.
所以要每次让一个区间去跳.
所以要维护倍增的倍增数组.
然后跳到了最远的地方后.
考虑如果最优答案(>=mid),那么就一定有(y>max place)且y也不能到达x.
所以处理后面每个点向左跳的最小下标,然后后缀取(min),每个左端点判断自己的右边界+1的后缀min是否小于自己,若是就有一组合法解.
3.切蛋糕
半平面交
圆不好整.
用多边形拟合圆.
直接半平面交.
交出的多边形拟合的边就是弧长,其他的就是弦长.
模拟测试44
1.跑步
BIT+单调指针
(O(n^2))的dp直接搞就完事了.
考虑每次修改的区域是(x,y)->(n,n).
不妨再缩小一点就是一个(x,y)开始的连续的位置.
再缩小一点发现每行的区间l,r单调不降.
所以用单调指针维护这个东西.
每行区间修改,单点查询.
BIT维护.
2.算术
?
什么剩余都不是.
若(m^k=n)那么在取模意义下仍相等.
那么构造几个微妙的质数满足(p=ak+1)
(x^{ak}equiv 1)
如果说(m^k=n)又(m^{ak}equiv 1).
那么(n^aequiv 1)才行.
前提是(n\\% p!=0).
然后这个东西异常的精准,模拟个20次左右就行了.
3.求和
只会一种90分的暴力.
线段树,维护堆.
这个区间的堆里的元素表示的是([l,r])作为左端点同时满足的右端点.
然后就可以区间的(max)+堆的(top).
然后上传答案到根更新.
模拟测试45
1.matrix
trie合并 set合并
一种很新奇的思路.
把每一行当作一个字符串看待.
先讨论(l=1)的所有子矩阵本质不同的行个数.
换种方法,考虑每一行被统计进本质不同的子矩阵个数.
就是((i-pre)*(n-nxt)).
然后考虑怎么做使得l+1.
就是把trie上根的儿子合并成新根,然后set也启发式合并,map也启发式合并.
考虑怎么更新答案.
发现答案可以从增量角度来计算.
这里一种复杂度不对的打法就是合并x,y时(delta=ret[x]+ret[y]-ret[x']).
这样不对的原因是这样需要把合并后的x‘的set统计一遍答案,每个元素照旧会被算n次.
正确的姿势应该是减去ret[y]但是把y中元素加入x的贡献加入.
(O(nlog^2))
#include<cstdio>
#include<set>
#include<map>
#define ll long long
using namespace std;
const int NM=5e5+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,ptr;
set<int>s[NM];
map<int,int>ch[NM];
ll ans[NM],now,ret[NM];
void bl(int rt){
int lst=0;
for(set<int>::iterator it=s[rt].begin();it!=s[rt].end();++it) ret[rt]+=1LL*(*it-lst)*(n-*it+1),lst=*it;
for(map<int,int>::iterator it=ch[rt].begin();it!=ch[rt].end();++it)bl(it->second);
}
set<int>::iterator Pre(set<int>::iterator it){return --it;}
int merge(int x,int y){
if(!x||!y) return x+y;
if(ch[x].size()<ch[y].size())swap(x,y);
for(map<int,int>::iterator it=ch[y].begin();it!=ch[y].end();++it)
ch[x][it->first]=merge(ch[x][it->first],it->second);
if(s[x].size()<s[y].size())swap(s[x],s[y]),swap(ret[x],ret[y]);
ans[now]-=ret[y];
for(set<int>::iterator it=s[y].begin();it!=s[y].end();++it) if(s[x].find(*it)==s[x].end()){
set<int>::iterator t=s[x].lower_bound(*it);
int l=*it,r=n-*it+1;
if(t!=s[x].begin())l=*it-*Pre(t);
if(t!=s[x].end())r=*t-*it;
s[x].insert(*it);
ret[x]+=1LL*l*r;
ans[now]+=1LL*l*r;
}
return x;
}
int main(){
n=rd();m=rd();ptr=1;
for(int i=1,rt;rt=1,i<=n;++i)for(int j=1,c;j<=m;++j){
c=rd();
if(!ch[rt][c]) ch[rt][c]=++ptr;
rt=ch[rt][c]; s[rt].insert(i);
}
bl(1);for(int i=1;i<=ptr;++i) ans[0]+=ret[i];
for(int o=1,rt=1;o<m;++o){
int c=0; now=o;
for(map<int,int>::iterator it=ch[rt].begin();it!=ch[rt].end();++it) c=merge(c,it->second);
rt=c;
ans[o]-=ret[rt];
ans[o]+=ans[o-1];
}
ll sum=0;for(int i=0;i<m;++i) sum+=ans[i];
printf("%lld
",sum);
return 0;
}
2.sequence
线段树
离线扫描线.
维护每个点作为右端点时候的线段树.
左端点位置的线段树值就是答案.
发现一个后缀和的&值最多改变30次.
所以直接新添入一个元素该合并的合并.
&值模k得零的区间+1就好了.
区间修改,单点查询.
可以标记永久化.
#include<cstdio>
#include<algorithm>
#define lch k<<1
#define rch k<<1|1
#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,Q,K;
int a[N],nxt[N],tag[N<<2];
ll sum[N<<2],ans[N*5];
struct node{
int l,r,id;
friend bool operator < (node a,node b){return a.r<b.r;}
}q[N*5];
void down(int k,int l,int r){
if(!tag[k])return;int mid=(l+r)>>1;
sum[lch]+=1LL*tag[k]*(mid-l+1);tag[lch]+=tag[k];
sum[rch]+=1LL*tag[k]*(r-mid);tag[rch]+=tag[k];
tag[k]=0;
}
void add(int k,int l,int r,int L,int R){
if(L<=l&&r<=R)return sum[k]+=r-l+1,tag[k]++,void();
int mid=(l+r)>>1;down(k,l,r);
if(L<=mid)add(lch,l,mid,L,R);
if(R>mid)add(rch,mid+1,r,L,R);
sum[k]=sum[lch]+sum[rch];
}
ll ask(int k,int l,int r,int L,int R){
if(L<=l&&r<=R)return sum[k];
int mid=(l+r)>>1;down(k,l,r);
if(R<=mid)return ask(lch,l,mid,L,R);
if(L>mid)return ask(rch,mid+1,r,L,R);
return ask(lch,l,mid,L,R)+ask(rch,mid+1,r,L,R);
}
int main(){
n=rd();Q=rd();K=rd();
for(int i=1;i<=n;++i) a[i]=rd();
for(int i=1;i<=Q;++i) q[i].l=rd(),q[i].r=rd(),q[i].id=i;
sort(q+1,q+Q+1);
for(int i=1,j=1;i<=n&&j<=Q;++i){
int val=a[i]; nxt[i]=i-1;
for(int x=i;x;x=nxt[x]){
int y=nxt[x];
while(y)if((val&a[y])==val)y=nxt[y];else break;
nxt[x]=y;
if(val%K==0) add(1,1,n,nxt[x]+1,x);
val&=a[nxt[x]];
}
while(q[j].r==i) ans[q[j].id]=ask(1,1,n,q[j].l,q[j].r),++j;
}
for(int i=1;i<=Q;++i) printf("%lld
",ans[i]);
return 0;
}
3.permutation
第一类斯特林数+NTT
(ans(n,a,b)=sum_{i=1}^{n}f(i-1,a-1)*f(n-i,b-1)*C(n-1,i-1))
含义是枚举最大值的位置求得方案
f的求法:(f[i][j]=f[i-1][j]*(i-1)+f[i-1][j-1])
含义是考虑当前元素的排名.
发现(f[i][j]=s1(i,j))
再次考虑ans,等价于在n-1个元素组成a+b-2个环,然后分a-1,b-1个给左右.
那么等价于(s1(n-1,a+b-2)*C(a+b-2,a-1))
需要(O(nlog))求出第一类斯特林数.
二项式推一推就好了.
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#define ll long long
using namespace std;
const int mod=998244353;
const int N=1e6+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,A,B;
int BIT[N],rev[N],a[N],b[N],c[N],fac[N],inv[N],finv[N];
ll mgml(ll a,ll b,ll ans=1){for(;b;b>>=1,a=a*a%mod)if(b&1)ans=ans*a%mod;return ans;}
ll C(ll x,ll y){return x<y||y<0?0:1LL*fac[x]*finv[y]%mod*finv[x-y]%mod;}
int mo(int a){return a>=mod?a-mod:a;}
void NTT(int *a,int len,int opt){
for(int i=0;i<len;++i){
rev[i]=(rev[i>>1]>>1)|((i&1)<<BIT[len]-1);
if(i<rev[i]) swap(a[i],a[rev[i]]);
}
for(int i=1;i<len;i<<=1)for(int j=0,wn=mgml(3,(mod-1)/(i<<1));j<len;j+=i<<1)for(int k=0,w=1,x,y;k<i;++k,w=1LL*w*wn%mod)x=a[j+k],y=1LL*w*a[j+k+i]%mod,a[j+k]=mo(x+y),a[j+k+i]=mo(x-y+mod);
if(opt==1)return; reverse(a+1,a+len);
for(int i=0,inv=mgml(len,mod-2);i<len;++i)a[i]=1LL*a[i]*inv%mod;
}
void solve(int l,int r){
if(l==r) return a[0]=l,a[1]=1,void();
int mid=(l+r)>>1;if((r-l+1)&1)mid--;solve(l,mid);
int len=1<<((int)(log2(mid-l+2)+1));
for(int i=0,pw=1;i<len;++i,pw=1LL*pw*(mid+1)%mod)b[i]=1LL*fac[i]*a[i]%mod,c[i]=1LL*pw*finv[i]%mod;
for(int i=len;i<len<<1;++i) b[i]=c[i]=0;
reverse(b,b+len);NTT(b,len<<1,1);NTT(c,len<<1,1);for(int i=0;i<len<<1;++i)b[i]=1LL*b[i]*c[i]%mod;NTT(b,len<<1,-1);reverse(b,b+len);
for(int i=0;i<len;++i)b[i]=1LL*b[i]*finv[i]%mod;
if((r-l+1)&1) for(int i=len-1;~i;--i) b[i]=mo(1LL*b[i]*r%mod+(i==0?0:b[i-1]));
for(int i=len;i<len<<1;++i) b[i]=a[i]=0;
len<<=1;
NTT(a,len,1);NTT(b,len,1);for(int i=0;i<len;++i)a[i]=1LL*a[i]*b[i]%mod;NTT(a,len,-1);
}
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;
for(int i=1,j=0;i<N;++j,i<<=1) BIT[i]=j;
n=rd();A=rd(),B=rd();
if(A==1&&B==1)return !printf("%d
",n==1?1:0);
solve(0,n-2);
printf("%lld
",1LL*C(A+B-2,A-1)*a[A+B-2]%mod);
return 0;
}
以上是关于省选模拟(41-45)的主要内容,如果未能解决你的问题,请参考以下文章