做题笔记(二)
[CSP-S 2023] 消消乐
题目传送门
思路
考虑 DP。
显然,设 \(f_i\) 表示以位置 \(i\) 结尾的可消除序列的个数,我们对每一个可消除序列考虑模型,大概就是这个样子:\(\text{a}\cdots\text{ab}\cdots\text{b}\)。
那么我们当前位置为 \(i\),前一个可以与 \(i\) 匹配的最大的下标为 \(low_i\),显然:
到这里其实都很简单的,去年的我考场上也是想到这里就戛然而止,最后写了一个 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}\) 转移过来。我们可以正反跑两边。
注意到,每一个位置只能是从旁边消除或者从上面一个一个消除下来。
那么:
反过来也一样。
之间复杂度 \(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_{0,0}=0\)。
然后就可以发现:
加上初始化后就可以轻松 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\) 的话就满足不了题目要求,所以加上一维表示当前这个位置是比旁边低还是比旁边高。
显然有:
然后加上环形 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
题目传送门
思路
一个很思维的思维题。发现洛谷上的题解证明部分略少,故此处给出较严谨的证明。
- 命题一:\(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] 征兵
题目传送门
思路
一个需要小小思考的绿题,难度可能比 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] 军训
题目传送门
思路
感觉一个很板的 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;
}

浙公网安备 33010602011771号