Atcoder Educational DP Contest

知识共享许可协议
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。


一、 题目概况

题目来源 完成时间 完成数量 正确率
Atcoder Educational DP Contest 2019/01/24 - 2019/2/2 24 24 24题(除V题和Z题以外) AC 24 / 26 24/26 24/26

二、试题分析

A - Frog 1

题目大意

青蛙跳石头,每次只能从 i − 1 i-1 i1 i − 2 i-2 i2个石头的地方跳过来,花费为前后两次跳的石头的高度差。求最小花费和。

解题思路

简单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{hihi1+f(i1),hihi2+f(i2)},递推即可,复杂度 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]);
}

B - Frog 2

题目大意

青蛙跳石头,每次只能从第 i − j ( 1 ≤ j ≤ k ) i-j(1\le j\le k) ij(1jk)个石头的地方跳过来,花费为前后两次跳的石头的高度差。求最小花费和。

解题思路

简单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=iki1{hihj+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]);
}

C - Vacation

题目大意

有三种活动,每天做一项,每种活动不能连续两天做,共 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;
}

D - Knapsack 1

01背包问题。略。

E - Knapsack 2

题目大意

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 n100,wi109,vi103

解题思路

这个是价值特别大的背包,传统的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 vi105,因此空间复杂度 O ( n ⋅ ∑ v i ) O(n\cdot \sum v_i) O(nvi),即 O ( n 2 ⋅ max ⁡ v i ) O(n^2\cdot\max v_i) O(n2maxvi),可以接受。转移是 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(i1,j),f(i1,jvi)+wi)。答案只需要对于每一个可能的 ∑ v i \sum v_i vi取最小背包容量满足要求的即可,详见代码。复杂度 O ( n ⋅ ∑ v i ) O(n\cdot \sum v_i) O(nvi)

#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;
}

F - LCS

题目大意

最长公共子序列并输出答案。

解题思路

标准的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(i1,j1)+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;
}

G - Longest Path

题目大意

求有向无环图上最长链。

解题思路

对于每个点找出从这点出发的最长链的长度,然后记忆化搜索即可,复杂度 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;
}

H - Grid 1

题目大意

网格图,有些格子是墙不能走,求左上角到右下角方案数。

解题思路

简单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(i1,j)+f(i,j1)(当该点不为墙时,否则答案是 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;
}

I - Coins

题目大意

n n n个硬币,正面朝上概率为 p i p_i pi,反面 1 − p i 1-p_i 1pi,求正面数量大于反面数量的概率。

解题思路

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(i1,j,0)+f(i1,j,1))×pif(i,j,1)=(f(i1,j1,0)+f(i1,j1,1))×(1pi)
答案就是 ∑ 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=0n/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;
}

(未完待续)

J - Sushi

K - Stones

L - Deque

M - Candies

N - Slimes

O - Matching

P - Independent Set

Q - Flowers

R - Walk

S - Digit Sum

T - Permutation

U - Grouping

W - Intervals

X - Tower

Y - Grid 2

你可能感兴趣的:(题解,动态规划)