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;
}
总结:见上方打记的话。
涉及的知识点:并查集,组合数学。