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[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 $ 的期望次数
那么有
发现 $ a=n-b-c-d $
替换一下状态w
发现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 $ 中的一个数,双方都以最优策略行动,判断先手必胜还是后手必胜。
设 $ f[i] $ 表示当前先手取时必输/必胜(0/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] $ 的答案(先手最多取多少)...
那么就有
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 $ 取模。
设 $ f[i][j] $ 表示给前 $ i $ 个人分 $ j $ 个糖果的方案数
那么有
这个是O(nk^2)的()优化的话...可以加个前缀和
设 $ c[j] $ 表示 $ \sum_{k=1}{j}f[i-1][k] $ ..
那么就有
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(
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[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\) 染成白/黑的方案数。
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\),上一位未满/满的方案数。
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 $ ,可以找到每个点前面的所有点。
首先一个常用的两点(不考虑障碍)路径数量
正难则反。考虑走到点 $ (x,y) $ 经过了任何障碍的方案数 $ g_{x,y} $ 。则走到点 $ (x,y) $ 不经过障碍的方案数为
根据不重不漏计数的钦定思想,钦定第一个到达的障碍为 $ j $ 。到达该障碍后, $ j $ 到达 $ i $ 的路径可以任意计数。于是有: