CSP vp 记录
CSP-S2019 JX
注:使用了 IOI 赛制。
赛时:\(100+70+64+0+0=234\),目测上了 JX 1=。
补题:\(100+100+100+0+100=400\)。
T1
分数变动:\(73 \to 64 \to 73 \to 73 \to 100\)。
首先判定月份是否合法,若不合法则可以 保留个位 或者 把十位变成 \(1\)(\(73\) 分寄因:未考虑后者情况)。
如果合法,则保留原来的月份(\(64\) 分寄因:合法时没有保留原来月份)。
然后判断日期是否在上述两种情况其中一种的月份区间之内,若是则不用改日期,否则保留个位就行。
code
#include<bits/stdc++.h> using namespace std; int m,d; int m1,m2; const int q[12][2]={{1,31},{1,28},{1,31},{1,30},{1,31},{1,30},{1,31},{1,31},{1,30},{1,31},{1,30},{1,31}}; char op; int main(){ cin>>m>>op>>d; int ans=0; if(m<1||m>12){ ans++; m1=m%10; m2=10+m%10; } else m1=m2=m; if((d<q[m1-1][0]||d>q[m1-1][1])&&(d<q[m2-1][0]||d>q[m2-1][1])) ans++; cout<<ans; return 0; }
总结:想题时,一定要将所有情况列出在草稿纸上,并多问自己是否全想到了。
涉及的知识点:分类讨论,模拟。
T2
分数变动:\(70\)。
\(70\) 分做法:令 \(sa_i=\sum_{j=1}^i a_j,sb_i=\sum_{j=1}^i b_j\),枚举每对 \((l,r)\),令 \(ans \leftarrow ans+(sa_r-sa_{l-1}) \times (sb_r-sb_{l-1})\) 即可。
考虑枚举其中右端点 \(r\),令 \(ans_r\) 表示以 \(r\) 为右端点的区间的贡献之和,则答案为 \(\sum_{i=1}^n ans_i\)。
接着推式子:
于是,我们在计算每个 \(ans_r\) 之后顺便维护 \(\sum_{l=0}^{r-1} sa_l,\sum_{l=0}^{r-1} sb_l,\sum_{l=0}^{r-1} sa_l \times sb_l\) 即可 \(O(1)\) 计算,总时间复杂度 \(O(n)\)。
code
#include<bits/stdc++.h> #define int long long using namespace std; const int N=5e5+5; int n; int ans[N]; int a[N],b[N]; int sa[N],sb[N]; const int MOD=1e9+7; signed main(){ ios::sync_with_stdio(0); cin.tie(0); cin>>n; int ans1=0,ans2=0; for(int i=1;i<=n;i++){ cin>>a[i]; sa[i]=(sa[i-1]+a[i])%MOD; } for(int i=1;i<=n;i++){ cin>>b[i]; sb[i]=(sb[i-1]+b[i])%MOD; } int ssa=0,ssb=0,ssasb=0; for(int r=1;r<=n;r++){ ans[r]=(((r%MOD*sa[r]%MOD*sb[r]%MOD-sa[r]%MOD*ssb%MOD+MOD)%MOD-sb[r]%MOD*ssa%MOD+MOD)%MOD+ssasb)%MOD; ssa=(ssa+sa[r])%MOD; ssb=(ssb+sb[r])%MOD; ssasb=(ssasb+sa[r]%MOD*sb[r]%MOD)%MOD; } int Ans=0; for(int i=1;i<=n;i++) Ans=(Ans+ans[i])%MOD; cout<<Ans; return 0; }
总结:看到这种式子比较多的题,考虑推式子,推到将每个部分都可以直接计算或维护了为止。
涉及的知识点:数学。
T3
分数变动:\(40 \to 0 \to 28 \to 28 \to 40 \to 28 \to 40 \to 40 \to 64\)。
\(64\) 分做法:依题建图并跑 kruskal 即可。
\(40\) 分寄因:将二维坐标转化为一维坐标的函数写错了(
(x-1)*n+y -> (x-1)*m+y
),以后注意这个函数的写法,经常在这里犯错。\(28\) 分寄因:建了两次边(没必要) / 数组没开两倍(因为每个点都有两条边),以后多注意数组空间问题。
\(0\) 分寄因:
N=3e5
仍然开N*N
的数组。
发现网格图中有许多重复边权,考虑整体思想。
具体而言,先将 \(a,b\) 分别从小到大排序。显然第一行与第一列是必选的,这样既能代价最小,又保证了联通。
接着维护两个指针 \(p1,p2\),每次操作若 \(a_{p1}<b_{p2}\),则选 \(a_{p1}\) 所在的这一行(注意去除行中已选列的个数,这个可以顺带维护),否则选 \(b_{p2}\) 所在的这一列(也要去除列中所选行的个数)。
code
#include<bits/stdc++.h> #define int long long using namespace std; const int N=6e5+5; int n,m,ans,cnt; int a[N],b[N]; void kruskal(){ sort(a+1,a+n+1); sort(b+1,b+m+1); int p1=2,p2=2,r=1,c=1; cnt=n+m-2,ans=a[1]*(m-1)+b[1]*(n-1); while(cnt<n*m-1){ if(a[p1]<b[p2]) ans+=a[p1++]*(m-c),r++,cnt+=m-c; else ans+=b[p2++]*(n-r),c++,cnt+=n-r; } } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cin>>n>>m; for(int i=1;i<=n;i++) cin>>a[i]; for(int j=1;j<=m;j++) cin>>b[j]; kruskal(); cout<<ans; return 0; }
总结:对于重复比较多的题目或优化 BF,可以考虑整体思想。
涉及的知识点:最小生成树的变形。
T4
skip,什么抽象线段树。
T5
分数变动:\(0\)。
\(0\) 分寄因:没把 \(0\) 下标转 \(1\) 下标。
\(30\) 分寄因:
int fnd(int x){ return (fa[x]==x?x:fa[x]==fnd(fa[x])); } xswl.
很容易想到用 dsu 去维护树上的信息,因为每个操作都可以在根上完成。
然后求方案数,并且又不好 dp,可以考虑计数。
我们令 \(sum_u\) 表示节点 \(u\) 的答案,显然每次操作二我们输出 \(sum_{\operatorname{find}(u)}\) 即可(因为信息都挂在根上)。
对于操作一,我们在 \(\operatorname{uni}\) 时完成信息的传递。
令合并的两棵树的根为 \(u,v\),\(u\) 的树内的方案数为 \(sum_u\),但他还需要选出 \(siz_u\)(子树大小)个数填进去,而它最多选 \(siz_u+siz_v-1\) 个数(他必须给 \(v\) 的树留下至少一个数,因为他们那边至少有一个节点),于是选数的方案数为 \(C(siz_u+siz_v-1,siz_u)\),总的即为 \(C(siz_u+siz_v-1,siz_u) \times siz_u\),然后 \(v\) 那边就定好数了,直接再乘一个 \(sum_v\) 即可,即 \(C(siz_u+siz_v-1,siz_u) \times siz_u \times siz_v\)。
然后这题的模数可以达到 \(10^9+7\),不能递推,因此用费马小定理即可。
code
#include<bits/stdc++.h> #define int long long using namespace std; const int N=3e5+5; const int MOD=1e9+7; int n,q,ans; int sum[N],siz[N],fa[N],fac[N]; int qpow(int x,int y){ int res=1; for(;y;x=(x*x)%MOD,y>>=1) if(y&1) res=(res*x)%MOD; return res; } void init(){ for(int i=1;i<=n;i++){ fa[i]=i; siz[i]=sum[i]=1; } fac[0]=fac[1]=1; for(int i=2;i<=n;i++) fac[i]=(fac[i-1]*i)%MOD; } int C(int x,int y){ return fac[x]*qpow(fac[y],MOD-2)%MOD*qpow(fac[x-y],MOD-2)%MOD; } int fnd(int x){ return (fa[x]==x?x:fa[x]=fnd(fa[x])); } void uni(int x,int y){ x=fnd(x),y=fnd(y); if(x!=y){ siz[y]+=siz[x]; sum[y]=sum[y]%MOD*C(siz[y]-1,siz[x])%MOD*sum[x]%MOD; fa[x]=y; } } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cin>>n>>q; init(); while(q--){ int op,x,y; cin>>op>>x; x=(x+ans)%n+1; if(op==1){ cin>>y; y=(y+ans)%n+1; uni(x,y); //cout<<C(fnd(x),fnd(y))<<'\n'; } else{ ans=sum[fnd(x)]; cout<<ans<<'\n'; } } return 0; }
总结:见上方打记的话。
涉及的知识点:并查集,组合数学。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】