暑假集训CSP提高模拟27
暑假集训CSP提高模拟27
组题人: @H_Kaguya | @KafuuChinocpp
\(T1\) P236.线性只因 \(100pts\)
-
诈骗题。
-
部分分
-
正解
- 记 \(opt\) 表示待选集合,统计 \(opt\) 中这一位为 \(1\) 的数并加入临时集合 \(tmp\) 。若 \(tmp\) 大小 \(\ge m\) 则加上这一位的贡献并将 \(opt\) 赋成 \(tmp\) 否则不管。
点击查看代码
ll a[1000010]; vector<ll>opt,tmp; int main() { ll n,m,ans=0,i,s; cin>>n>>m; for(i=1;i<=n;i++) { cin>>a[i]; opt.push_back(i); } for(s=32;s>=0;s--) { tmp.clear(); for(i=0;i<opt.size();i++) { if((a[opt[i]]>>s)&1) { tmp.push_back(opt[i]); } } if(tmp.size()>=m) { opt=tmp; ans|=(1<<s); } } cout<<ans<<endl; return 0; }
\(T2\) P234.金箱子 \(30pts\)
-
若钓出来一个金箱子在模 \(998244353\) 意义下概率为 \(p_{i}\) ,则钓出来一个银箱子在模 \(998244353\) 意义下概率为 \(1-p_{i}+998244353\) 。
- 设钓出来一个金箱子的概率为 \(\frac{x}{y}\) ,由题有 \(\gcd(y,998244353)=1\) ,那么 \(\frac{x}{y}\) 在模 \(998244353\) 意义等于 \(p_{i}=x \times y^{998244353-2} \bmod 998244353\) ,相应的 \(\frac{y-x}{y}\) 在模 \(998244353\) 意义下等于 \((y-x) \times y^{998244353-2} \bmod998244353=(y^{998244353-1}-p_{i}+998244353) \bmod 998244353=1-p_{i}+998244353\) 。
-
部分分
- \(20pts\) :爆搜。
- \(10pts\) :观察到 \(k=1\) ,遂 \(\sum\limits_{i=1}^{n}p_{i}a_{i}+(1-p_{i})b_{i}\) 即为所求,
点击查看代码
const ll p=998244353; ll c[10010],a[10010],b[10010],ans=0; ll qpow(ll a,ll b,ll p) { ll ans=1; while(b) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } void dfs(ll pos,ll mul,ll num,ll n,ll k) { if(pos==n+1) { ans=(ans+mul*qpow(num,k,p))%p; } else { if(mul!=0) { dfs(pos+1,mul*c[pos]%p,(num+a[pos])%p,n,k); dfs(pos+1,mul*(1+p-c[pos])%p,(num+b[pos])%p,n,k); } } } int main() { ll n,k,i; cin>>n>>k; for(i=1;i<=n;i++) { cin>>c[i]>>a[i]>>b[i]; } if(k==1) { for(i=1;i<=n;i++) { ans=(ans+c[i]*a[i]%p+(1+p-c[i])*b[i]%p)%p; } } else { dfs(1,1,0,n,k); } cout<<ans<<endl; return 0; }
-
正解
- 设 \(E(X)=\sum\limits_{i=1}^{n}x_{i}p_{i}\) 则有 \(E(X^{k})=\sum\limits_{i=1}^{n}x_{i}^{k}p_{i}\) ,相应的 \(E((X+1)^{k})=\sum\limits_{i=1}^{n}(x_{i}+1)^{k}p_{i}\) ,考虑用二项式定理将式子拆开,即维护 \(x^{k}\) 的贡献。
- 设当前增加 \(a\) 个金币则 \((x+a)^{k}=\sum\limits_{i=0}^{k}\dbinom{k}{i}x^{i}a^{k-i}\) (严格来说指数的 \(i,k-i\) 写反了但不要紧)。
- 设 \(f_{i,j}\) 表示当前处理的是前 \(i\) 项的 \(j\) 次方和的期望,同上转移即可。边界为 \(\begin{cases} f_{i,0}=1 & i \in [1,n] \\ f_{1,i}=p_{i}a_{1}^{i}+(1-p_{1})b_{1}^{i} & i \in [1,k] \end{cases}\) 。
点击查看代码
const ll p=998244353; ll C[110][110],c[10010],a[10010],b[10010],val[10010][110],f[10010][110]; ll qpow(ll a,ll b,ll p) { ll ans=1; while(b) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } int main() { ll n,k,i,j,h; cin>>n>>k; C[0][0]=C[1][0]=C[1][1]=1; for(i=2;i<=k;i++) { C[i][0]=1; for(ll j=1;j<=i;j++) { C[i][j]=(C[i-1][j-1]+C[i-1][j])%p; } } for(i=1;i<=n;i++) { cin>>c[i]>>a[i]>>b[i]; for(j=0;j<=k;j++) { val[i][j]=(qpow(a[i],j,p)*c[i]%p+qpow(b[i],j,p)*(1+p-c[i])%p)%p; } } f[1][0]=1; for(i=1;i<=k;i++) { f[1][i]=val[1][i]; } for(i=2;i<=n;i++) { f[i][0]=1; for(j=1;j<=k;j++) { for(h=0;h<=j;h++) { f[i][j]=(f[i][j]+(C[j][h]*f[i-1][h]%p)*val[i][j-h]%p)%p; } } } cout<<f[n][k]<<endl; return 0; }
\(T3\) P235.可持久化字符串 \(50pts\)
-
部分分
-
\(50pts\) :哈希判等。大样例没过遂不保证代码正确性。
点击查看代码
const ll p=1000003579,base=13331; ll a[100010],pre[100010],hsh[100010],len[100010],num[100010],sum[100010],jc[100010]; char s[100010]; void sx_hash(char s[],ll len,ll a[]) { for(ll i=0;i<=len;i++) { a[i]=(i==0)?0:(a[i-1]*base%p+s[i])%p; } } ll ask_hash(ll l,ll r) { return (a[r]-a[l-1]*jc[r-l+1]%p+p)%p; } void solve(ll pos,ll n) { for(ll i=1;i+len[pos]-1<=n;i++) { num[pos]+=(ask_hash(i,i+len[pos]-1)==hsh[pos]); } } int main() { ll n,m,pd,x,l,r,c_cnt=0,i; char c; cin>>m>>n>>(s+1); sx_hash(s,n,a); for(i=0;i<=100000;i++) { jc[i]=(i==0)?1:jc[i-1]*base%p; } num[0]=sum[0]=1; for(i=1;i<=m;i++) { cin>>pd; if(pd==1) { cin>>x>>c; c_cnt++; pre[c_cnt]=x; hsh[c_cnt]=(hsh[x]*base+c)%p; len[c_cnt]=len[x]+1; solve(c_cnt,n); sum[c_cnt]=sum[c_cnt-1]+num[c_cnt]; } if(pd==2) { cin>>x; c_cnt++; pre[c_cnt]=pre[pre[x]]; hsh[c_cnt]=hsh[pre[x]]; len[c_cnt]=len[pre[x]]; num[c_cnt]=num[pre[x]]; sum[c_cnt]=sum[c_cnt-1]+num[c_cnt]; } if(pd==3) { cin>>l>>r; if(l-1>=0) { cout<<sum[r]-sum[l-1]<<endl; } else { cout<<sum[r]<<endl; } } } return 0; }
-
-
正解
- 操作 \(1,2\) 等价于动态建 \(Trie\) 树时新建和回撤一个节点的操作。
- 发现模式串很多,文本串只有一个,且支持离线。考虑将所有操作离线下来建出 \(AC\) 自动机。
- 求模式串在文本串里的出现次数同 luogu P5357 【模板】AC 自动机 ,需要拓扑优化建图。
- 注意加入空串的影响,前缀和维护询问即可。
点击查看代码
struct ACM { int fa[100010],fail[100010],din[100010],lazy[100010],flag[100010],id[100010],ans[100010],rt_sum; vector<int>e[100010]; struct trie { int ch[27]; }tree[100010]; int val(char x) { return x-'a'+1; } void add(int &x,char s,int idx) { if(tree[x].ch[val(s)]==0) { rt_sum++; tree[x].ch[val(s)]=rt_sum; fa[tree[x].ch[val(s)]]=x; } x=tree[x].ch[val(s)]; flag[x]=(flag[x]==0)?idx:flag[x]; id[idx]=flag[x]; } void del(int &x,int idx) { x=fa[x]; flag[x]=(flag[x]==0)?idx:flag[x]; id[idx]=flag[x]; } void build() { queue<int>q; for(int i=1;i<=26;i++) { if(tree[0].ch[i]!=0) { fail[tree[0].ch[i]]=0; q.push(tree[0].ch[i]); } } while(q.empty()==0) { int x=q.front(); q.pop(); for(int i=1;i<=26;i++) { if(tree[x].ch[i]==0) { tree[x].ch[i]=tree[fail[x]].ch[i]; } else { fail[tree[x].ch[i]]=tree[fail[x]].ch[i]; e[tree[x].ch[i]].push_back(fail[tree[x].ch[i]]); din[fail[tree[x].ch[i]]]++; q.push(tree[x].ch[i]); } } } } void mark(char s[],int len) { int x=0; for(int i=1;i<=len;i++) { x=tree[x].ch[val(s[i])]; lazy[x]++; } } void top_sort() { queue<int>q; for(int i=1;i<=rt_sum;i++) { if(din[i]==0) { q.push(i); } } while(q.empty()==0) { int x=q.front(); q.pop(); ans[flag[x]]=lazy[x]; for(int i=0;i<e[x].size();i++) { lazy[e[x][i]]+=lazy[x]; din[e[x][i]]--; if(din[e[x][i]]==0) { q.push(e[x][i]); } } } } }A; int l[100010],r[100010],ed[100010],sum[100010],q_cnt=0,c_cnt=0; char s[100010]; int main() { int n,q,pd,x,i; char c; cin>>q>>n>>(s+1); for(i=1;i<=q;i++) { cin>>pd; if(pd==1) { cin>>x>>c; c_cnt++; ed[c_cnt]=ed[x]; A.add(ed[c_cnt],c,c_cnt); } if(pd==2) { cin>>x; c_cnt++; ed[c_cnt]=ed[x]; A.del(ed[c_cnt],c_cnt); } if(pd==3) { q_cnt++; cin>>l[q_cnt]>>r[q_cnt]; } } A.build(); A.mark(s,n); A.top_sort(); sum[0]=1; for(i=1;i<=c_cnt;i++) { sum[i]=sum[i-1]+(ed[i]==0?1:A.ans[A.id[i]]); } for(i=1;i<=q_cnt;i++) { cout<<sum[r[i]]-(l[i]==0?0:sum[l[i]-1])<<endl; } return 0; }
\(T4\) T3236.圣诞树 \(0pts\)
-
部分分
-
\(Subtask 1\) :主席树代替原来的桶,询问时枚举值域判断。
-
因 \(\operatorname{mex}\) 跑不满(数据没卡),加或不加 \(Subtask 3\) 的特判实测均可以跑 \(70pts\) 。
点击查看代码
struct node { int nxt,to; }e[1000010]; int head[500010],a[500010],top[500010],fa[500010],siz[500010],dep[500010],son[500010],vis[500010],cnt=0; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } struct PDS_SMT { int root[500010],rt_sum; struct SegmentTree { int ls,rs,sum,cnt; }tree[500010<<5]; #define lson(rt) tree[rt].ls #define rson(rt) tree[rt].rs int build_rt() { rt_sum++; return rt_sum; } void build_tree(int &rt,int l,int r) { rt=build_rt(); if(l==r) { return; } int mid=(l+r)/2; build_tree(lson(rt),l,mid); build_tree(rson(rt),mid+1,r); } void pushup(int rt) { tree[rt].cnt=tree[lson(rt)].cnt+tree[rson(rt)].cnt; } void update(int pre,int &rt,int l,int r,int pos) { rt=build_rt(); tree[rt]=tree[pre]; tree[rt].sum++; if(l==r) { tree[rt].cnt=1; return; } int mid=(l+r)/2; if(pos<=mid) { update(lson(pre),lson(rt),l,mid,pos); } else { update(rson(pre),rson(rt),mid+1,r,pos); } pushup(rt); } int query1(int rt1,int rt2,int rt3,int rt4,int l,int r,int pos) { if(l==r) { return tree[rt1].sum+tree[rt2].sum-tree[rt3].sum-tree[rt4].sum; } int mid=(l+r)/2; if(pos<=mid) { return query1(lson(rt1),lson(rt2),lson(rt3),lson(rt4),l,mid,pos); } else { return query1(rson(rt1),rson(rt2),rson(rt3),rson(rt4),mid+1,r,pos); } } int query3(int rt1,int rt2,int rt3,int rt4,int l,int r,int x,int y) { if(x<=l&&r<=y) { return tree[rt1].sum+tree[rt2].sum-tree[rt3].sum-tree[rt4].sum; } int mid=(l+r)/2,ans=0; if(x<=mid) { ans+=query3(lson(rt1),lson(rt2),lson(rt3),lson(rt4),l,mid,x,y); } if(y>mid) { ans+=query3(rson(rt1),rson(rt2),rson(rt3),rson(rt4),mid+1,r,x,y); } return ans; } }T; void dfs1(int x,int father,int n) { siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; T.update(T.root[father],T.root[x],1,n,a[x]); for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dfs1(e[i].to,x,n); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } } void dfs2(int x,int id) { top[x]=id; if(son[x]!=0) { dfs2(son[x],id); for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]&&e[i].to!=son[x]) { dfs2(e[i].to,e[i].to); } } } } int lca(int u,int v) { while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { u=fa[top[u]]; } else { v=fa[top[v]]; } } return (dep[u]>dep[v])?v:u; } int query1(int u,int v,int n) { int rt=lca(u,v); for(int i=1;i<=n;i++) { if(T.query1(T.root[u],T.root[v],T.root[rt],T.root[fa[rt]],1,n,i)==0) { return i; } } return n+1; } int main() { int n,m,u,v,flag=1,i; scanf("%d%d",&n,&m); for(i=1;i<=n;i++) { scanf("%d",&a[i]); vis[a[i]]++; flag&=(vis[a[i]]==1); } for(i=1;i<=n-1;i++) { scanf("%d%d",&u,&v); add(u,v); add(v,u); } dfs1(1,0,n); dfs2(1,1); for(i=1;i<=m;i++) { scanf("%d%d",&u,&v); printf("%d\n",query1(u,v,n)); } return 0; }
-
-
\(Subtask 3\) :因为每个数只出现一次,主席树维护一段值域内数的个数,询问时二分主席树或主席树上二分维护即可。
点击查看代码
struct node { int nxt,to; }e[1000010]; int head[500010],a[500010],top[500010],fa[500010],siz[500010],dep[500010],son[500010],vis[500010],cnt=0; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } struct PDS_SMT { int root[500010],rt_sum; struct SegmentTree { int ls,rs,sum,cnt; }tree[500010<<5]; #define lson(rt) tree[rt].ls #define rson(rt) tree[rt].rs int build_rt() { rt_sum++; return rt_sum; } void build_tree(int &rt,int l,int r) { rt=build_rt(); if(l==r) { return; } int mid=(l+r)/2; build_tree(lson(rt),l,mid); build_tree(rson(rt),mid+1,r); } void pushup(int rt) { tree[rt].cnt=tree[lson(rt)].cnt+tree[rson(rt)].cnt; } void update(int pre,int &rt,int l,int r,int pos) { rt=build_rt(); tree[rt]=tree[pre]; tree[rt].sum++; if(l==r) { tree[rt].cnt=1; return; } int mid=(l+r)/2; if(pos<=mid) { update(lson(pre),lson(rt),l,mid,pos); } else { update(rson(pre),rson(rt),mid+1,r,pos); } pushup(rt); } int query1(int rt1,int rt2,int rt3,int rt4,int l,int r,int pos) { if(l==r) { return tree[rt1].sum+tree[rt2].sum-tree[rt3].sum-tree[rt4].sum; } int mid=(l+r)/2; if(pos<=mid) { return query1(lson(rt1),lson(rt2),lson(rt3),lson(rt4),l,mid,pos); } else { return query1(rson(rt1),rson(rt2),rson(rt3),rson(rt4),mid+1,r,pos); } } int query3(int rt1,int rt2,int rt3,int rt4,int l,int r,int x,int y) { if(x<=l&&r<=y) { return tree[rt1].sum+tree[rt2].sum-tree[rt3].sum-tree[rt4].sum; } int mid=(l+r)/2,ans=0; if(x<=mid) { ans+=query3(lson(rt1),lson(rt2),lson(rt3),lson(rt4),l,mid,x,y); } if(y>mid) { ans+=query3(rson(rt1),rson(rt2),rson(rt3),rson(rt4),mid+1,r,x,y); } return ans; } }T; void dfs1(int x,int father,int n) { siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; T.update(T.root[father],T.root[x],1,n,a[x]); for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dfs1(e[i].to,x,n); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } } void dfs2(int x,int id) { top[x]=id; if(son[x]!=0) { dfs2(son[x],id); for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]&&e[i].to!=son[x]) { dfs2(e[i].to,e[i].to); } } } } int lca(int u,int v) { while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { u=fa[top[u]]; } else { v=fa[top[v]]; } } return (dep[u]>dep[v])?v:u; } int query1(int u,int v,int n) { int rt=lca(u,v); for(int i=1;i<=n;i++) { if(T.query1(T.root[u],T.root[v],T.root[rt],T.root[fa[rt]],1,n,i)==0) { return i; } } return n+1; } int query3(int u,int v,int n) { int rt=lca(u,v),l=1,r=n,mid,ans=n+1; while(l<=r) { mid=(l+r)/2; if(T.query3(T.root[u],T.root[v],T.root[rt],T.root[fa[rt]],1,n,1,mid)==mid) { l=mid+1; } else { r=mid-1; ans=mid; } } return ans; } int main() { int n,m,u,v,flag=1,i; scanf("%d%d",&n,&m); for(i=1;i<=n;i++) { scanf("%d",&a[i]); vis[a[i]]++; flag&=(vis[a[i]]==1); } for(i=1;i<=n-1;i++) { scanf("%d%d",&u,&v); add(u,v); add(v,u); } dfs1(1,0,n); dfs2(1,1); if(flag==1) { for(i=1;i<=m;i++) { scanf("%d%d",&u,&v); printf("%d\n",query3(u,v,n)); } } else { for(i=1;i<=m;i++) { scanf("%d%d",&u,&v); printf("%d\n",query1(u,v,n)); } } return 0; }
-
-
正解
- 等学了整体二分再来补。
总结
- 感觉看到题后立刻想到一个自己没学过的知识点能解决这个问题,然后自己思路就被限制住了,接着就开始着急、开始恼,对心态有不少影响。或许要学着做到初三原政治老师说的到考场后忘掉自己学过和没学过的一切,而是重新分析题意了。
- \(T1\) 成功被题目名称和题目背景迷惑,以为线性基能处理按位与和的最大值,然后就没再管。最后打 \(m=2\) 部分分时发现 \(01Trie\) 维护不了,也就推翻了线性基的做法,然后就会维护从高到低维护待选集合的正解了,过了大样例后就交了。
- \(T2\) 推式子时把 \(1\) 漏掉了,最后重新来看的时候才发现了这个问题。
- \(T4\) 主席树向右子树递归把
update(rson(pre),rson(rt),mid+1,r,pos);
写成了update(rson(pre),rson(rt),l,mid,pos);
,挂了 \(70pts\) 。
后记
- 比赛一开始是 \(ACM/ICPC\) 赛制(但有部分分)然后又换成了 \(OI\) 赛制。
- \(T3\) 中途上传数据和小样例,第一次下发的大样例还出锅了,提示时的聊天框还说得不明不白的。
- 不知道组题人怎么想的,会赛时改输入输出格式:标准输入输出->文件输入输出->标准输入输出。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18373575,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。