本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。
题目来源 | 完成时间 | 完成数量 | 正确率 |
---|---|---|---|
Atcoder Educational DP Contest |
2019/01/24 - 2019/2/2 | 24 24 24题(除V 题和Z 题以外) |
AC : 24 / 26 24/26 24/26 |
青蛙跳石头,每次只能从 i − 1 i-1 i−1或 i − 2 i-2 i−2个石头的地方跳过来,花费为前后两次跳的石头的高度差。求最小花费和。
简单DP。由于只能从前两个石头跳转过来,所以对于每个石头记 f ( i ) f(i) f(i)为到当前为止最优解,则有 f ( i ) = max { ∣ h i − h i − 1 ∣ + f ( i − 1 ) , ∣ h i − h i − 2 ∣ + f ( i − 2 ) } f(i)=\max\{|h_i-h_{i-1}|+f(i-1) , |h_i-h_{i-2}|+f(i-2)\} f(i)=max{∣hi−hi−1∣+f(i−1),∣hi−hi−2∣+f(i−2)},递推即可,复杂度 O ( n ) O(n) O(n)。
#include
#include
#include
const int MAXN=100010;
int h[MAXN],f[MAXN];
int main(){
int n;scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&h[i]);
f[0]=1<<30;
for(int i=2;i<=n;i++)
f[i]=std::min(std::abs(h[i]-h[i-1])+f[i-1],abs(h[i]-h[i-2])+f[i-2]);
printf("%d\n",f[n]);
}
青蛙跳石头,每次只能从第 i − j ( 1 ≤ j ≤ k ) i-j(1\le j\le k) i−j(1≤j≤k)个石头的地方跳过来,花费为前后两次跳的石头的高度差。求最小花费和。
简单DP。由于只能从前 k k k个石头跳转过来,所以对于每个石头记 f ( i ) f(i) f(i)为到当前为止最优解,则有 f ( i ) = max j = i − k i − 1 { ∣ h i − h j ∣ + f ( j ) } f(i)=\max_{j=i-k}^{i-1}\{|h_i-h_j|+f(j)\} f(i)=maxj=i−ki−1{∣hi−hj∣+f(j)},递推即可,复杂度 O ( n k ) O(nk) O(nk)。
#include
#include
#include
const int MAXN=100010;
int h[MAXN],f[MAXN];
int main(){
int n,k;scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%d",&h[i]);
memset(f,0x3f,sizeof(f));
f[1]=0;
for(int i=2;i<=n;i++){
for(int j=1;j<=std::min(i-1,k);j++)
f[i]=std::min(f[i],std::abs(h[i]-h[i-j])+f[i-j]);
}
printf("%d\n",f[n]);
}
有三种活动,每天做一项,每种活动不能连续两天做,共 n n n天。第 i i i天做三个活动的喜悦值为 a i , b i , c i a_i,b_i,c_i ai,bi,ci,求最大喜悦值。
简单DP。每天第 j j j项活动只能从前一天的另外两项转移过来,递推即可。复杂度 O ( n ) O(n) O(n)。
#include
#include
#include
using std::max;
const int MAXN=100010;
struct node{
int a,b,c;
node(int a=0,int b=0,int c=0){
this->a=a;
this->b=b;
this->c=c;
}
}p[MAXN];
int n,f[MAXN][3];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
int a,b,c;scanf("%d%d%d",&a,&b,&c);
p[i]=node(a,b,c);
}
for(int i=1;i<=n;i++){
f[i][0]=max(f[i-1][1],f[i-1][2])+p[i].a;
f[i][1]=max(f[i-1][0],f[i-1][2])+p[i].b;
f[i][2]=max(f[i-1][0],f[i-1][1])+p[i].c;
}
printf("%d\n",max(f[n][0],max(f[n][1],f[n][2])));
return 0;
}
01背包问题。略。
01背包,其中 n ≤ 100 , w i ≤ 1 0 9 , v i ≤ 1 0 3 n\le 100, w_i\le 10^9, v_i\le 10^3 n≤100,wi≤109,vi≤103。
这个是价值特别大的背包,传统的01背包写法在此不适用,因此我们考虑其它方式DP。设 f ( i , j ) f(i,j) f(i,j)表示前 i i i个物品价值为 j j j的最小背包容量。由于 ∑ v i ≤ 1 0 5 \sum v_i\le 10^5 ∑vi≤105,因此空间复杂度 O ( n ⋅ ∑ v i ) O(n\cdot \sum v_i) O(n⋅∑vi),即 O ( n 2 ⋅ max v i ) O(n^2\cdot\max v_i) O(n2⋅maxvi),可以接受。转移是 O ( 1 ) O(1) O(1)的: f ( i , j ) = max ( f ( i − 1 , j ) , f ( i − 1 , j − v i ) + w i ) f(i,j)=\max(f(i-1,j),f(i-1,j-v_i)+w_i) f(i,j)=max(f(i−1,j),f(i−1,j−vi)+wi)。答案只需要对于每一个可能的 ∑ v i \sum v_i ∑vi取最小背包容量满足要求的即可,详见代码。复杂度 O ( n ⋅ ∑ v i ) O(n\cdot \sum v_i) O(n⋅∑vi)。
#include
#include
#include
const int MAXN=100010;
long long f[110][MAXN];
int n,w[110],v[110];
int main(){
memset(f,0x7f,sizeof(f));
long long p;scanf("%d%lld",&n,&p);
for(int i=1;i<=n;i++)
scanf("%d%d",&w[i],&v[i]);
f[0][0]=0;
for(int i=1;i<=n;i++)
for(int j=0;j<=100000;j++){
if(j-v[i]<0)
f[i][j]=f[i-1][j];
else
f[i][j]=std::min(f[i-1][j],f[i-1][j-v[i]]+w[i]);
// printf("(%d %d)%lld\n",i,j,f[i][j]);
}
for(int i=100000;~i;i--)
if(f[n][i]<=p){
printf("%d\n",i);
return 0;
}
return 0;
}
最长公共子序列并输出答案。
标准的LCS解法,转移时记上一步来自哪里(路径),输出答案时dfs即可。
即对于第一列到 i i i第二列到 j j j的最优解 f ( i , j ) f(i,j) f(i,j),如果这两位相同,则直接转移为 f ( i − 1 , j − 1 ) + 1 f(i-1,j-1)+1 f(i−1,j−1)+1,否则比较上一位的较优值。复杂度 O ( n m ) O(nm) O(nm)。
#include
#include
const int MAXN=3010;
char a[MAXN],b[MAXN];
int f[MAXN][MAXN],g[MAXN][MAXN];
//(g=0:in LCS sequence),(g=1:from[i-1,j]),(g=-1:from[i,j-1])
void dfs(int i,int j){
if(i==0||j==0)
return;
if(g[i][j]==0){
dfs(i-1,j-1);
putchar(a[i]);
}
else if(g[i][j]==1)
dfs(i-1,j);
else
dfs(i,j-1);
}
int main(){
scanf("%s%s",a+1,b+1);
int n=strlen(a+1),m=strlen(b+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
if(a[i]==b[j]){
f[i][j]=f[i-1][j-1]+1;
g[i][j]=0;
}
else if(f[i-1][j]>=f[i][j-1]){
f[i][j]=f[i-1][j];
g[i][j]=1;
}
else{
f[i][j]=f[i][j-1];
g[i][j]=-1;
}
}
dfs(n,m);puts("");
return 0;
}
求有向无环图上最长链。
对于每个点找出从这点出发的最长链的长度,然后记忆化搜索即可,复杂度 O ( n ) O(n) O(n)。
#include
#include
#include
const int MAXN=100010;
struct edge{
int v;
edge *nxt;
edge(){
v=0;
nxt=NULL;
}
}*head[MAXN];
void adde(int u,int v){
edge *p=new edge;
p->v=v;
p->nxt=head[u];
head[u]=p;
}
int n,m,f[MAXN];
int dfs(int u){
if(f[u])
return f[u];
for(edge *i=head[u];i!=NULL;i=i->nxt){
int v=i->v;
f[u]=std::max(f[u],dfs(v)+1);
}
return f[u];
}
int main(){
scanf("%d%d",&n,&m);
while(m--){
int u,v;scanf("%d%d",&u,&v);
adde(u,v);
}
for(int i=1;i<=n;i++)
if(!f[i])
dfs(i);
int ans=0;
for(int i=1;i<=n;i++)
ans=std::max(ans,f[i]);
printf("%d\n",ans);
return 0;
}
网格图,有些格子是墙不能走,求左上角到右下角方案数。
简单DP。记 f ( i , j ) f(i,j) f(i,j)为到 ( i , j ) (i,j) (i,j)的方案数,则 f ( i , j ) = f ( i − 1 , j ) + f ( i , j − 1 ) f(i,j)=f(i-1,j)+f(i,j-1) f(i,j)=f(i−1,j)+f(i,j−1)(当该点不为墙时,否则答案是 0 0 0)。复杂度 O ( n m ) O(nm) O(nm)。
#include
#include
#include
const int MAXN=1010;
const int MOD=1e9+7;
bool p[MAXN][MAXN];
int n,m;
int f[MAXN][MAXN];
int main(){
scanf("%d%d",&n,&m);
f[1][1]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
char c;std::cin>>c;
p[i][j]=(c=='.');
if(i==1&&j==1)
continue;
if(p[i][j])
f[i][j]=(f[i-1][j]*p[i-1][j]+f[i][j-1]*p[i][j-1])%MOD;
}
printf("%d\n",f[n][m]);
return 0;
}
有 n n n个硬币,正面朝上概率为 p i p_i pi,反面 1 − p i 1-p_i 1−pi,求正面数量大于反面数量的概率。
记 f ( i , j , 0 / 1 ) f(i,j,0/1) f(i,j,0/1)表示前 i i i枚硬币,正面朝上的有 j j j枚,第 i i i枚是正面/反面,则 { f ( i , j , 0 ) = ( f ( i − 1 , j , 0 ) + f ( i − 1 , j , 1 ) ) × p i f ( i , j , 1 ) = ( f ( i − 1 , j − 1 , 0 ) + f ( i − 1 , j − 1 , 1 ) ) × ( 1 − p i ) \begin{cases}f(i,j,0)=\Big(f(i-1,j,0)+f(i-1,j,1)\Big)\times p_i\\f(i,j,1)=\Big(f(i-1,j-1,0)+f(i-1,j-1,1)\Big)\times (1-p_i)\end{cases} ⎩⎨⎧f(i,j,0)=(f(i−1,j,0)+f(i−1,j,1))×pif(i,j,1)=(f(i−1,j−1,0)+f(i−1,j−1,1))×(1−pi)
答案就是 ∑ i = 0 n / 2 f ( n , i , 0 ) + f ( n , i , 1 ) \displaystyle\sum_{i=0}^{n/2}f(n,i,0)+f(n,i,1) i=0∑n/2f(n,i,0)+f(n,i,1)。时间复杂度 O ( n 2 ) O(n^2) O(n2)。
#include
#include
const int MAXN=3010;
long double p[MAXN],f[MAXN][MAXN>>1][2];
int main(){
int n;scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%Lf",&p[i]);
f[1][0][0]=(1-p[1]);f[1][1][1]=p[1];
for(int i=2;i<=n;i++){
for(int j=0;j<=(n>>1);j++){
if(j)
f[i][j][1]=(f[i-1][j-1][0]+f[i-1][j-1][1])*p[i];
f[i][j][0]=(f[i-1][j][0]+f[i-1][j][1])*(1-p[i]);
}
}
long double ans=0;
for(int i=0;i<=(n>>1);i++)
ans+=f[n][i][0]+f[n][i][1];
printf("%.17Lf\n",1-ans);
return 0;
}
(未完待续)