链接:Educational DP Contest - AtCoder
A - Frog 1
题意:有n个石头,从1石头出发,每次可以跳一格或者俩格,代价为俩个格子间的高度差
思路:对于第i个石头,可以从石头i-1和i-2得到,比较一下代价即可
f[i]=max(f[i-1]+abs(h[i]-h[i-1),f[i-2]+abs(h[i]-h[i-2])
代码:
#include <bits/stdc++.h>
//#define endl '\n'
#define ll long long
using namespace std;
const ll mod=1e9+7;
const int N=1e6+7;
void solve()
{
int n;
cin>>n;
vector<int> a(n);
for(int &x:a){
cin>>x;
}
vector<int> f(n,1e9);
f[0]=0;
for(int i=1;i<n;i++){
if(i-1>=0){
f[i]=min(f[i-1]+abs(a[i]-a[i-1]),f[i]);
}
if(i-2>=0){
f[i]=min(f[i],min(f[i-1]+abs(a[i]-a[i-1]),f[i-2]+abs(a[i]-a[i-2])));
}
}
cout<<f[n-1]<<endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
B - Frog 2
题意:将上一题的跳跃距离改为k
思路:枚举i-k到i-1到i的代价就可以了
代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e4;
const int N=1e5+7;
void solve()
{
int n,k;
cin>>n>>k;
vector<int> a(n+1);
for(int i=1;i<=n;i++) cin>>a[i];
vector<int> dp(n+1,1e9);
dp[1]=0;
//f[0]=0;
for(int i=1;i<=n;i++){
for(int j=i-1;j>=0&&(i-j)<=k;j--){
dp[i]=min(dp[i],dp[j]+abs(a[i]-a[j]));
}
}
cout<<dp[n]<<endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
C - Vacation
题意:有N天,每天可以选择三件事情其中一件事情去做,获得该事情当天对应的快乐值,不能连续俩天做一样的事情,求最大的快乐
思路:对于第i天的事情,如果做A,那就是从前一天做B或者做C转移过来
dp数组开第二维分别对应ABC三件事情
对于BC同理转移
代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e4;
const int N=1e5+7;
ll f[N][5];
void solve()
{
int n;
cin>>n;
vector<ll> a(n+1);
vector<ll> b(n+1);
vector<ll> c(n+1);
for(int i=1;i<=n;i++){
cin>>a[i]>>b[i]>>c[i];
}
f[0][1]=f[0][2]=f[0][3]=0;
for(int i=1;i<=n;i++){
f[i][1]=max(f[i-1][2]+a[i],f[i-1][3]+a[i]);
f[i][2]=max(f[i-1][1]+b[i],f[i-1][3]+b[i]);
f[i][3]=max(f[i-1][1]+c[i],f[i-1][2]+c[i]);
}
cout<<max(f[n][1],max(f[n][2],f[n][3]))<<endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
D - Knapsack 1
题意:N件物品,每件有一个重量和价值,给定一个背包最大容量,求最大体积
思路:总体积较小的背包问题可以枚举重量,dp数组含义为对于重量i的最大价值是多少,注意是每件只能拿一件
对于每一种质量i: 可以有
代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e4;
const int N=1e5+7;
void solve()
{
int n,w;
cin>>n>>w;
vector<pair<int,ll>> q(n);
for(int i=0;i<n;i++){
cin>>q[i].first>>q[i].second;
}
vector<ll> f(w+1,0);
for(int i=0;i<n;i++){
for(int j=w;j>=q[i].first;j--){
f[j]=max(f[j],f[j-q[i].first]+q[i].second);
}
}
//cout<<f[3]<<endl;
cout<<f[w]<<endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
E - Knapsack 1
题意:同上,但是w的范围变大了
思路:改为枚举价值,最后从大到小看价值对于的质量是否超过W
代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e4;
const int N=1e5+7;
void solve()
{
int n,w;
cin>>n>>w;
vector<pair<ll,int>> q(n);
for(int i=0;i<n;i++){
cin>>q[i].first>>q[i].second;
}
vector<ll> f(N+1,1e12);
f[0]=0;
for(int j=0;j<n;j++){
for(int i=N;i>=q[j].second;i--){
f[i]=min(f[i],f[i-q[j].second]+q[j].first);
}
}
for(int i=N;i>=0;i--){
if(f[i]<=w){
cout<<i<<endl;
return;
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
F - LCS
题意:对于俩个字符串,找到最大公共字符串
思路:dp数组俩维表示a字符串到i的位置,b字符串到j的位置的时候的最大匹配
所以有
else
代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e4;
const int N=3e3+7;
int f[N][N];
void solve()
{
string a,b;
cin>>a>>b;
f[0][0]=0;
for(int i=0;i<a.size();i++){
for(int j=0;j<b.size();j++){
if(a[i]==b[j]){
f[i+1][j+1]=max(f[i+1][j+1],f[i][j]+1);
}
else{
f[i+1][j+1]=max(f[i+1][j],f[i][j+1]);
}
}
}
//cout<<f[a.size()][b.size()]<<endl;
int i=a.size(),j=b.size();
vector<char> ans;
while(i!=0&&j!=0){
if(a[i-1]==b[j-1]){
ans.push_back(a[--i]);
j--;
}
else if(f[i-1][j]<f[i][j-1]){
j--;
}
else{
i--;
}
}
reverse(ans.begin(),ans.end());
for(auto it:ans){
cout<<it;
}
cout<<endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
G - Longest Path
题意:图上最长路径
思路:从一个点出发的时候,找他的最长儿子,然后从多个点出发都试试
代码:
#include <bits/stdc++.h>
//#define endl '\n'
#define ll long long
using namespace std;
const ll mod=1e9+7;
const int N=1e5+7;
vector<int> q[N];
int dp[N];
int ans=0;
int dfs(int now){
//cout<<1<<endl;
if(dp[now]){
return dp[now];
}
for(auto it:q[now]){
dp[now]=max(dp[now],dfs(it)+1);
}
return dp[now];
}
void solve()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
//if(x>y) swap(x,y);
q[x].push_back(y);
//q[y].push_back(x);
}
//cout<<1<<endl;
for(int i=1;i<=n;i++){
ans=max(ans,dfs(i));
}
cout<<ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
H - Grid 1
题意:询问从(1,1)到(n,m)的路径数量
思路:dp数组俩维表示到(i,j)的时候的路径数量
同时如果(i-1,j)是非法的就不用加,(i,j-1)同理,如果(i,j)不能走就不用修改了,记得取模QAQ
代码:
#include <bits/stdc++.h>
//#define endl '\n'
#define ll long long
using namespace std;
const ll mod=1e9+7;
const int N=1e5+7;
char mp[1005][1050];
void solve()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>mp[i][j];
}
}
vector<vector<ll>> f(n+1,vector<ll>(m+1,0));
f[1][1]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(i==1&&j==1) continue;
if(mp[i][j]=='#') continue;
if(i-1>=1&&mp[i-1][j]!='#'){
f[i][j]+=f[i-1][j]%mod;
}
if(j-1>=1&&mp[i][j-1]!='#'){
f[i][j]+=f[i][j-1]%mod;
}
f[i][j]%=mod;
}
}
cout<<f[n][m]<<endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
I - Coins
题意:有N个硬币,丢出后每一有一个概率p,共n个p的概率朝上,求超过一半的硬币向上的概率
思路:dp数组俩维为对于前i个硬币有j个向上
就是当前状态可以由i-1个硬币来,这个硬币是朝上或者朝下,
注意j-1不能越界
代码:
#include <bits/stdc++.h>
//#define endl '\n'
#define ll long long
using namespace std;
const ll mod=1e9+7;
const int N=3e3+7;
double f[N][N];
void solve()
{
int n;
cin>>n;
vector<double> p(n+1);
for(int i=1;i<=n;i++){
cin>>p[i];
}
f[1][1]=p[1];
f[1][0]=(1.0-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.0-p[i]);
}
else{
f[i][j]=f[i-1][j-1]*p[i]+f[i-1][j]*(1.0-p[i]);
}
}
}
double ans=0;
for(int i=(n+1)/2;i<=n;i++){
ans+=f[n][i];
}
cout<<setprecision(12)<<ans<<endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
J - Sushi
题意:给定N个盘子,每个盘子上面有ai个寿司,每次在1到n等概率抽取一个盘子去掉一个寿司,(如果没有就不用去),求去掉全部寿司的期望
思路:ai的范围只有1~3,我们使用三维dp数组来记录每一种数量的寿司的变化,i,j,k表示使用1,2,3个寿司的盘子个数
例如使用了三个寿司的盘子,那么三个寿司的盘子数量会减少1,但是俩个盘子的数量会增加1;影响他人的要放在循环的外围,还有可以没变化的操作。
其他同理
代码:
#include <bits/stdc++.h>
//#define endl '\n'
#define ll long long
using namespace std;
const ll mod=1e9+7;
const int N=300+7;
#define all (i+j+k)
double f[N][N][N];
void solve()
{
vector<int> book(5,0);
int n;
cin>>n;
for(int i=1;i<=n;i++){
int x;cin>>x;
book[x]++;
}
for(int k=0;k<=n;k++){
for(int j=0;j<=n;j++){
for(int i=0;i<=n;i++){
if(i||j||k){
if(i){
f[i][j][k]+=f[i-1][j][k]*((double)i/all);
}
if(j){
f[i][j][k]+=f[i+1][j-1][k]*((double)j/all);
}
if(k){
f[i][j][k]+=f[i][j+1][k-1]*((double)k/all);
}
f[i][j][k]+=(double)n/all;
}
}
}
}
cout<<setprecision(12)<<f[book[1]][book[2]][book[3]]<<endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
K - Stones
题意:一个集合有n个数字,轮流拿石头数量为集合中的数字的石头,直到有K个石头,求谁赢,没法拿的人就算输了
思路:背包问题的变形,dp数组表示数量总和为i的时候的数字,然后枚举所有质量,所有质量下去看是谁先手后手
代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e4;
const int N=3e3+7;
void solve()
{
int n,k;
cin>>n>>k;
vector<int> v(n+1);
for(int i=1;i<=n;i++){
cin>>v[i];
}
vector<int> dp(k+1,0);
for(int i=1;i<=k;i++){
for(int j=1;j<=n;j++){
if(i>=v[j]){
dp[i]|=(!dp[i-v[j]]);
}
}
}
if(dp[k]){
cout<<"First"<<endl;
}
else cout<<"Second"<<endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
L - Deque
题意:给出一个n个数字的序列,俩人轮流从前或者后拿一个数字,结果为俩个人分别取到的数字的总和之差,先手的要让差尽可能大,后手的要让他尽可能小,求俩人都用最优策略的结果
思路:区间DP,可以枚举n个长度的时候取左边还是取右边,dp数组表示为在区间[l,r]的时候的结果,那么[l,r]区间的结果就是由[l+1,r] [l,r-1]得到的
对于后手的考虑成减法就可以了
代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e4;
const int N=3e3+7;
ll dp[N][N];
void solve()
{
int n,k;
cin>>n;
vector<ll> a(n+1);
for(int i=1;i<=n;i++){
cin>>a[i];
}
memset(dp,0,sizeof(dp));
for(int len=1;len<=n;len++){
for(int l=1,r=len;r<=n;l++,r++){
if((n-len)%2==0){
dp[l][r]=max(dp[l+1][r]+a[l],dp[l][r-1]+a[r]);
}
else{
dp[l][r]=min(dp[l+1][r]-a[l],dp[l][r-1]-a[r]);
}
}
}
cout<<dp[1][n]<<endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
M - Candies
题意:有N个人,要分给他们K个糖果,第i个人可以收到0~ai个糖果,求有多少种分配方案
思路:dp数组表示到第i个人的时候已经分掉了j个糖果,那么dp[i][j]的状态就可以从dp[i-1][j-x]
x为0~a[j]
但是光是这样还会超时,所以对dp[i-1][j-x]要做一个前缀和处理了
代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e9+7;
const int N=1e5+7;
ll dp[105][N];
ll pre[105][N];
void solve()
{
int n,k;
cin>>n>>k;
vector<int> a(n+1);
for(int i=1;i<=n;i++){
cin>>a[i];
}
dp[1][0]=pre[1][0]=1;
for(int i=1;i<=k;i++){
dp[1][i]=(i<=a[1]);
pre[1][i]=dp[1][i]+pre[1][i-1];
}
for(int i=2;i<=n;i++){
dp[i][0]=pre[i][0]=1;
for(int j=1;j<=k;j++){
if(j<=a[i]){
dp[i][j]=pre[i-1][j]%mod;
}
else{
dp[i][j]=(pre[i-1][j]-pre[i-1][j-a[i]-1]+mod)%mod;
}
pre[i][j]=(pre[i][j-1]+dp[i][j])%mod;
}
}
cout<<dp[n][k]<<endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
N - Slimes
题意:石子合并,有N块石头,每块有一个质量,将相邻石头堆合并后代价是俩者的和,求最小的代价
思路:区间DP,从小到大枚举合并的长度,然后枚举合并时候的中间点,dp数组含义为i开始的长度为len的石头堆合并的代价
代价和可以利用前缀和计算
代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e9+7;
const int N=1e5+7;
ll dp[405][405];
void solve()
{
int n;
cin>>n;
vector<ll> a(n+1);
vector<ll> pre(n+1);
memset(dp,0x3f3f3f,sizeof(dp));
for(int i=1;i<=n;i++){
cin>>a[i];
pre[i]=pre[i-1]+a[i];
dp[i][1]=0;
}
for(int len=2;len<=n;len++){
for(int j=1;j<=n-len+1;j++){
for(int k=1;k<len;k++){
dp[j][len]=min(dp[j][len],dp[j][k]+dp[j+k][len-k]+pre[j+len-1]-pre[j-1]);
//cout<<dp[j][len]<<endl;
}
}
}
cout<<dp[1][n]<<endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
O - Matching
题意:有N个男生和N个女生,给出一个二维数组代表第i个男生和第j个女生是否匹配,求让所有人都匹配的方案数量
思路:观察到N的数量很小,像是状压DP,枚举女生所有状态,用01串表示,第i个位置是1表示有第i个女生的状态,然后和cnt个男生匹配,cnt表示该状态中拥有的女生的数量,然后枚举每一个i
看mp[cnt][i]是否匹配和这个女生是否存在在这个状态中,可以的话进行转移
这个状态可以从没有这个女生且少一个男生的状态转移来,利用了滚动数组,所以只要dp数组只要记录状态S就行了
S-(1<<i-1)就是从01串中将第i个女生(为1)去掉
代码:
#include <bits/stdc++.h>
//#define endl '\n'
#define ll long long
using namespace std;
const ll mod=1e9+7;
const int N=300+7;
int mp[25][25];
int f[1<<22];
void solve()
{
int n;
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>mp[i][j];
}
}
f[0]=1;
for(int S=1;S<1<<n;S++){
int cnt=__builtin_popcount(S);
for(int i=1;i<=n;i++){
if(mp[cnt][i]&&S>>i-1&1){
f[S]=(f[S]+f[S-(1<<i-1)])%mod;
}
}
}
cout<<f[(1<<n)-1]<<endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
P - Independent Set
题意:给出一棵树,要给树上的点染色,可以染成白色或者黑色,黑色不能连续染,求有多少种方案
思路:经典树DP,dp数组为i的节点是染什么色的,0白1红,白色节点的子节点就可以是黑色或者白色,黑色节点下面就是白色,先遍历子节点,然后统计答案
代码:
#include <bits/stdc++.h>
//#define endl '\n'
#define ll long long
using namespace std;
const ll mod=1e9+7;
const int N=1e5+7;
vector<int> q[N];
ll dp[N][2];
void dfs(int now,int fa){
dp[now][1]=dp[now][0]=1;
for(auto it:q[now]){
if(it==fa) continue;
dfs(it,now);
dp[now][0]=(dp[now][0]*(dp[it][0]+dp[it][1])%mod)%mod;
dp[now][1]=(dp[now][1]*dp[it][0])%mod;
}
}
void solve()
{
int n;
cin>>n;
for(int i=1;i<n;i++){
int x,y;
cin>>x>>y;
q[x].push_back(y);
q[y].push_back(x);
}
dfs(1,0);
ll ans=(dp[1][0]+dp[1][1])%mod;
cout<<ans<<endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}