CSP vp 记录

Posted on 2024-09-07 19:30  _XOFqwq  阅读(3)  评论(0编辑  收藏  举报

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=1}^r (sa_r-sa_{l-1}) \times (sb_r-sb_{l-1}) \\ =\sum_{l=1}^r sa_r \times sb_r-sa_r \times sb_{l-1}-sa_{l-1} \times sb_r+sa_{l-1} \times sb_{l-1} \\ =r \times sa_r \times sb_r-r \times sa_r \times \sum_{l=0}^{r-1} sb_l-r \times sb_r \times \sum_{l=0}^{r-1} sa_l+\sum_{l=0}^{r-1} sa_l \times sb_l \]

于是,我们在计算每个 \(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;
}

总结:见上方打记的话。

涉及的知识点:并查集,组合数学