做题笔记(二)

[CSP-S 2023] 消消乐

题目传送门

[CSP-S 2023] 消消乐

思路

考虑 DP。

显然,设 \(f_i\) 表示以位置 \(i\) 结尾的可消除序列的个数,我们对每一个可消除序列考虑模型,大概就是这个样子:\(\text{a}\cdots\text{ab}\cdots\text{b}\)

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

\[f_i=f_{low_i-1}+1 \]

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

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

假设当前位置为 \(i\)\(low_{1\rightarrow i}\) 已经求出,那么我们注意到类似于 \(\text{a}\cdots\text{a}\) 的子串中的省略号一定是由若干个可消除字串。那么我们可以一个一个往前跳。

由于最多只有 \(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\) 号位置有 \(h_i\) 个方块。

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

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

\(1\le n\le10^5\)

\(1\le h_i\le10^9\)

思路

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

定义 \(f_i\) 为第 \(i\) 个位置的消除时间。

不难看出,\(f_i\) 只能由 \(f_{i-1}\)\(f_{i+1}\) 转移过来。我们可以正反跑两边。

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

那么:

\[i\in[1,n],f_i=1 \]

\[i\notin[1,n],f_i=\min\{f_{i-1}+1,h_i\} \]

反过来也一样。

之间复杂度 \(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。

很容易想到设 \(f_{i,j}\) 为字符串 \(A\) 的前 \(i\) 个和字符串 \(B\) 的前 \(j\) 个的最小距离。

转移方程还是比较板的:

\[f_{i,j}=\min\{f_{i-1,j-1}+|A_i-B_j|,f_{i-1,j}+k,f_{i,j-1}+k\} \]

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

显然有:\(f_{0,0}=0\)

然后就可以发现:

\[f_{i,0}=i\times k \]

\[f_{0,j}=j\times 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 转移的数组:\(f_{i,j}\) 表示考虑到第 \(i\) 个位置,且种了第 \(j\) 棵树。

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

显然有:

\[f_{i,1,0}=\max\{f_{i-1,2,1},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},f_{i-1,2,0}\}+c_i \]

然后加上环形 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

思路

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

  • 命题一:\(Y\mid X\)

证明:因为 \(\gcd(p,q)=Y\),所以 \(Y\mid p,Y\mid q\),所以 \(Y^2\mid pq\)。有因为 \(\text{lcm}(p,q)=X\),所以 \(X=\frac{pq}{\gcd(p,q)}\),所以 \(X=\frac{pq}{Y}\)。同乘一个 \(\frac{1}{Y}\),得到:\(\frac{X}{Y}=\frac{pq}{Y^2}\),由于 \(Y^2\mid pq\),所以 \(Y\mid X\)。证毕。

  • 命题二:不存在一个质数 \(g\),使得 \(g\nmid Y\) 并且满足 \(g\mid p,g\mid q\)

证明:假设 \(g\) 存在,那么 \(g\mid p,g\mid q\),那么 \(g^2\mid pq\),又因为 \(g\nmid Y\),并且 \(Y^2\mid pq\),那么 \(Y^2g^2\mid pq\),所以 \(\gcd(p,q)>Y\),与 \(\gcd(p,q)=Y\) 矛盾,所以这样的 \(g\) 不存在。证毕。

  • 命题三:令 \(k\)\(\frac{X}{Y}\) 的不同质因子个数,那么 \(p,q\) 数对的个数为 \(2^k\)

证明:将 \(\frac{pq}{Y^2}\) 分解质因数,也就是 \(\frac{pq}{Y^2}=\begin{aligned}\prod_{i=1}^n P_i^{e_i}\end{aligned}\),其中 \(P_i\) 为质数。根据命题二,我们可以得出一个引理:不存在一个质数 \(g\),使得 \(g\mid\frac{p}{Y},g\mid\frac{q}{Y}\)。同时,我们把 \(\frac{X}{Y}\) 分解质因数,那么 \(\frac{X}{Y}=\begin{aligned}\prod_{i=1}^k D_i^{w_i}\end{aligned}\),由于 \(\frac{pq}{Y^2}=\frac{X}{Y}\),所以 \(\begin{aligned}\prod_{i=1}^n P_i^{e_i}\end{aligned}=\begin{aligned}\prod_{i=1}^k D_i^{w_i}\end{aligned}\)。根据唯一分解定理和命题二的引理,\(P_i\) 一定和 \(D_i\) 一一对应并且每一个 \(P_i^{e_i}\) 一定只存在于 \(p\)\(q\) 的其中一个。所以对于 \(\frac{X}{Y}\) 的不同质因子的个数即 \(k\),每一个 \(D_i^{w_i}\) 一定是存在 \(p\) 中或者 \(q\) 中两种可能。根据乘法原理,可得答案即为 \(2^k\)。证毕。

根据命题三,我们只需要分解 \(\frac{X}{Y}\) 的质因数就可以得到答案了。

代码

#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 稍低。

显然面对这种亲戚关系,使用并查集是可以的。也就是说,在处理完这若干个亲戚关系之后,我们可以得到一堆亲戚集合 \(S_1,S_2,\cdots,S_k\),那么答案就是每一个集合中元素最大值的累加,也就是 \(\sum^kS_{i\max}\)

复杂度取决于并查集。

代码

#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。

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

\(f_{i,j}\) 表示考虑到第 \(i\) 位,并且后 \(j\) 位都是 \(1\) 的方案数。

显然可以分类讨论:

  • 如果 \(j=0\)\(f_{i,j}=\begin{aligned}\sum_{k=0}^Mf_{i-1,k}\end{aligned}\)

  • 如果 \(j\ne0\)\(f_{i,j}=f_{i-1,j-1}\)

然后直接写即可,时间复杂度 \(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 @ 2024-08-08 21:54  SnapYust  阅读(26)  评论(0)    收藏  举报