模拟赛总结
2024.2.6 【寒假集训】20240206测试
T1 珠子
看来是关于双指针的神秘东西。
T2 数组
这个题,我没考虑到的是要保留全部的 x,yx,y 操作信息,以及上一次 AA 操作的时间等等。
代码(参考 lcy):
#include<bits/stdc++.h> #define int long long using namespace std; int n,m; int t1[500001],t2[500001]; int lst[50000100]; int pos,dx; signed main() { cin>>n>>m; int su=(n+1)*n/2; pos=0,t1[0]=1,t2[0]=0; // for(int i=1;i<=n;i++)lst[i]=i; for(register int i=1;i<=m;++i) { char c=getchar(); int x,y;cin>>x>>y; t1[i]=x,t2[i]=y; if(c=='A') { pos=i,dx=0; printf("%lld\n",x*su+n*y); } else { if(lst[t1[i]]<=pos) { dx+=t2[i]-(t1[i]*t1[pos]+t2[pos]); lst[t1[i]]=i; } else { int last=lst[t1[i]]; dx-=t2[last]-(t1[i]*t1[pos]+t2[pos]); dx+=t2[i]-(t1[i]*t1[pos]+t2[pos]); lst[t1[i]]=i; } printf("%lld\n",t1[pos]*su+t2[pos]*n+dx); } } return 0; }
T3 幸运区间
题意:在序列 aa 中,求出所有子序列 bb 中, gcd(b)=1gcd(b)=1 的个数。
(∼∼ 表示从 xx 到 yy 的所有元素)
考虑画出一个表示所有子序列的 gcdgcd 的三角形矩阵:
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 5 1 4 1 3 1 2 1 1 5 5 4 4 3 3 2 2 1 1
我们可以看到,从最下面开始,对于每个左端点确定的区间,只要在右端点为 rr 的时候 gcd=1gcd=1 ,那么从[l,r][l,r] 到 [l,n][l,n],所有区间的 gcd=1gcd=1。
有一个定理:只要一个序列的子序列 gcd=1gcd=1,那么这个序列 gcd=1gcd=1。
所以,我们需要实现两个东西:
-
查找区间 [l,r][l,r] 的 gcdgcd。
-
找到对于一个 l∈[1,n]l∈[1,n],rr 最小并且 gcd=1gcd=1 的子序列,统计答案
ans+=n-r+1
。
对于查找,由上面的定理可知, gcdgcd 具有传递性,因此我们可以构建一棵线段树来实现此操作。
tr[now].gcd=__gcd(tr[lid].gcd,tr[rid].gcd);
对于找到一个 ll、满足条件的最小的 rr,我们考虑直接 for 循环去找(区间伸缩)。
#include<bits/stdc++.h> using namespace std; #define int long long int n,m; int a[200100]; class emw{ public: #define lid now<<1 #define rid now<<1|1 void build(int now,int l,int r) { if(l==r){ tr[now].g=a[l];return ; } int mid=(l+r)>>1; build(lid,l,mid),build(rid,mid+1,r); tr[now].g=__gcd(tr[lid].g,tr[rid].g); } int getgcd(int now,int l,int r,int x,int y) { if(x<=l&&r<=y)return tr[now].g; int mid=(l+r)>>1; int res=0; if(x<=mid) res=__gcd(res,getgcd(lid,l,mid,x,y)); if(y>mid) res=__gcd(res,getgcd(rid,mid+1,r,x,y)); return res; } private: struct segTree{ int g; }tr[200100<<2]; }st; int ans; signed main() { cin>>n; for(int i=1;i<=n;i++)cin>>a[i]; st.build(1,1,n); int l=1,r=1; for(l=1;l<=n;l++) { r=max(l,r); while(l<=r&&r<=n) { if(st.getgcd(1,1,n,l,r)==1) { break; } r++; } ans+=(n-r+1); } cout<<ans; return 0; }
T4 找不同
官方题解的 RMQ 我没理解,但是理解了题解区某位大佬的线性 DP,深受震撼。
思路是这样的:
- 定义 dp[i]dp[i] 表示以 ii 为右端点、最长的、使得区间内没有重复出现单词的左端点。
形式化地,定义 dp[i]dp[i] 表示 dp[i]=j,∀x,y∈[j,i],a[x]≠a[y]dp[i]=j,∀x,y∈[j,i],a[x]≠a[y],并且对于所有 jj 符合,要求 j=dp[i]j=dp[i] 最小。
- 状态转移时,我们会用
map<string,int>last
记录当前字符串 a[i]a[i] 上一次出现的位置(下标),显然 dp[i]dp[i] 有可能从 last[a[i]]+1last[a[i]]+1 转移而来,并且 dp[i]dp[i] 的值不会小于等于 last[a[i]]last[a[i]]。
-
那么还能怎么转移?
-
我们在转移 i−1i−1 之后,已经知道了 dp[i−1]dp[i−1] 的值,意思就是我们知道了以 i−1i−1 为右端点的合法区间的最大长度。如果在这个区间 [ji−1,i−1][ji−1,i−1] 中没有 a[i]a[i] ,那么 dp[i]dp[i] 一定等于 dp[i−1]dp[i−1]。
-
如果 last[a[i]]>dp[i−1]last[a[i]]>dp[i−1] ,就是在上一个合法区间内有一个 a[i]a[i],那么 dp[i]=last[a[i]]+1dp[i]=last[a[i]]+1。
-
综上所述,我们可以得到状态转移方程:
- 对于每个询问 x,yx,y,我们只需要判断 dp[y]≤xdp[y]≤x 即可得到答案。
意思是如果 dp[y]≤xdp[y]≤x,那么 xx 在 yy 对应的合法区间内,一定满足要求,输出 YES
;
否则输出 NO
。
#include<bits/stdc++.h> using namespace std; int n,m; string a[100101]; int dp[100101]; map<string,int>last; int main() { cin>>n; for(int i=1;i<=n;i++) { cin>>a[i]; dp[i]=max(dp[i-1],last[a[i]]+1); last[a[i]]=i; } cin>>m; while(m--) { int x,y;cin>>x>>y; if(dp[y]<=x) { cout<<"YES\n"; } else cout<<"NO\n"; } }
2024.2.18 【寒假集训】20240218测试
T1 家庭作业
太可恶了!!!
没想到被离散化背刺了。
这个题思路很简单,只需要找出 aa 和 bb 的所有质因数是什么及其个数。
我考场上用的桶,想着省事开了个 map
,结果炸了。
考完试改了十分钟,把一个桶改成两个数组,加上 cntcnt 就过了。
真令人忍俊不禁。
for(int i=1;i<=cnta;i++) { int kk=findb(ax[i]); if(kk>0) { // cout<<min(aa[i],bb[i])<<" "<<i<<endl; tag[ax[i]]=1; ans*=(ppow(ax[i],min(aa[i],bb[kk])))%mod; ans%=mod; } } for(int i=1;i<=cntb;i++) { int kk=finda(bx[i]); if(kk>0&&!tag[bx[i]]) { tag[bx[i]]=1; ans*=(ppow(bx[i],min(aa[kk],bb[i])))%mod; ans%=mod; } }
T2 距离之和
更令人忍俊不禁。
考场上想到二分优化的做法,但是二分一直神奇地出问题。
二分:
考虑每个转移,只会影响 xx 轴或 yy 轴上的距离。
- 如果是
S
或J
,那么 xx 轴方向上的距离都不会变 - 如果是
I
或Z
,那么 yy 轴方向上的距离都不会变
我们可以考虑分别以纵坐标和横坐标为关键字,对所有控制点进行排序。
然后就可以二分查找找到当前机器人坐标的相对位置,并记录其变化量就能得出答案。
还有两种说法,第一种用了权值线段树,60 分;
第二种 lhx 用了计数前缀和直接秒掉二分。
题解给的正解就是二分!!!!!
T3 country
T4 太空飞船
2024.2.19 【寒假集训】20240219测试
T1 素数
小黄题,无话可说。
搞前缀和就行了。
T2 晨练
小 dpdp,是我的能力范围之外,用 dfsdfs 骗了 20 分。
定义 dp[i][j]dp[i][j] 表示在第 ii 分钟,疲劳度为 jj 时的最长跑步距离。
对于每一个 ii,有四种状态:
-
疲劳度不为 00,且要继续跑
-
疲劳度不为 00,且要休息
-
疲劳度为 00,且要开始跑
-
疲劳度为 00,且要休息
由此,我们分类讨论,可以得到:
#include<bits/stdc++.h> #define int long long using namespace std; int n,a[10005]; int dp[10005][505]; int m; signed main() { cin>>n>>m; for(int i=1;i<=n;i++) cin>>a[i]; dp[1][1]=a[1]; for(int i=1;i<=m;i++) dp[0][i]=-1145141919; for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { dp[i][j]=dp[i-1][j-1]+a[i]; // dp[i+j][0]=max(dp[i][j],dp[i+j][0]); }dp[i][0]=dp[i-1][0]; for(int j=i-1,k=1;j&&k<=m;j--,k++) { //j-i-p,k=j+p dp[i][0]=max(dp[i][0],dp[j][k]); } } cout<<dp[n][0]; }
T3 奇怪的桌子
神秘组合 dp,用纯组合骗了 40 分。
那就是当 n=m 时,答案是 Ckn2。
通过模拟,我们可以知道对于第 i 列和第 i 列的放法一定是相同的
考虑 dp,令 dp[i][j] 表示第 i 列放 j 个的方案数,
通过神秘的感性理解,可以得到状态转移方程:
其中 [i≤m%n] 表示当 i≤m%n 成立时为 1,否则为 0 (一种神秘的 bool 表达式)。
但是,这样写会 T 掉几个点,必须先预处理 Cmn+[i≤m%n]n 这一堆。
预处理后时间复杂度为 O(n3) 左右(也有说法是 O(n3+n×max(n,k)))。
dp[0][0]=1; for(int i=1;i<=n;i++)//预处理 { for(int j=0;j<=max(n,k);j++) { qp[i][j]=ppow(getc(n,j),(m/n+(i<=m%n)))%mod; } } for(int i=1;i<=n;i++) { for(int j=0;j<=min(i*n,k);j++) { for(int l=0;l<=min(n,j);l++) { dp[i][j]=(dp[i][j]+(dp[i-1][j-l]*qp[i][l])%mod)%mod; } } } cout<<dp[n][k];
T4 学校
神秘最短路+tarjan 求割边。
2024.2.21【寒假集训】20240221测试
寄的最惨的一次。。
T1 排序
这是个数学题。
给定 n 和 4n 个元素,要求将其分为 n 组,使得对于每组四个数 a,b,c,d,所有组中 ∣ab−cd∣ 的和最大,求最大和。
一开始我看了一眼,没多想就去看 T2 T3 T4 了,结果 T4 写了两个小时挂了,回来再看 T1 已经神志不清了。。
现在用不等式证明一下:
设有 8 个数,分别为 a1,a2,a3,a4,并且 a1>a2>a3>a4。
我们可以知道,所有的 ab−cd,有这几种可能:
我们要找 a1a2−a3a4 和 a1a3−a2a4 的关系,可以这么写:
其他证明同理。
最终我们可以得到这个不等式链:
所以,大的数应该相邻搭配,小的数应该一大一小搭配。
用样例去不完全归纳也可以得到这个结论。
答案序列:5,5,3,1 4,3,2,1
#include<bits/stdc++.h> #define int long long using namespace std; int n; int a[400005],ans; signed main() { cin>>n; for(int i=1;i<=4*n;i++) { cin>>a[i]; } sort(a+1,a+4*n+1); for(int i=1;i<=n;i++) { ans+=a[2*n+2*i-1]*a[2*n+2*i]-a[i]*a[2*n-i+1]; } cout<<ans; return 0; }
T2 牛吃草
神秘 dp,是我的认知范围之外:所有我解释不了的题目不是神秘 dp 就是科技数据结构。
首先二分答案 size,对于每个 size,跑一遍神秘 dp 计算答案:
令 dp[i] 表示考虑完 [1,i] 后得到的最大覆盖长度。
则:
神秘 dp 跑完之后,如果 dp[n]≥m,m 表示s×n100,就记录答案。
这样能搞到 75。
bool ck(int siz) { for(int i=1;i<=n;i++) dp[i]=0; for(int i=1;i<=n;i++) { dp[i]=dp[i-1]; for(int j=i-a[i];j<=i-siz;j++) dp[i]=max(dp[i],dp[j]+(i-j)); } if(dp[n]>=m) return 1; return 0; }
考虑优化:
观察:
可以感性地得到:
下限单调不降,相当于滑动窗口 [i−wi,i−size],可以单调队列维护。
#include<bits/stdc++.h> using namespace std; int n,a[1000101]; int ans; double m; int dp[1000010]; int q[1001001]; bool ck(int siz) { for(int i=1;i<=n;i++) dp[i]=0,q[i]=0; int tt=1,hh=1;q[1]=1; for(int i=siz;i<=n;i++) { if(a[i]<siz) { dp[i]=dp[i-1];continue; } while(hh<=tt&&dp[i-siz]-i+siz>=dp[q[tt]]-q[tt]) tt--; q[++tt]=i-siz; while(hh<=tt&&i-a[i]>q[hh])hh++; dp[i]=max(dp[i-1],dp[q[hh]]+i-q[hh]); } if(dp[n]>=m) return 1; return 0; } int main() { cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; int s;cin>>s; m=ceil(n*s/100.0); int l=1,r=n; while(l<=r) { int mid=(l+r)>>1; if(ck(mid)) ans=mid,l=mid+1; else r=mid-1; } cout<<ans; }
2024.2.22【寒假集训】20240222测试
T1 打赌
大大大模拟。
空空空间思维。
判判判断。
这样我们可以找规律:
- 当 cmod4=1 时,列以 4 个一循环。
- 当 cmod4=2 时,列以 6 个一循环。
- 当 cmod4=3 时,列以 2 个一循环。
- 当 cmod4=0 时,列以 1 个一循环。
然后对于循环,预处理出每一列的和,最后一加就可以了。
搞了一个比较骚的 namespace。
#include<bits/stdc++.h> #define int long long using namespace std; int n,c; int line[7]; namespace Rotation{ void rotateleft(int &top,int &bottom,int &left,int &right) { int tmp=top; top=left,left=bottom,bottom=right,right=tmp; return ; } void rotatefront(int &top,int &bottom,int &front,int &back) { int tmp=top; top=back,back=bottom,bottom=front,front=tmp; return ; } void rotateright(int &top,int &bottom,int &left,int &right) { int tmp=top; top=right,right=bottom,bottom=left,left=tmp; return ; } } void init() { int top=1,bottom=6,front=2,back=5,left=3,right=4; for(int k=1;k<=6;k++) { int cnt=0; for(int i=1;i<c;i++) { cnt+=top; if(k%2==1) Rotation::rotateright(top,bottom,left,right); else Rotation::rotateleft(top,bottom,left,right); }cnt+=top; line[k]=cnt; Rotation::rotatefront(top,bottom,front,back); } } int cntline=0; signed main() { cin>>n>>c; init();int tp=c%4,kk=0; if(tp==1) kk=4; else if(tp==2) kk=6; else if(tp==3) kk=2; else kk=1; for(int i=1;i<=kk;i++) cntline+=line[i]; cntline*=(n/kk); for(int i=1;i<=n%kk;i++) cntline+=line[i]; cout<<cntline; }
T2 舞会
对于这种不需要动脑的 ** 数据结构题,就不要考虑朴素算法。
我们考虑使用权值线段树去模拟平衡树操作。
但是不会。
那我们可以使用一个万能小 map 加上万能lower(upper)_bound 帮助我们找一个数的前驱和后继。
这样权值线段树就起到了辅助我们判断是否有合法的匹配,map 来找前驱后继。
然后分别在这两个数据结构里删点即可。
正数和负数要开两个。
要注意判断是否有合法时 now 要加一或减一。
码子有点长,有点丑,放到里头了:
#include<bits/stdc++.h> using namespace std; int n,a[101000],b[101000]; struct segmentTree{ public: class node{ public: int sum,l,r; }tr[30000<<2]; #define lid now<<1 #define rid now<<1|1 void update(const int now,const int l,const int r,const int x,const int y,const int k) { if(x<=l&&r<=y) { tr[now].sum+=k;return ; } const int mid=(l+r)>>1; if(x<=mid) update(lid,l,mid,x,y,k); if(y>mid) update(rid,mid+1,r,x,y,k); tr[now].sum=tr[lid].sum+tr[rid].sum; } int query(const int now,const int l,const int r,const int x,const int y) { if(x<=l&&r<=y) return tr[now].sum; const int mid=(l+r)>>1; int res=0; if(x<=mid) res+=query(lid,l,mid,x,y); if(y>mid) res+=query(rid,mid+1,r,x,y); return res; } }st[2]; long long ans; map<int,int>gir1,gir0; int main() { cin>>n; for(int i=1;i<=n;i++) { int t;cin>>t; st[t>=0].update(1,1,2500,abs(t),abs(t),1); if(t>0) gir1[t]++; else gir0[-t]++; } for(int i=1;i<=n;i++) { int now;cin>>now; if(now>0) { if(st[0].query(1,1,2500,now+1,2500)>=1) { int kk=(gir0.upper_bound(now))->first; ++ans; st[0].update(1,1,2500,kk,kk,-1); gir0[kk]--; if(gir0[kk]==0) gir0.erase(kk); } } else { if(st[1].query(1,1,2500,0,-now-1)>=1) { int kk=(--gir1.lower_bound(-now))->first; ++ans; st[1].update(1,1,2500,kk,kk,-1); gir1[kk]--; if(gir1[kk]==0) gir1.erase(kk); } } } cout<<ans; }
T3 最小生成树
这个题看似是一个图论题,实际上是数学题。
- 对于要留下的边的个数,我们知道是要互质的。
答案就是每个点欧拉函数乘积。
可以使用 wme 大佬的线性筛,也可以使用简单质朴的神秘筛法:
inline int euler() { for(int i=1;i<=n;++i) phi[i]=i; for( int i=2;i<=n;++i) { if(phi[i]==i) { for( int j=i;j<=n;j+=i) phi[j]=phi[j]/i*(i-1); } } }
T4 买汽水
这道题可以使用神秘 dp 骗到 100 分。
但是正解是搜素,对半搜索加上神秘剪枝。
参考:(不是我写的)
#include<bits/stdc++.h> #define ll long long using namespace std; int n; ll ans=0,m,a[50]; void dfs(int x,ll k){ if(x==n+1){ if(k<=m) ans=max(ans,k); return ; } k+=a[x]; if(k==m||ans==m){//再加点剪枝? ans=m; return ; } if(k<=m) dfs(x+1,k); k-=a[x]; dfs(x+1,k); } int main(){ scanf("%d%lld",&n,&m); ll sum=0; for(int i=1;i<=n;i++){ scanf("%lld",&a[i]); sum+=a[i]; } if(sum<=m){ printf("%lld",sum); return 0; } dfs(1,0); printf("%lld",ans); return 0; }
20240405测试
我爱期望。。。
T1 [JLOI2014] 聪明的燕姿
这是数学。用线性筛去筛约数和然后循环判断可以拿 28 分。
T2 luogu4550收集邮票
纯期望。
考虑倒推,取 i 次剩下的期望 f[i]=f[i+1]×nn−i。
取 i 次的期望得分 g[i]=in−i+g[i+1]+f[i+1]×f[i]+nn−i
for(int i=n-1;~i;i--) { f[i]=f[i+1]+1.0*n/(1.0*(n-i)); g[i]=(1.0*i)/(1.0*n-i)*f[i]+g[i+1]+f[i+1]+n/(1.0*(n-i)); }
答案就是 g[0]。
20240503测试
T1 [CF1279C]Stack of Present
就是小贪心,记录探过的最深的地方,每次更新。
T2 [luogu5522]棠梨煎雪
T3 [luogu1174]打砖块
神秘三维 dp,维护 dp[i][j][0/1] 表示到第 i 列,用过 j 颗子弹的最大得分,0/1 表示这一列有没有向前面借子弹。
T4 「NOIP2015」斗地主
超级神秘大模拟,使用 dfs 加回溯。
这是搜索的顺序,至于为啥,咱也不知道,咱也不敢问。
心肺骤停。。。就说为啥调不出来。
这直接无缝衔接,把小王大王读成 A 了。。。
END
set up on 24.2.6
upd on 24.2.15
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战