码迷,mamicode.com
首页 > 其他好文 > 详细

省选模拟(41-45)

时间:2020-03-16 21:59:36      阅读:81      评论:0      收藏:0      [点我收藏+]

标签:元素   第二部分   启发式   insert   数组   直接   swap   pac   网络   

省选模拟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\n",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^a\equiv 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\n",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\n",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",n==1?1:0);
    solve(0,n-2);
    printf("%lld\n",1LL*C(A+B-2,A-1)*a[A+B-2]%mod);
    return 0;
}

省选模拟(41-45)

标签:元素   第二部分   启发式   insert   数组   直接   swap   pac   网络   

原文地址:https://www.cnblogs.com/hzoi2018-xuefeng/p/12506825.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!