把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

AtCoder Educational DP Contest

(还在更新,别急。更新进度(20/26);做题进度(24/26);口胡进度(26/26))

本来想等写完来写总结的..........

还是没写完啊。

前面的题大多比较水....后面倒是很多难题没写。不过通过这场DP Edu,也让我发现我连背包都写不清楚....

以前对动态规划这部分有点逃避()现在要好好练习了啊。

感谢 chy 在高一讲的dp基础技巧。


A

基础递推题啦QAQ

Code - A
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN (int)(1e5+233)
#define MAXH (int)(1e4+233)
int f[MAXN],h[MAXN];
 
int main()
{
	int n;
	scanf("%d",&n);
	for (int i=1;i<=n;i++) scanf("%d",&h[i]);
	f[2]=abs(h[2]-h[1]);
	for (int i=3;i<=n;i++)
		f[i]=min(abs(h[i]-h[i-1])+f[i-1],abs(h[i]-h[i-2])+f[i-2]);
//	for (int i=1;i<n;i++) printf("%d ",f[i]);
	printf("%d\n",f[n]);
	return 0;
}

B

还是基础递推...

Code - B
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN (int)(1e5+233)
#define MAXH (int)(1e4+233)
int f[MAXN],h[MAXN];
 
int main()
{
	int n,k;
	scanf("%d%d",&n,&k);
	for (int i=1;i<=n;i++) scanf("%d",&h[i]);
	f[2]=abs(h[2]-h[1]);
	for (int i=3;i<=n;i++)
	{
		f[i]=abs(h[i]-h[i-1])+f[i-1];
		for (int j=i-2;j>=i-k;j--)
			if (j>0) f[i]=min(f[i],abs(h[i]-h[j])+f[j]);
	}
		
//	for (int i=1;i<n;i++) printf("%d ",f[i]);
	printf("%d\n",f[n]);
	return 0;
}

C ~ H(好像太水了不讲了。)


I - Coins(概率)

有 $ N $ 枚硬币,第 $ i $ 枚硬币抛出后正面朝上的概率为 $ p $ ,反面朝上的概率为 $ 1-p $

扔完所有硬币,求正面朝上的银币数比反面朝上的银币数多的概率。

设 $ f[i][j] $ 表示抛 $ i $ 枚硬币,有 $ j $ 枚朝上的概率。

那么就有

\[f[i][j]=f[i-1][j]*(i-p[i])+f[i-1][j-1]*p[i] \]

然后对于每个 $ f[n][i](i>=(n+1)/2) $ 统计就好了对吧(

Code - I - Coins
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define MAXN 3021
using namespace std;
int n;
double p[MAXN],f[MAXN][MAXN];
double jt[MAXN];

int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++) scanf("%lf",&p[i]);
	f[1][0]=1.000-p[1]; f[1][1]=p[1];
	for (int i=2;i<=n;i++)
		for (int j=0;j<=i;j++)
			if (j>0)
				f[i][j]=f[i-1][j]*(1.00-p[i])+f[i-1][j-1]*p[i];
			else f[i][j]=f[i-1][j]*(1.00-p[i]);
	double ans=0;
	for (int j=(n+1)/2;j<=n;j++) ans+=f[n][j];//,printf("%d %d : %lf\n",n,j,f[n][j]);
	printf("%.10lf\n",ans);
	return 0;
}

J - Sushi(期望)

设 $ f[a][b][c][d] $ 为剩余 $ a/b/c/d $ 盘 $ 0/1/2/3 $ 的期望次数

那么有

\[f[a][b][c][d]= 1 + \frac{a}{n} f[a][b][c][d] + \frac{b}{n} f[a+1][b-1][c][d] + \frac{c}{n} f[a][b+1][c-1][d] + \frac{d}{n} f[a][b][c+1][d-1] \]

\[\frac{n-a}{n} f[a][b][c][d]= 1 + \frac{b}{n} f[a+1][b-1][c][d] + \frac{c}{n} f[a][b+1][c-1][d] + \frac{d}{n} f[a][b][c+1][d-1] \]

\[f[a][b][c][d]= \frac{n}{n-a} + \frac{b}{n-a} f[a+1][b-1][c][d] + \frac{c}{n-a} f[a][b+1][c-1][d] + \frac{d}{n-a} f[a][b][c+1][d-1] \]

发现 $ a=n-b-c-d $

替换一下状态w

\[f[b][c][d]= \frac{n}{b+c+d} + \frac{b}{b+c+d} f[b-1][c][d] + \frac{c}{b+c+d} f[b+1][c-1][d] + \frac{d}{b+c+d} f[b][c+1][d-1] \]

发现d是递增的,拿它当最外层阶段,c当次外层

最后答案是 $ f[sum1][sum2][sum3] $

Code - J - Sushi
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define MAXN 321
using namespace std;
int n;
double f[MAXN][MAXN][MAXN];

int main()
{
	scanf("%d",&n);
	int sum1=0,sum2=0,sum3=0;
	for (int i=1,a;i<=n;i++)
	{
		scanf("%d",&a);
		if (a==1) sum1++;
		else if (a==2) sum2++;
		else sum3++;
	}
	for (int d=0;d<=sum3;d++)
		for (int c=0;c<=sum2+sum3;c++)
			for (int b=0;b<=n;b++)
			{
				if (!(b||c||d)) continue;
				f[b][c][d]=1.00*n/(1.00*b+1.00*c+1.00*d);
				if (b>=1) f[b][c][d]+=b*1.0/(b*1.0+c*1.0+d)*(double)f[b-1][c][d];
				if (c>=1&&b+1<=n) f[b][c][d]+=c*1.0/(b*1.0+c*1.0+d)*(double)f[b+1][c-1][d];
				if (d>=1&&c+1<=sum2+sum3) f[b][c][d]+=d*1.0/(b*1.0+c*1.0+d)*(double)f[b][c+1][d-1];
			}
	/*
	for (int i=0;i<=sum1;i++)
		for (int j=0;j<=sum2;j++)
			for (int k=0;k<=sum3;k++)
				printf("%d %d %d : %lf\n",i,j,k,f[i][j][k]);
	*/
	printf("%.10lf\n",f[sum1][sum2][sum3]);
	return 0;
}

K - Stones(博弈论)

有 $ K $ 个石子,双方轮流取石子,每一次取的石子数必须是集合 $ A $ 中的一个数,双方都以最优策略行动,判断先手必胜还是后手必胜。

\[1 \leq N \leq 100 \]

\[1\ \leq\ K\ \leq\ 10^5 \]

设 $ f[i] $ 表示当前先手取时必输/必胜(0/1)

那么有

\[f[0 \leq i \leq min_a]=0 \]

\[f[i]|=(f[i-a[i]] xor 1) \]

Code - K - Stones
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN 107
#define MAXK (int)(1e5+233)
int f[MAXK],a[MAXN];
int n,k;
 
int main()
{
	scanf("%d%d",&n,&k);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	for (int i=1;i<=k;i++)
		for (int j=1;j<=n;j++)
			if (i>=a[j])
				f[i]|=(f[i-a[j]]^1);
	if (f[k]) printf("First\n");
	else printf("Second\n");
	return 0;
}

L - Deque(区间DP)

给一个双端队列,双方轮流取数,每一次能且只能从队头或队尾取数,取完数后将这个数从队列中弹出。双方都希望自己取的所有数之和尽量大,且双方都以最优策略行动,假设先手取的所有数之和为 $ X $ ,后手取的所有数之和为 $ Y $ ,求 $ X−Y $ 。

.......其实就是 先手和后手都尽量使自己取得最多

设 $ f[i][j] $ 表示区间 $ [i,j] $ 的答案(先手最多取多少)...

那么就有

\[f[i][j]=sum[j]-sum[i-1]-min(f[i+1][j],f[i][j-1]) \]

Code - L - Deque
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define MAXN 3021
using namespace std;
int a[MAXN];
int n;
long long f[MAXN][MAXN],c[MAXN];


int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]),c[i]=c[i-1]+a[i];
	for (int i=1;i<=n;i++) f[i][i]=a[i];
	for (int len=2;len<=n;len++)
		for (int i=1,j;i<=n-len+1;i++)
		{
			j=i+len-1;
			f[i][j]=c[j]-c[i-1]-min(f[i+1][j],f[i][j-1]);
		}
	printf("%lld\n",(f[1][n]<<1)-c[n]);
	return 0;
}

M - Candies(计数,前缀和优化)

$ K $ 颗糖分给 $ n $ 个人,第 $ i $ 个人至少分得 $ 0 $ 颗,至多分得 $ a_i $ 颗,必须分完,求方案数,答案对 $ 10^9+7 $ 取模。

\[1 \leq N \leq 100 \]

\[0\ \leq\ K\ \leq\ 10^5 \]

\[0\ \leq\ a_i\ \leq\ K \]

设 $ f[i][j] $ 表示给前 $ i $ 个人分 $ j $ 个糖果的方案数

那么有

\[f[i][j]=f[i-1][j-k] \ (k = 0 to a[i] , j /geq k) \]

这个是O(nk^2)的()优化的话...可以加个前缀和

设 $ c[j] $ 表示 $ \sum_{k=1}{j}f[i-1][k] $ ..

那么就有

\[f[i][j]=c[j]-c[max(0,j-a[i]-1)] \]

\[c[j]=c[j-1]+f[i][j] \]

Code - M - Candies
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN 107
#define MAXK (int)(1e5+233)
#define mod (int)(1e9+7)
int n,k;
long long f[MAXN][MAXK];
long long c[MAXK];
int a[MAXN];

int main()
{
	scanf("%d%d",&n,&k);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	f[0][0]=1;
	c[0]=f[0][0]; for (int j=1;j<=k;j++) c[j]=c[j-1]+f[0][j];
	for (int i=1;i<=n;i++)
	{
		for (int j=0;j<=k;j++)
			f[i][j]+=((c[j]-((j<=a[i])?0:c[j-a[i]-1]))+mod)%mod,f[i][j]%=mod;
		c[0]=f[i][0]; for (int j=1;j<=k;j++) c[j]=(c[j-1]+f[i][j])%mod;
	}
	/*
	for (int i=0;i<=n;i++)
		for (int j=0;j<=k;j++)
			printf("%d %d : %lld\n",i,j,f[i][j]);
	*/	
	printf("%lld\n",f[n][k]);
	return 0;
}

N - Slimes(区间DP)

有 $ n $ 个数,第 $ i $ 个数是 $ a_i $ ,现在要进行 $ n-1 $ 次操作。

对于每一次操作,可以把相邻两个数合并成他们的和,这次操作的代价就是这个和。

求代价最小值。

$ n \ 400 $ ,纯区间dp(

\[f[i][j]=max(f[i][k]+f[k][j]+c[j]-c[i-1]) \]

Code - N - Slimes
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN 407
#define MAXA (int)(1e9+233)

int a[MAXN];
long long f[MAXN][MAXN],c[MAXN];
inline long long minl(long long x,long long y) { return x<y?x:y; }

int main()
{
	int n;
	scanf("%d",&n);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]),c[i]=c[i-1]+a[i];
	for (int len=2;len<=n;len++)
		for (int i=1,j;i<=n-len+1;i++)
		{
			j=i+len-1;
			f[i][j]=f[i][i]+f[i+1][j]+c[j]-c[i-1];
			for (int k=i+1;k<j;k++)
				f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+c[j]-c[i-1]);
		}
	printf("%lld\n",f[1][n]);
	return 0;
}

O - Matching(状压DP)

给定二分图,两个集合都有 $ N $ 个点, $ a_{i,j}=1 $ 表示第一个集合第 $ i $ 个点与第二个集合第 $ j $ 个点连边。

状压 $ DP $

设 $ f[i][S] $ 表示前i个白点和S的配对方案数

\[f[i][S]= \sum{(S>>j) \ and \ 1=1 \ and \ e[i][j]=1} f[i-1][S-(1<<j)] \ (bitsum1(S)=i) \]

答案是 $ f[n][(1<<n)-1] $

Code - O - Matching
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN (22)
#define mod (int)(1e9+7)
int e[MAXN][MAXN];
int n;
long long f[MAXN][1<<MAXN];

inline int count_b(int x) { int sum=0; while (x) { sum+=(x&1); x>>=1; } return sum; }

int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
		for (int j=1;j<=n;j++)
			scanf("%d",&e[i][j]);
	f[0][0]=1;
	for (int S=1,i;S<(1<<n);S++)
	{
		i=count_b(S);
		for (int j=1;j<=n;j++)
		{
			if (e[i][j]&&((S>>(j-1))&1))
				f[i][S]=(f[i][S]+f[i-1][S-(1<<(j-1))])%mod;
		}
	}
	printf("%lld\n",f[n][(1<<n)-1]);
	return 0;
} 

P - Independent Set(树形DP)

树上独立集计数。

直接设 \(f_{x,0/1}\) 表示以 \(x\) 为根的子树,节点 \(x\) 染成白/黑的方案数。

\[f_{x,0}=\prod\limits_{y\in son_x} (f_{y,0}+f_{y,1})\\ f_{x,1}=\prod\limits_{y\in son_x} f_{y,0} \]

Code - P - Independent Set
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN (int)(1e5+233)
#define mod (int)(1e9+7)

int n;

struct qwq
{
	int nex,to;
}e[MAXN<<1];
int h[MAXN],tot=0;
inline void add(int x,int y)
{
	e[++tot].to=y;
	e[tot].nex=h[x];
	h[x]=tot;
}

long long f[MAXN][2];
int fa[MAXN];
void dfs(int x)
{
	f[x][0]=f[x][1]=1;
	for (int i=h[x],y;i;i=e[i].nex)
	{
		y=e[i].to;
		if (y==fa[x]) continue;
		fa[y]=x;
		dfs(y);
		f[x][0]=f[x][0]*f[y][1]%mod;
		f[x][1]=f[x][1]*(f[y][0]+f[y][1])%mod;
	}
}

int main()
{
	scanf("%d",&n);
	for (int i=1,x,y;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	dfs(1);
	printf("%lld\n",(f[1][0]+f[1][1])%mod);
	return 0;
}

Q - Flowers(数据结构维护DP)

带权严格最长上升子序列

随便拿个数据结构维护最大 DP 值就可以了。

Code - Q - Flowers
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define MAXN (int)(2e5+233)
using namespace std;
int n;
int a[MAXN],h[MAXN];
#define lowbit(aa) (aa&-aa)
long long c[MAXN],f[MAXN];
inline long long maxl(long long A,long long B) { return A>B?A:B; }
inline void add(int x,long long k)
{
	while (x<=n)
	{
		c[x]=maxl(c[x],k);
		x+=lowbit(x);
	}
}
inline long long query(int x)
{
	long long ans=0;
	while (x>0)
	{
		ans=maxl(ans,c[x]);
		x-=lowbit(x);
	}
	return ans;
}

int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++) scanf("%d",&h[i]);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	long long answer=0;
	for (int i=1;i<=n;i++)
	{
		f[i]=query(h[i])+a[i];
		add(h[i],f[i]);
		answer=max(answer,f[i]);
	}
	printf("%lld\n",answer);
	return 0;
}


R - Walk(矩阵快速幂)

矩阵加速 floyd 大板子。根据 \(f_{P,i,j}=\sum\limits_{k}f_{P-1,i,k}\times f_{1,k,j}\)

\(f_{1,i,j}\) 即原本的邻接矩阵。


S - Digit Sum(数位DP)

数位 DP。头一次写()

\(f_{i,p,0/1}\) 表示到了第 \(i\) 位,\(\text{mod} \ d\) 意义下为 \(p\),上一位未满/满的方案数。

\[f_{i,p,0}=\sum\limits_{j=0}^9f_{i-1,(p-j)\%d,0} \]

\[f_{i,p,1}=f_{i-1,(p-a(i))\%d,1}+\sum\limits_{j=0}^{a(i)-1}f_{i-1,(p-j)\%d,0} \]

Code - S - Digit Sum
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN (int)(1e4+233)
#define MAXD (107)
const long long mod=(long long)(1e9+7);
string s;
#define a(C) ((int)(s[n-C]-'0'))

long long f[MAXN][MAXD][2];
int d,n;
long long F(int i,int p,int tp)
{
	if (p<0) p=(p+d)%d;
	if (p>d) p%=d; 
	if (f[i][p][tp]!=-1) return /*printf("1:f[%d][%d][%d]=%lld\n",i,p,tp,f[i][p][tp]),*/f[i][p][tp];
	if (tp==0)
	{
		f[i][p][tp]=0;
		for (int j=0;j<=9;j++) f[i][p][tp]=(f[i][p][tp]+F(i-1,(p-j+10*d)%d,0))%mod;//,printf("sumf[%d][%d][%d]<-f[%d][%d][%d]=%lld\n",i,p,tp,i-1,(p-j+10*d)%d,0,F(i-1,(p-j+10*d)%d,0));
	}
	else
	{
		f[i][p][tp]=0;
//		printf("!!!!a(%d)\n",i,a(i));
		for (int j=0;j<=a(i)-1;j++) f[i][p][tp]=(f[i][p][tp]+F(i-1,(p-j+10*d)%d,0))%mod;//,printf("sumf[%d][%d][%d]<-f[%d][%d][%d]=%lld\n",i,p,tp,i-1,(p-j+10*d)%d,0,F(i-1,(p-j+10*d)%d,0));
		f[i][p][tp]=(f[i][p][tp]+F(i-1,(p-a(i)+10*d)%d,1))%mod;
	}
//	printf("2:f[%d][%d][%d]=%lld\n",i,p,tp,f[i][p][tp]);
	return f[i][p][tp];
}

int main()
{
	cin>>s; n=s.size(); scanf("%d",&d);
	memset(f,-1,sizeof f);
	f[0][0][0]=f[0][0][1]=1;
	for (int j=1;j<d;j++) f[0][j][0]=f[0][j][1]=0;
//	printf("%lld\n",F(1,0,0));
	printf("%lld\n",(F(n,0,1)-1+mod)%mod);
	return 0;
}

Y - Grid 2(组合数学,计数)

给定障碍物坐标,求从 $ (1,1) $ 每步向右或向下走到 $ (H,W) $ 的路径数。

首先很容易想到应该把偏左上的点的答案先处理。由于数据可以做 $ n^2 $ ,可以找到每个点前面的所有点。

首先一个常用的两点(不考虑障碍)路径数量

\[psum(x1,y1,x2,y2)={x2-x1+y2-y1 \choose x2-x1} \]

正难则反。考虑走到点 $ (x,y) $ 经过了任何障碍的方案数 $ g_{x,y} $ 。则走到点 $ (x,y) $ 不经过障碍的方案数为

\[f_{x,y}=psum(1,1,x,y)-g_{x,y} \]

根据不重不漏计数的钦定思想,钦定第一个到达的障碍为 $ j $ 。到达该障碍后, $ j $ 到达 $ i $ 的路径可以任意计数。于是有:

\[g_{i}=\sum\limits_{j,(x_j,y_j)\in[(1,1)\sim(x_i,y_i)]}f_{j} \times psum(x_j,y_j,x_i,y_i) \]

Code - Y - Grid 2 ```cpp #include #include #include #include #include using namespace std; #define MAXN 3007 struct qwq { int x,y; }e[MAXN]; inline bool cmp(qwq A,qwq B) { return A.x==B.x?A.y>=1; } return ANS; } inline void INIT() { frac[0]=1; for (int i=1;i<=MP;i++) frac[i]=frac[i-1]*i%mod; inv[MP]=qpow(frac[MP],mod-2); for (int i=MP-1;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod; } inline long long C(int N,int M) { if (N
posted @ 2021-11-18 15:29  Kan_kiz  阅读(150)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end