目录
一. Codeforces Round #462 (Div. 2)
比赛网址链接:http://codeforces.com/contest/934
A. A Compatible Pair (枚举)
题意
两个序列 a 和 b a和b a和b,去 a a a 中找一个数,使得其与 b b b 中某个数的乘积最大,将其删除,然后在 a a a 剩下的数中找出一个数,使得其与 b b b 中某个数的乘积最大
思路
范围不大,直接去 a a a 中枚举要删除的那个数,然后找到并标记,再在剩余的数中枚举每个数,找出最大值
代码
#include <bits/stdc++.h>
#define ft first
#define sd second
#define pb push_back
#define ms(x,y) memset(x,y,sizeof(x))
using namespace std;
typedef long long ll;
typedef pair<int,ll> P;
const int maxn=57;
const int N=1e6+7;
const ll inf=1e18+5;
int n,m;
ll a[maxn],b[maxn];
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)scanf("%lld",&a[i]);
for(int i=0;i<m;i++)scanf("%lld",&b[i]);
int id=0;ll ans=-inf;
for(int i=0;i<n;i++){
ll ad=a[i],sum=-inf;
for(int j=0;j<m;j++){
ll no=ad*b[j];
sum=max(sum,no);
}
if(sum>ans){
ans=sum;id=i;
}
}
ans=-inf;
for(int i=0;i<n;i++){
if(i==id)continue;
ll ad=a[i],sum=-inf;
for(int j=0;j<m;j++){
ll no=ad*b[j];
sum=max(sum,no);
}
if(sum>ans){
ans=sum;
}
}
cout<<ans<<'\n';
return 0;
}
B. A Prosperous Lot (贪心)
题意
每个数字都有一个循环个数,现在给定一个数字 k k k,表示循环个数,让你构造一个不超过 1 0 18 10^{18} 1018 的数刚好包含 k k k 个循环,不存在输出 − 1 -1 −1
思路
含循环个数最多的是数字 8 8 8,有一个是数字 0 , 4 , 6 , 9 0,4,6,9 0,4,6,9,由于数不能超过 1 0 18 10^{18} 1018,所以就算都用 8 8 8 去构造,也只能有 32 32 32 个循环,所以 k k k 大于 32 32 32 就输出 − 1 -1 −1,否则就先用 8 8 8 凑,然后用 4 、 6 、 9 4、6、9 4、6、9 凑,不用 0 0 0 的原因是防止前导零
代码
#include <bits/stdc++.h>
#define ft first
#define sd second
#define pb push_back
#define ms(x,y) memset(x,y,sizeof(x))
using namespace std;
typedef long long ll;
typedef pair<int,ll> P;
const int maxn=1e5+7;
const int N=1e6+7;
const int inf=0x3f3f3f3f;
int n,k;
int main()
{
cin>>k;
if(k>36)puts("-1");
else {
while(k>0){
if(k==1)printf("4");
else printf("8");
k-=2;
}
printf("\n");
}
return 0;
}
C. A Twisty Movement (枚举/dp)
题意
一个序列,只包含 1 1 1 和 2 2 2,现在选择一个区间 [ l , r ] [l,r] [l,r] 进行反转,使得序列的最长非递减子序列的长度最长
思路
我最开始的做法是枚举,因为序列里只有
1
1
1 和
2
2
2,所以如果任选一个区间
[
l
,
r
]
[l,r]
[l,r] 的话,设
m
m
m 为区间
[
l
,
r
]
[l,r]
[l,r] 中某点,即
l
<
=
m
<
r
l<=m<r
l<=m<r,将区间分为
[
l
,
m
]
[l,m]
[l,m] 和
[
m
+
1
,
r
]
[m+1,r]
[m+1,r],反转后若序列的最长非递减子序列的长度变长,肯定是
[
m
+
1
,
r
]
[m+1,r]
[m+1,r] 区间的
1
1
1 跑到左边,
[
l
,
m
]
[l,m]
[l,m] 区间的
2
2
2 跑到右边造成的,所以我们可以先预处理出原序列前缀
1
1
1 和
2
2
2 的个数,然后枚举
m
m
m 去其左边
[
1
,
m
]
[1,m]
[1,m] 寻找最优
l
l
l、去其右边
[
m
+
1
,
n
]
[m+1,n]
[m+1,n] 寻找最优
r
r
r 即可
d
p
dp
dp 做法:反转无非是使递减的变成递增的才使得序列的最长非递减子序列的长度变长,所以可以定义:
d
p
[
l
]
[
r
]
[
1
]
:
dp[l][r][1]:
dp[l][r][1]:区间
[
l
,
r
]
[l,r]
[l,r] 以1结尾的最长非递增子序列的长度
d
p
[
l
]
[
r
]
[
2
]
:
dp[l][r][2]:
dp[l][r][2]:区间
[
l
,
r
]
[l,r]
[l,r] 以2结尾的最长非递增子序列的长度
则不难得出:
d
p
[
l
]
[
r
]
[
1
]
=
m
a
x
(
d
p
[
l
]
[
r
−
1
]
[
1
]
,
d
p
[
l
]
[
r
]
[
2
]
)
+
(
a
[
r
]
=
=
1
)
dp[l][r][1]=max(dp[l][r-1][1],dp[l][r][2])+(a[r]==1)
dp[l][r][1]=max(dp[l][r−1][1],dp[l][r][2])+(a[r]==1)
d
p
[
l
]
[
r
]
[
2
]
=
d
p
[
l
]
[
r
]
[
2
]
+
(
a
[
r
]
=
=
2
)
dp[l][r][2]=dp[l][r][2]+(a[r]==2)
dp[l][r][2]=dp[l][r][2]+(a[r]==2)
然后根据得到的区间信息,结合前缀
[
1
,
l
−
1
]
[1,l-1]
[1,l−1] 的1的个数 和后缀
[
r
+
1
,
n
]
[r+1,n]
[r+1,n] 的2的个数,求的最优解
代码
枚举做法
#include <bits/stdc++.h>
#define ft first
#define sd second
#define pb push_back
#define ms(x,y) memset(x,y,sizeof(x))
using namespace std;
typedef long long ll;
typedef pair<int,ll> P;
const int maxn=2e3+7;
const int N=1e6+7;
const ll inf=1e18+5;
int n,m;
int a[maxn];
int b1[maxn],b2[maxn];
int main()
{
cin>>n;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
b1[i]=b1[i-1];
b2[i]=b2[i-1];
if(a[i]==1)b1[i]++;
else b2[i]++;
}
int ma=-1;
for(int mid=1;mid<=n+1;mid++){
int l1=0,r2=0;
for(int l=1;l<=mid;l++){
l1=max(l1,b1[l-1]+b2[mid-1]-b2[l-1]);
}
for(int r=mid;r<=n+1;r++){
r2=max(r2,b2[n]-b2[r-1]+b1[r-1]-b1[mid-1]);
}
ma=max(ma,l1+r2);
}
cout<<ma<<'\n';
return 0;
}
d p dp dp 做法
#include <bits/stdc++.h>
#define ft first
#define sd second
#define pb push_back
#define ms(x,y) memset(x,y,sizeof(x))
using namespace std;
typedef long long ll;
typedef pair<int,ll> P;
const int maxn=2e3+7;
const int N=1e6+7;
const ll inf=1e18+5;
int n,m;
int a[maxn];
int b1[maxn],b2[maxn];
int dp[maxn][maxn][3];
int main()
{
cin>>n;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
b1[i]=b1[i-1];
b2[i]=b2[i-1];
if(a[i]==1)b1[i]++;
else b2[i]++;
}
int ma=-1;
for(int l=1;l<=n;l++){
for(int r=l;r<=n;r++){
dp[l][r][1]=max(dp[l][r-1][2],dp[l][r-1][1])+(a[r]==1);
dp[l][r][2]=dp[l][r-1][2]+(a[r]==2);
ma=max(ma,b1[l-1]+dp[l][r][1]+b2[n]-b2[r]);
ma=max(ma,b1[l-1]+dp[l][r][2]+b2[n]-b2[r]);
}
}
cout<<ma<<'\n';
return 0;
}
D. A Determined Cleanup (数学)
题意
给定两个正整数 p p p, k k k,我们需要找出一个多项式 q ( x ) q(x) q(x),使得 f ( x ) = q ( x ) ⋅ ( x + k ) + p f(x) = q(x)⋅(x + k) + p f(x) = q(x)⋅(x + k) + p,且要求 f ( x ) f(x) f(x) 的各项系数严格小于 k k k 且非负,输出任意一种满足要求的 f ( x ) f(x) f(x) 的各项系数。
思路
不妨设 p ( x ) = a 0 + a 1 ⋅ x + a 2 ⋅ x 2 + . . . + a n − 1 ⋅ x n − 1 p(x)=a_0+a_1⋅x+a_2⋅x^2+...+a_{n-1}⋅x^{n-1} p(x)=a0+a1⋅x+a2⋅x2+...+an−1⋅xn−1
则: f ( x ) = ( a 0 ⋅ k + p ) + ( a 1 ⋅ k + a 0 ) x 1 + ( a 2 ⋅ k + a 1 ) x 2 + . . . + ( a n − 1 ∗ k + a n − 2 ) x n − 1 + ( 0 ∗ k + a n − 1 ) x n f(x)=(a_0⋅k+p)+(a_1⋅k+a_0)x^1+(a_2⋅k+a_1)x^2+...+(a_{n-1}*k+a_{n-2})x^{n-1}+(0*k+a_{n-1})x^n f(x)=(a0⋅k+p)+(a1⋅k+a0)x1+(a2⋅k+a1)x2+...+(an−1∗k+an−2)xn−1+(0∗k+an−1)xn
由于要保证每一项的系数都非负且小于 k k k,那实际上就是将 p p p 转化为 − k -k −k 进制了,所以从常数项开始,在此过程中, p p p 即是 a i a_i ai, p % k p\%k p%k 即是 f ( x ) f(x) f(x) 的系数项。要注意保证系数项非负。
代码
#include <bits/stdc++.h>
#define ft first
#define sd second
#define pb push_back
#define ms(x,y) memset(x,y,sizeof(x))
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int maxn=1e5+7;
const int N=1e3+7;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
ll p,k;
int d,x;
int ans[maxn];
int main()
{
cin>>p>>k;
while(p){
x=p%k;
p/=-k;
if(x<0){
x+=k;p++;
}
ans[d++]=x;
}
cout<<d<<'\n';
for(int i=0;i<d;i++)printf("%d ",ans[i]);
return 0;
}
二. dp练习
A. 编辑距离
题目链接
https://www.51nod.com/Challenge/Problem.html#problemId=1183
题意
给两个字符串 a 和 b a和b a和b 求两者编辑距离
思路
动态规划经典题目,设字符串
a
a
a 的长度为
∣
a
∣
|a|
∣a∣,字符串
b
b
b 的长度为
∣
b
∣
|b|
∣b∣,定义
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] :
a
a
a 串从第1个字符开始到第
i
i
i 个字符和
b
b
b 串从第1个字符开始到第
j
j
j 个字符,这两个子串的编辑距离
则不难得出以下递推式:
d
p
[
i
]
[
j
]
=
i
(
j
=
0
)
dp[i][j]=i(j=0)
dp[i][j]=i(j=0)
d
p
[
i
]
[
j
]
=
j
(
i
=
0
)
dp[i][j]=j(i=0)
dp[i][j]=j(i=0)
d
p
[
i
]
[
j
]
=
m
i
n
(
d
p
[
i
−
1
]
[
j
−
1
]
,
d
p
[
i
]
[
j
−
1
]
+
1
,
d
p
[
i
−
1
]
[
j
]
+
1
)
(
a
i
=
b
j
)
dp[i][j]=min(dp[i-1][j-1],dp[i][j-1]+1,dp[i-1][j]+1)(a_i=b_j)
dp[i][j]=min(dp[i−1][j−1],dp[i][j−1]+1,dp[i−1][j]+1)(ai=bj)
d
p
[
i
]
[
j
]
=
m
i
n
(
d
p
[
i
−
1
]
[
j
−
1
]
,
d
p
[
i
]
[
j
−
1
]
,
d
p
[
i
−
1
]
[
j
]
)
+
1
(
a
i
!
=
b
j
)
dp[i][j]=min(dp[i-1][j-1],dp[i][j-1],dp[i-1][j])+1(a_i!=b_j)
dp[i][j]=min(dp[i−1][j−1],dp[i][j−1],dp[i−1][j])+1(ai!=bj)
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ft first
#define sd second
#define pb push_back
#define ms(x,y) memset(x,y,sizeof(x))
using namespace std;
typedef long long ll;
typedef pair<int,ll> P;
const int maxn=1e3+7;
const int N=1e6+7;
const int inf=0x3f3f3f3f;
int n,m;
char s[maxn],t[maxn];
int dp[maxn][maxn];
int main()
{
cin>>(s+1)>>(t+1);
n=strlen(s+1);
m=strlen(t+1);
ms(dp,inf);
for(int i=0;i<=n;i++)dp[i][0]=i;
for(int i=0;i<=m;i++)dp[0][i]=i;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(s[i]==t[j])dp[i][j]=min(dp[i-1][j-1],min(dp[i][j-1],dp[i-1][j])+1);
else dp[i][j]=min(dp[i-1][j-1],min(dp[i-1][j],dp[i][j-1]))+1;
}
}
cout<<dp[n][m]<<'\n';
return 0;
}
B. 最长公共子序列Lcs
题目链接
https://www.51nod.com/Challenge/Problem.html#problemId=1006
题意
求两个串 s s s 和 t t t 的最长公共子序列,并输出任意一个
思路
经典
d
p
dp
dp 题,定义
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]:
s
s
s 串从第1个字符开始到第
i
i
i 个字符和
t
t
t 串从第1个字符开始到第
j
j
j 个字符,这两个子串的LIS长度
不难得出递推式:
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
−
1
]
+
1
(
s
[
i
]
=
t
[
j
]
)
dp[i][j]=dp[i-1][j-1]+1(s[i]=t[j])
dp[i][j]=dp[i−1][j−1]+1(s[i]=t[j])
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
]
[
j
−
1
]
,
d
p
[
i
−
1
]
[
j
]
)
(
s
[
i
]
!
=
t
[
j
]
)
dp[i][j]=max(dp[i][j-1],dp[i-1][j])(s[i]!=t[j])
dp[i][j]=max(dp[i][j−1],dp[i−1][j])(s[i]!=t[j])
由于要输出任意一个LIS,可以用一个数组在
d
p
dp
dp 过程中记录下路径,然后从后往前 dfs 一下,回溯时记录下即可
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#define ft first
#define sd second
#define pb push_back
#define ms(x,y) memset(x,y,sizeof(x))
using namespace std;
typedef long long ll;
typedef pair<int,ll> P;
const int maxn=1e3+7;
const int N=1e6+7;
const int inf=0x3f3f3f3f;
int n,m;
string ans;
char s[maxn],t[maxn];
int dp[maxn][maxn];
int mp[maxn][maxn];
void dfs(int i,int j,int no){
if(!i||!j)return;
if(no==1){
dfs(i-1,j-1,mp[i-1][j-1]);ans+=s[i];
}
else if(no==2){
dfs(i-1,j,mp[i-1][j]);
}
else {
dfs(i,j-1,mp[i][j-1]);
}
}
int main()
{
cin>>(s+1)>>(t+1);
n=strlen(s+1);
m=strlen(t+1);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(s[i]==t[j]){
dp[i][j]=dp[i-1][j-1]+1;
mp[i][j]=1;
}
else {
if(dp[i-1][j]>dp[i][j-1]){
mp[i][j]=2;
}
else {
mp[i][j]=3;
}
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
}
dfs(n,m,mp[n][m]);
cout<<ans<<'\n';
return 0;
}
C. Find Black Hand
题目链接
http://acm.hdu.edu.cn/showproblem.php?pid=4271
题意
一个母串 s s s,还有几个模式串 t t t,母串是一个循环串(环),现在定义 f i f_i fi 为母串通过一系列操作(添加、修改、删除字符)使得子串 t i t_i ti 成为母串的子串的代价(也是单向的编辑距离),让你找出最小的 f i f_i fi 并输出字典序最小的对应的模式串
思路
考虑两种情况,第一种子串比母串长,一种是母串比子串长。由于母串是个环,所以我们一般将母串复制一遍接在后面来进行 d p dp dp,本题模式串长度最多10,所以只需要母串在后面接长度为10的原串前缀即可,但是当子串比母串长时,这样做可能会使母串中一个位置的字母被复用,所以针对第一种情况,就将母串赋值一份,然后枚举起始点,找长度为母串长度的子字符串进行 d p dp dp;对于第二种情况,不会存在复用的情况,就直接 d p dp dp,对于单向的编辑距离,只需要在初始化的时候做些改动即可,因为是只有母串做改变。
代码
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <algorithm>
#define ft first
#define sd second
#define pb push_back
#define ms(x,y) memset(x,y,sizeof(x))
using namespace std;
typedef long long ll;
typedef pair<int,ll> P;
const int maxn=2e5+7;
const int inf=0x3f3f3f3f;
int n,m,len,f;
string a,b,s,t,ans;
int dp[maxn][15];
void DP(string x,string y,int len1,int len2){
for(int i=0;i<=len1;i++){
for(int j=0;j<=len2;j++)dp[i][j]=0;
}
for(int j=0;j<=len2;j++)dp[0][j]=j;
for(int i=0;i<len1;i++){
for(int j=0;j<len2;j++){
if(x[i]==y[j])dp[i+1][j+1]=min(dp[i][j],min(dp[i][j+1],dp[i+1][j])+1);
else dp[i+1][j+1]=min(dp[i][j],min(dp[i][j+1],dp[i+1][j]))+1;
}
}
int ret=inf;
for(int i=0;i<=len1;i++)ret=min(ret,dp[i][len2]);
if(ret<f){
f=ret;ans=y;
}
else if(ret==f){
if(ans>y)ans=y;
}
}
int main()
{
while(cin>>a){
n=a.length();
scanf("%d",&len);
s=a+a.substr(0,10);
t=a+a;f=inf;
for(int k=1;k<=len;k++){
cin>>b;
m=b.length();
if(m>=n){
for(int be=0;be<n;be++){
string mu=t.substr(be,n);
DP(mu,b,n,m);
}
}
else {
DP(s,b,s.length(),m);
}
}
cout<<ans<<' '<<f<<'\n';
}
return 0;
}
D. Two
题目链接
http://acm.hdu.edu.cn/showproblem.php?pid=5791
题意
两个序列 a , b a,b a,b,求两者相同子序列的个数
思路
很显然要用动态规划解决,首先定义状态:
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]:序列
a
a
a 的前
i
i
i 个数与序列
b
b
b 的前
j
j
j 个数的相同子序列的个数
递推式:
d
p
[
i
]
[
j
]
=
d
p
[
i
]
[
j
−
1
]
+
d
p
[
i
−
1
]
[
j
]
+
1
(
a
i
=
b
j
)
dp[i][j]=dp[i][j-1]+dp[i-1][j]+1(a_i=b_j)
dp[i][j]=dp[i][j−1]+dp[i−1][j]+1(ai=bj)
d
p
[
i
]
[
j
]
=
d
p
[
i
]
[
j
−
1
]
+
d
p
[
i
−
1
]
[
j
]
−
d
p
[
i
−
1
]
[
j
−
1
]
(
a
i
!
=
b
j
)
dp[i][j]=dp[i][j-1]+dp[i-1][j]-dp[i-1][j-1](a_i!=b_j)
dp[i][j]=dp[i][j−1]+dp[i−1][j]−dp[i−1][j−1](ai!=bj)
当
a
i
a_i
ai 与
b
j
b_j
bj 相同时,显然是 没有加上
a
i
a_i
ai 时相同子序列的个数(
d
p
[
i
−
1
]
[
j
]
dp[i-1][j]
dp[i−1][j]) 和 没有加上
b
j
b_j
bj 时相同子序列的个数(
d
p
[
i
]
[
j
−
1
]
dp[i][j-1]
dp[i][j−1]) 再加上
a
i
,
b
j
a_i,b_j
ai,bj 这一组相同的子序列,没有减
d
p
[
i
−
1
]
[
j
−
1
]
dp[i-1][j-1]
dp[i−1][j−1] 是因为
d
p
[
i
−
1
]
[
j
−
1
]
dp[i-1][j-1]
dp[i−1][j−1] 可以与
a
i
a_i
ai 配对相同,也可以与
b
j
b_j
bj 配对相同,于是计算了两次
而
a
i
a_i
ai 与
b
j
b_j
bj 不相同时,就需要减去一次
d
p
[
i
−
1
]
[
j
−
1
]
dp[i-1][j-1]
dp[i−1][j−1] ,因为
d
p
[
i
]
[
j
−
1
]
dp[i][j-1]
dp[i][j−1] 中包含了一次,
d
p
[
i
−
1
]
[
j
]
dp[i-1][j]
dp[i−1][j] 中也包含了一次
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#define ft first
#define sd second
#define pb push_back
#define ms(x,y) memset(x,y,sizeof(x))
using namespace std;
typedef long long ll;
typedef pair<int,ll> P;
const int maxn=1e3+7;
const int N=1e6+7;
const int mod=1e9+7;
int n,m;
int a[maxn],b[maxn];
int dp[maxn][maxn];
int main()
{
while(cin>>n>>m){
ms(dp,0);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int j=1;j<=m;j++)scanf("%d",&b[j]);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i]==b[j])dp[i][j]=(dp[i][j-1]+dp[i-1][j]+1)%mod;
else dp[i][j]=(dp[i][j-1]+dp[i-1][j]-dp[i-1][j-1])%mod;
}
}
//保证结果非负
cout<<(dp[n][m]+mod)%mod<<'\n';
}
return 0;
}