做题笔记(二)

[CSP-S 2023] 消消乐

题目传送门

[CSP-S 2023] 消消乐

思路

考虑 DP。

显然,设 fi 表示以位置 i 结尾的可消除序列的个数,我们对每一个可消除序列考虑模型,大概就是这个样子:aabb

那么我们当前位置为 i,前一个可以与 i 匹配的最大的下标为 lowi,显然:

fi=flowi1+1

到这里其实都很简单的,去年的我考场上也是想到这里就戛然而止,最后写了一个 50pts 的暴力遗憾离场。

但我们可以考虑一个一个往前找,就是那个 O(n2) 的暴力做法,考虑优化。

假设当前位置为 ilow1i 已经求出,那么我们注意到类似于 aa 的子串中的省略号一定是由若干个可消除字串。那么我们可以一个一个往前跳。

由于最多只有 26 个字母,所以每一次向前跳的期望应该是一个常数级别,所以时间复杂度是线性的。

代码

#include <bits/stdc++.h>
#define int long long
using namespace std;

string s;
int f[2000005]={0},n,ans=0;
int last[2000005]={0};

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n>>s; s=" "+s;
	
	for(int i=1;i<=n;i++){
		for(int j=i-1;j>0;j=last[j]-1){
			if(s[i]==s[j]){
				last[i]=j;
				break;
			}
		}
		
		if(last[i]!=0)
			f[i]+=(1ll+f[last[i]-1]);
	}
	
	for(int i=1;i<=n;i++)
		ans+=f[i];
		
	cout<<ans<<endl;
	
	return 0;
}

[12.3 互测 A] 简单

题目传送门

不知道有没有原题咕咕咕。

题目大意

你要炸掉在 n 个位置上的一些方块,一开始 i 号位置有 hi 个方块。

定义裸露的方块为它的上方,左方,右方有一个方向没有方块。

定义一次轰炸为消除所有裸露的方块。求出要使所有方块消除所需的轰炸次数。

1n105

1hi109

思路

一道很神奇的思维题。其实难度不大,但是硬控了我 1h。

定义 fi 为第 i 个位置的消除时间。

不难看出,fi 只能由 fi1fi+1 转移过来。我们可以正反跑两边。

注意到,每一个位置只能是从旁边消除或者从上面一个一个消除下来。

那么:

i[1,n],fi=1

i[1,n],fi=min{fi1+1,hi}

反过来也一样。

之间复杂度 O(n)

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;

const int MAXN=1e5+5;
int n,h[MAXN]={0},f[MAXN]={0};

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n;
	
	for(int i=1;i<=n;i++)
		cin>>h[i];
		
	for(int i=1;i<=n;i++){
		if(i==1) f[i]=1;
		else f[i]=min(h[i],f[i-1]+1);
	}
	
	for(int i=n;i>=1;i--){
		if(i==n) f[i]=1;
		else f[i]=min(f[i+1]+1,f[i]);
	}
	
	int ans=0;
	
	for(int i=1;i<=n;i++)
		ans=max(f[i],ans);
		
	cout<<ans<<endl;
	
	return 0;
}

字串距离

题目传送门

字串距离

思路

比较能看出来这是一个 DP,难度大概在 CSP-S 的 T1 或者 T2。

很容易想到设 fi,j 为字符串 A 的前 i 个和字符串 B 的前 j 个的最小距离。

转移方程还是比较板的:

fi,j=min{fi1,j1+|AiBj|,fi1,j+k,fi,j1+k}

但是这样直接写之后答案会错,转移方程的正确性不言而喻,考虑初始化。

显然有:f0,0=0

然后就可以发现:

fi,0=i×k

f0,j=j×k

加上初始化后就可以轻松 AC,时间复杂度 O(|A||B|)

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;

string a,b;
int k,f[2005][2005]={0};

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>a>>b>>k;
	a=" "+a; b=" "+b;
	
	f[0][0]=0;
	
	for(int i=1;i<a.size();i++)
		f[i][0]=i*k;

	for(int i=1;i<b.size();i++)
		f[0][i]=i*k;
	
	for(int i=1;i<a.size();i++){
		for(int j=1;j<b.size();j++){
			f[i][j]=min(min(f[i-1][j]+k,f[i][j-1]+k),f[i-1][j-1]+abs(a[i]-b[j]));
		}
	}
	
	cout<<f[a.size()-1][b.size()-1]<<endl;
	
	return 0;
}

教主的花园

题目传送门

教主的花园

思路

这道题很显然是 DP。

先设一个 DP 转移的数组:fi,j 表示考虑到第 i 个位置,且种了第 j 棵树。

发现如果 j=2 的话就满足不了题目要求,所以加上一维表示当前这个位置是比旁边低还是比旁边高。

显然有:

fi,1,0=max{fi1,2,1,fi1,3,1}+ai

fi,2,0=fi1,3,1+bi

fi,2,1=fi1,1,0+bi

fi,3,1=max{fi1,1,0,fi1,2,0}+ci

然后加上环形 DP 的简单处理就可以了,时间复杂度 O(n)

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;

const int MAXN=2e5+5;
int n,a[MAXN],b[MAXN],c[MAXN];
int f[MAXN][4][2]={0},ans=0;

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n;
	
	for(int i=1;i<=n;i++)
		cin>>a[i]>>b[i]>>c[i];
		
	for(int k=1;k<=3;k++){
		memset(f,0,sizeof(f));
		if(k==1) f[1][1][0]=a[1];
		else if(k==2) f[1][2][0]=b[1],f[1][2][1]=b[1];
		else f[1][3][1]=c[1];
		
		for(int i=2;i<=n;i++){
			f[i][1][0]=max(f[i-1][2][1]+a[i],f[i-1][3][1]+a[i]);
			f[i][2][0]=f[i-1][3][1]+b[i];
			f[i][2][1]=f[i-1][1][0]+b[i];
			f[i][3][1]=max(f[i-1][1][0]+c[i],f[i-1][2][0]+c[i]);
		}

		if(k==1) ans=max(max(f[n][2][1],f[n][3][1]),ans);
		else if(k==2) ans=max(max(f[n][3][1],f[n][1][0]),ans);
		else ans=max(max(f[n][1][0],f[n][2][0]),ans);
	}
	
	cout<<ans<<endl;
	
	return 0;
}

Battling with Numbers

题目传送门

Battling with Numbers

思路

一个很思维的思维题。发现洛谷上的题解证明部分略少,故此处给出较严谨的证明。

  • 命题一:YX

证明:因为 gcd(p,q)=Y,所以 Yp,Yq,所以 Y2pq。有因为 lcm(p,q)=X,所以 X=pqgcd(p,q),所以 X=pqY。同乘一个 1Y,得到:XY=pqY2,由于 Y2pq,所以 YX。证毕。

  • 命题二:不存在一个质数 g,使得 gY 并且满足 gp,gq

证明:假设 g 存在,那么 gp,gq,那么 g2pq,又因为 gY,并且 Y2pq,那么 Y2g2pq,所以 gcd(p,q)>Y,与 gcd(p,q)=Y 矛盾,所以这样的 g 不存在。证毕。

  • 命题三:令 kXY 的不同质因子个数,那么 p,q 数对的个数为 2k

证明:将 pqY2 分解质因数,也就是 pqY2=i=1nPiei,其中 Pi 为质数。根据命题二,我们可以得出一个引理:不存在一个质数 g,使得 gpY,gqY。同时,我们把 XY 分解质因数,那么 XY=i=1kDiwi,由于 pqY2=XY,所以 i=1nPiei=i=1kDiwi。根据唯一分解定理和命题二的引理,Pi 一定和 Di 一一对应并且每一个 Piei 一定只存在于 pq 的其中一个。所以对于 XY 的不同质因子的个数即 k,每一个 Diwi 一定是存在 p 中或者 q 中两种可能。根据乘法原理,可得答案即为 2k。证毕。

根据命题三,我们只需要分解 XY 的质因数就可以得到答案了。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;

const int MAXN=1e5+5,MOD=998244353;
const int MAXNUM=2e6+5;
int n,a[MAXN]={0},b[MAXN]={0};
int m,c[MAXN]={0},d[MAXN]={0};
int pa[MAXNUM]={0};
int cnt=0;

int qpow(int a,int b){
	int res=1;
	
	while(b){
		if(b&1) res=((res%MOD)*(a%MOD))%MOD;
		a=((a%MOD)*(a%MOD))%MOD;
		b>>=1;
	}
	
	return res;
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n;
	
	for(int i=1;i<=n;i++)
		cin>>a[i];
		
	for(int i=1;i<=n;i++){
		cin>>b[i];
		pa[a[i]]=b[i];
	}
		
	cin>>m;
	
	for(int i=1;i<=m;i++)
		cin>>c[i];
		
	for(int i=1;i<=m;i++)
		cin>>d[i];
	
	for(int i=1;i<=m;i++){
		if(pa[c[i]]<d[i]){
			cout<<0<<endl;
			return 0;
		}
		else pa[c[i]]-=d[i];
	}
	
	for(int i=1;i<=n;i++)
		if(pa[a[i]]>0) cnt++;
		
	cout<<qpow(2ll,cnt)<<endl;
	
	return 0;
}

[翟翟 OI Round #1 1A] 征兵

题目传送门

[翟翟 OI Round #1 1A] 征兵

思路

一个需要小小思考的绿题,难度可能比 CSP-S 的 T1 稍低。

显然面对这种亲戚关系,使用并查集是可以的。也就是说,在处理完这若干个亲戚关系之后,我们可以得到一堆亲戚集合 S1,S2,,Sk,那么答案就是每一个集合中元素最大值的累加,也就是 kSimax

复杂度取决于并查集。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;

const int MAXN=3000010;
int f[MAXN]={0},ct=0,mx[MAXN]={0},ans=0,a[MAXN];
bool b[MAXN]={0};

int find(int v){
	if(f[v]==v) return v;
	else return f[v]=find(f[v]);
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	int n,u,v; cin>>n;
	for(int i=1;i<=n;i++)
		f[i]=i;
		
	for(int i=1;i<=n;i++)
		cin>>a[i];
		
	while(cin>>u>>v){
		int x=find(u),y=find(v);
		
		if(x!=y)f[x]=y;
	}
	
	for(int i=1;i<=n;i++){
		if(!b[find(i)]) ct++;

		b[find(i)]=1;
		mx[find(i)]=max(a[i],mx[find(i)]);
	}
	
	for(int i=1;i<=n;i++)
		if(b[i]) ans+=mx[i];
		
	cout<<ct<<endl<<ans<<endl;
	
	return 0;
}

[翟翟 OI Round #1 1B] 军训

题目传送门

[翟翟 OI Round #1 1B] 军训

思路

感觉一个很板的 DP。

首先为了简便,抽象题面:有一个序列 {aN},每一位都可以取 [0,1],并且不能连续取 M1

fi,j 表示考虑到第 i 位,并且后 j 位都是 1 的方案数。

显然可以分类讨论:

  • 如果 j=0fi,j=k=0Mfi1,k

  • 如果 j0fi,j=fi1,j1

然后直接写即可,时间复杂度 O(NM)

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	int n,m; cin>>n>>m;
	int f[60][10]={0}; f[0][0]=1;
	
	for(int i=1;i<=n;i++){
		for(int j=0;j<m;j++)
			f[i][0]+=f[i-1][j];
			
		for(int j=1;j<m;j++)
			f[i][j]+=f[i-1][j-1];
	}
	
	int ans=0;
	
	for(int j=0;j<m;j++)
		ans+=f[n][j];
		
	cout<<ans<<endl;
	
	return 0;
}
posted @   SnapYust  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示