暑假集训CSP提高模拟2
暑假集训CSP提高模拟2
组题人: @Delov
\(T1\) T2745. 活动投票 \(30pts\)
-
懒得再复制一遍了,直接挂当时的 题解 得了。
点击查看代码
int main() { ll n,a,sum=0,ans=-0x7f7f7f7f,i; scanf("%lld",&n); for(i=1;i<=n;i++) { scanf("%lld",&a); if(ans==a) { sum++; } else { sum--; if(sum<=0) { sum=1; ans=a; } } } printf("%lld\n",ans); return 0; }
\(T2\) T2741. 序列 \(0pts\)
- 原题: UVA1608 不无聊的序列 Non-boring sequences
- 部分分
-
\(10pts\) :莫队处理出所有区间,询问即可。
点击查看代码
int a[200010],b[200010],cnt[200010],sum[200010],L[200010],R[200010],pos[200010],klen,ksum; struct ask { int l,r,id; }q[10000010]; bool q_cmp(ask a,ask b) { return (pos[a.l]==pos[b.l])?((pos[a.l]%2==1)?(a.r<b.r):(a.r>b.r)):(a.l<b.l); } void init(int n,int m) { klen=n/sqrt(m)+1; ksum=n/klen; for(int i=1;i<=ksum;i++) { L[i]=R[i-1]+1; R[i]=R[i-1]+klen; } if(R[ksum]<n) { ksum++; L[ksum]=R[ksum-1]+1; R[ksum]=n; } for(int i=1;i<=ksum;i++) { for(int j=L[i];j<=R[i];j++) { pos[j]=i; } } } void add(int x) { cnt[x]++; if(cnt[x]==1) { sum[pos[x]]++; } if(cnt[x]==2) { sum[pos[x]]--; } } void del(int x) { cnt[x]--; if(cnt[x]==1) { sum[pos[x]]++; } if(cnt[x]==0) { sum[pos[x]]--; } } int query() { for(int i=1;i<=ksum;i++) { if(sum[i]>=1) { return 1; } } return 0; } int main() { int t,n,m,l,r,flag,i,j; scanf("%d",&t); for(j=1;j<=t;j++) { scanf("%d",&n); m=flag=0; memset(cnt,0,sizeof(cnt)); memset(sum,0,sizeof(sum)); for(i=1;i<=n;i++) { scanf("%d",&a[i]); b[i]=a[i]; } sort(b+1,b+1+n); b[0]=unique(b+1,b+1+n)-(b+1); for(i=1;i<=n;i++) { a[i]=lower_bound(b+1,b+1+b[0],a[i])-b; } for(l=1;l<=n;l++) { for(r=l;r<=n;r++) { m++; q[m].l=l; q[m].r=r; q[m].id=m; } } init(n,m); sort(q+1,q+1+m,q_cmp); l=1; r=0; for(i=1;i<=m;i++) { while(l>q[i].l) { l--; add(a[l]); } while(r<q[i].r) { r++; add(a[r]); } while(l<q[i].l) { del(a[l]); l++; } while(r>q[i].r) { del(a[r]); r--; } if(query()==0) { flag=1; break; } } if(flag==0) { printf("non-boring\n"); } else { printf("boring\n"); } } return 0; }
-
- 正解
-
线段树优化 \(DP\) 。
-
设 \(f_{l,r}\) 表示 \([l,r]\) 中仅出现 \(1\) 次的数的个数。
-
从 \(r-1\) 到 \(r\) 的过程,实际上是多加了一个 \(a_{r}\) 。若 \(a_{r}\) 以前没有出现过,则 \(f_{k,r}=f_{k,r-1}+1 (k \in [1,r])\) ;否则有 \(\begin{cases} f_{k,r}=f_{k-1,r}(k \in [1,lastlast_{a_{r}}]) \\ f_{k,r}=f_{k,r-1}-1 (k \in (lastlast_{a_{r}},last_{a_{r}}]) \\ f_{k,r}=f_{k,r-1}+1 (k \in (last_{a_{r}},r]) \end{cases}\) 。序列合法当且仅当 \(\min\limits_{1 \le l \le r \le n}^{} \{ f_{l,r} \} \ge 1\) 。
-
区间修改、区间查询的线段树维护即可。
点击查看代码
int a[200010],b[200010],f[200010],pos[200010]; pair<int,int>last[200010]; struct SMT { struct SegmentTree { int l,r,minn,lazy; }tree[1600010]; int lson(int x) { return x*2; } int rson(int x) { return x*2+1; } void pushup(int rt) { tree[rt].minn=min(tree[lson(rt)].minn,tree[rson(rt)].minn); } void build(int rt,int l,int r,int a[]) { tree[rt].l=l; tree[rt].r=r; tree[rt].lazy=0; if(l==r) { tree[rt].minn=a[l]; return; } int mid=(l+r)/2; build(lson(rt),l,mid,a); build(rson(rt),mid+1,r,a); pushup(rt); } void pushdown(int rt) { if(tree[rt].lazy!=0) { tree[lson(rt)].lazy+=tree[rt].lazy; tree[rson(rt)].lazy+=tree[rt].lazy; tree[lson(rt)].minn+=tree[rt].lazy; tree[rson(rt)].minn+=tree[rt].lazy; tree[rt].lazy=0; } } void update(int rt,int x,int y,int val) { if(x<=tree[rt].l&&tree[rt].r<=y) { tree[rt].lazy+=val; tree[rt].minn+=val; return; } pushdown(rt); int mid=(tree[rt].l+tree[rt].r)/2; if(x<=mid) { update(lson(rt),x,y,val); } if(y>mid) { update(rson(rt),x,y,val); } pushup(rt); } int query(int rt,int x,int y) { if(x<=tree[rt].l&&tree[rt].r<=y) { return tree[rt].minn; } pushdown(rt); int mid=(tree[rt].l+tree[rt].r)/2,ans=0x7f7f7f7f; if(x<=mid) { ans=min(ans,query(lson(rt),x,y)); } if(y>mid) { ans=min(ans,query(rson(rt),x,y)); } return ans; } }T; int main() { int t,n,flag=0,i,j; scanf("%d",&t); for(j=1;j<=t;j++) { scanf("%d",&n); flag=0; for(i=1;i<=n;i++) { scanf("%d",&a[i]); b[i]=a[i]; last[i]=make_pair(0,0); pos[i]=0; } sort(b+1,b+1+n); b[0]=unique(b+1,b+1+n)-(b+1); for(i=1;i<=n;i++) { a[i]=lower_bound(b+1,b+1+b[0],a[i])-b; } T.build(1,1,n,f); for(i=1;i<=n;i++) { last[i].first=pos[a[i]]; last[i].second=last[last[i].first].first; pos[a[i]]=i; if(last[i].first==0) { T.update(1,1,i,1); } else { T.update(1,last[i].second+1,last[i].first,-1); T.update(1,last[i].first+1,i,1); } if(T.query(1,1,i)<=0) { flag=1; break; } } if(flag==1) { printf("boring\n"); } else { printf("non-boring\n"); } } return 0; }
-
-
官方题解
也可以分治递归求解,代码看 luogu 题解吧。
-
挂一组经典 \(hack\) 数据。
点击查看 hack 数据
in: 1 5 1 2 1 2 1 ans: boring
-
\(T3\) T6201. Legacy \(100pts\)
-
原题: CF786B Legacy
-
部分分
-
正解
-
直接挂学习笔记的 题解 了。
点击查看区间连区间代码
ll cnt=0; struct SMT_Q_BG { struct SegmentTree { ll l,r; }tree[2000010]; vector<pair<ll,ll> >e[2000010]; ll dis[2000010],id[2000010],vis[2000010]; ll lson(ll x) { return x*2; } ll rson(ll x) { return x*2+1; } void build(ll rt,ll l,ll r,ll n) { tree[rt].l=l; tree[rt].r=r; e[rt+n*4].push_back(make_pair(rt,0)); if(l==r) { id[l]=rt; return; } e[lson(rt)].push_back(make_pair(rt,0)); e[rson(rt)].push_back(make_pair(rt,0)); e[rt+n*4].push_back(make_pair(lson(rt)+n*4,0)); e[rt+n*4].push_back(make_pair(rson(rt)+n*4,0)); ll mid=(l+r)/2; build(lson(rt),l,mid,n); build(rson(rt),mid+1,r,n); } void update(ll rt,ll x,ll y,ll pd,ll n,ll t,ll w) { if(x<=tree[rt].l&&tree[rt].r<=y) { if(pd==0) { e[rt].push_back(make_pair(n*8+t,0)); } else { e[n*8+t].push_back(make_pair(rt+n*4,w)); } return; } ll mid=(tree[rt].l+tree[rt].r)/2; if(x<=mid) { update(lson(rt),x,y,pd,n,t,w); } if(y>mid) { update(rson(rt),x,y,pd,n,t,w); } } void dijkstra(ll p) { ll x,i; priority_queue<pair<ll,ll> >q; memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); dis[p]=0; q.push(make_pair(-dis[p],-p)); while(q.empty()==0) { x=-q.top().second; q.pop(); if(vis[x]==0) { vis[x]=1; for(i=0;i<e[x].size();i++) { if(dis[e[x][i].first]>dis[x]+e[x][i].second) { dis[e[x][i].first]=dis[x]+e[x][i].second; q.push(make_pair(-dis[e[x][i].first],-e[x][i].first)); } } } } } }T; void add(ll a,ll b,ll c,ll d,ll w,ll n) { cnt++; T.update(1,a,b,0,n,cnt,w); T.update(1,c,d,1,n,cnt,w); } int main() { ll n,m,p,pd,w,a,b,c,d,i; scanf("%lld%lld%lld",&n,&m,&p); T.build(1,1,n,n); for(i=1;i<=m;i++) { scanf("%lld",&pd); if(pd==1) { scanf("%lld%lld%lld",&a,&c,&w); b=a; d=c; } if(pd==2) { scanf("%lld%lld%lld%lld",&a,&c,&d,&w); b=a; } if(pd==3) { scanf("%lld%lld%lld%lld",&c,&a,&b,&w); d=c; } add(a,b,c,d,w,n); } T.dijkstra(T.id[p]); for(i=1;i<=n;i++) { if(i==p) { printf("0 "); } else { printf("%lld ",(T.dis[T.id[i]+n*4]==0x3f3f3f3f3f3f3f3f)?-1:T.dis[T.id[i]+n*4]); } } return 0; }
-
\(T4\) T2731. DP搬运工1 \(22pts\)
-
部分分
-
\(22pts\) :生成所有全排列,依次判断。
点击查看代码
const ll p=998244353; ll a[100]; int main() { ll n,k,sum,ans=0,i; cin>>n>>k; for(i=1;i<=n;i++) { a[i]=i; } do { sum=0; for(i=2;i<=n;i++) { sum+=max(a[i],a[i-1]); } ans=(ans+(sum<=k))%p; }while(next_permutation(a+1,a+1+n)); cout<<ans<<endl; return 0; }
-
-
正解
- 预设性 \(DP\) 主要思路是枚举填哪个数。
- 钦定从小到大填数,设 \(f_{i,j,k}\) 表示填到 \(i\) 时, \([1,n]\) 中共有 \(j+1\) 个连续段已经填数了,相邻两数最大值之和为 \(k\) 的方案数。
- 考虑刷表。
- 若新填入的数在两端
- 若没有相邻元素,填入的数则不会对答案产生贡献,有 \(f_{i,j+1,k}+=2f_{i-1,j,k}\) 。
- 若有相邻元素,填入的数则会对答案产生贡献,有 \(f_{i,j,k+i}+=2f_{i-1,j,k}\) 。
- 若新填入的数在中间
- 若没有相邻元素,有 \(j\) 个位置可以填数(可以把序列分开,硬插进去),有 \(f_{i,j+1,k}+=j \times f_{i-1,j,k}\) 。
- 若有一个相邻元素,有 \(j\) 个位置可以填数,有 \(f_{i,j,k+i }+=2j \times f_{i-1,j,k}\) 。
- 若有两个相邻元素,有 \(j\) 个位置可以填数,有 \(f_{i,j-1,k+2i}+=j \times f_{i-1,j,k}\) 。
- 若新填入的数在两端
- 边界为 \(f_{1,0,0}=1\) 。
- 最终,有 \(\sum\limits_{i=0}^{m}f_{n,0,i}\) 即为所求。
点击查看代码
const ll p=998244353; ll f[60][60][2510]; int main() { ll n,m,ans=0,i,j,k; cin>>n>>m; f[1][0][0]=1; for(i=2;i<=n;i++) { for(j=0;j<=n-i+1;j++) { for(k=0;k<=m;k++) { f[i][j+1][k]=(f[i][j+1][k]+2*f[i-1][j][k]%p)%p; f[i][j][k+i]=(f[i][j][k+i]+2*f[i-1][j][k])%p; f[i][j+1][k]=(f[i][j+1][k]+j*f[i-1][j][k]%p)%p; f[i][j][k+i]=(f[i][j][k+i]+2*j*f[i-1][j][k]%p)%p; if(j-1>=0) { f[i][j-1][k+2*i]=(f[i][j-1][k+2*i]+j*f[i-1][j][k]%p)%p; } } } } for(i=0;i<=m;i++) { ans=(ans+f[n][0][i])%p; } cout<<ans<<endl; return 0; }
总结
- \(T1\) 看到是原题后,马上就写了,结果把将
sum=1
写成sum++;
了,迷之自信让我没再去测别的样例,挂了 \(70pts\) 。 - \(T2\) 两次大样例有点弱,都把乱搞做法放过去了。
- \(T3\) 线段树优化建图贺的 luogu P6348 [PA2011] Journeys 代码;边权的处理想了想就会了;当时写的是 \(01BFS\) ,赛时在想 \(Dijkstra\) 怎么写,真就应了前几天听牛客的课时授课老师说的“要把广搜,\(Dijkstra,SPFA\),堆优化 \(Prim\) 这几个容易搞混的代码之间的不同点搞清楚,别整的最后场上一直在纠结某个地方应该怎么写”。
后记
- 压缩包头一次见有密码,密码是
mimashimima
。 - @xrlong 瑞平“不出线性 \(DP\) 的模拟赛不是好模拟赛,不出区间 \(DP\) 的模拟赛不是好模拟赛” ,被 @Delov 学长听见了。
- \(T2\) @Delov 学长实行赛时翻提交代码,然后加 \(hack\) 政策。
- @Delov 学长称整场都是让见见题的,一些套路如果之前不知道的话就感觉不太可做,符合昨天所说的今天这场模拟赛会很难。
- 貌似 @Delov 学长在比赛开始 \(1h\) 左右,就开始看榜上前几及各题首切的人的 luogu 主页、各原题提交记录、博客园和少量博客,目测看了 @xrlong , @wkh2008 和我的。炸裂的是看 @xrlong 的 2024.7.5 鲜花 将我给的图点开后,找到我博客时随机出来的也是这张图;看我博客的时候看着他对着摘要愣了会儿,不知道在干啥。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18312248,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。