①HDU5115:http://acm.hdu.edu.cn/showproblem.php?pid=5115
题意:每只狼有自身攻击和额外攻击两个属性,杀手杀死它时受到它自身攻击(ai)和相邻两只(一只)狼的额外攻击(b(i-1) , b(i+1))之和,杀死之后相邻关系会对应调整,
求杀手杀死全部狼受到的最少伤害。
思路:一开始一直想贪心,结果还是放弃了.....
区间DP,dp[i][j]表示杀死第i~j只狼所受到的最小伤害,枚举k(k∈[ i , j ] )为最后一只被杀死的狼求出dp[i][j]。
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 203;
const int inf = 0x3f3f3f3f;
int a[N],b[N],dp[N][N];
int main()
{
int T,cas = 1;
scanf("%d",&T);
while(T--)
{
int n;
scanf("%d",&n);
memset(dp,0,sizeof(dp));
for(int i = 1; i <= n; i++)
{
for(int j = i; j <= n; j++)
dp[i][j] = inf;
}
a[0] = a[n+1] = b[0] = b[n+1] = 0;
for(int i = 1; i <= n; i++)
scanf("%d",a+i);
for(int i = 1; i <= n; i++)
scanf("%d",b+i);
for(int i = n; i >= 1; i--)
{
for(int j = i; j <= n; j++)
{
for(int k = i; k <= j; k++)
dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k+1][j]+a[k]+b[i-1]+b[j+1]);//dp[i][k-1],dp[k+1][j]必须为已知,故第一、二层循环分别为倒序和正序
}
}
printf("Case #%d: %d\n",cas++,dp[1][n]);
}
return 0;
}
②HDU5900:http://acm.hdu.edu.cn/showproblem.php?pid=5900
题意:给定N个二元组(含有key和value),相邻且gcd不为1的两个二元组可以消去,之后旁边两个二元组变成相邻的。
思路:明显的区间DP,如果从i+1~j-1都可以被消去,则dp[i][j] = dp[i+1][j-1]+value[i]+value[j]。
判断区间是否可以被消去,可以建一个前缀数组sum,如果sum[j-1]-sum[i] = dp[i+1][j-1]即等于区间内所有值之和,说明可以被消去。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 305;
ll gcd(ll a,ll b)
{
return b ? gcd(b, a % b) : a;
}
int n;
ll key[N],val[N],sum[N],dp[N][N];
void init()
{
memset(dp,0,sizeof(dp));
memset(sum,0,sizeof(sum));
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
init();
scanf("%d",&n);
for(int i = 1; i <= n; i++)
scanf("%lld",key+i);
for(int i = 1; i <= n; i++)
{
scanf("%lld",val+i);
sum[i] = sum[i-1] + val[i];
}
for(int i = n - 1; i >= 1; i--)
{
for(int j = i + 1; j <= n; j++)
{
if(gcd(key[i],key[j]) > 1 && sum[j-1] - sum[i] == dp[i+1][j-1])
{
dp[i][j] = max(dp[i][j],sum[j] - sum[i-1]);
continue;
}
for(int k = i; k < j; k++)
dp[i][j] = max(dp[i][j], dp[i][k] + dp[k+1][j]);
}
}
printf("%lld\n",dp[1][n]);
}
return 0;
}
③HDU4283:http://acm.hdu.edu.cn/showproblem.php?pid=4283
题意:有N个屌丝,屌丝值为a[i],第k个出场的屌丝unhappy值为(k-1)*a[i],导演可以将一些屌丝关进小黑屋(栈)中,在适当的时候再让他们出来,以调整顺序。问怎么调整出场顺序使得总unhappy值最小。
思路:能想到是DP,但想不到是区间DP =_= 。。。dp[i][j]表示区间[ i , j ]的屌丝出场的最小unhappy值,则可得到方程:
dp[i][j] = min(dp[i][j],dp[i+1][k] + dp[k+1][j] + (k - i + 1)*(sum[j] - sum[k]) + (k - i)*a[i])表示在k之后i出场(k == i表示i第一个出场),由于i的插入,在i之后出场的相比之前([i+1,j])需要增加(k - i + 1)倍该区间的屌丝值,再加i本身(k-i)*a[i]的unhappy值。
#include<bits/stdc++.h>
using namespace std;
const int N = 103;
const int inf = 0x3f3f3f3f;
int a[N],dp[N][N],sum[N];
int main()
{
int T,cas = 1;
scanf("%d",&T);
while(T--)
{
int n;
scanf("%d",&n);
for(int i = 1; i <= n; i++)
{
scanf("%d",a+i);
sum[i] = sum[i-1] + a[i];
for(int j = i; j <= n; j++)
dp[i][j] = inf;
}
for(int i = n; i >= 1; i--)
{
for(int j = i; j <= n; j++)
{
for(int k = i; k <= j; k++)
dp[i][j] = min(dp[i][j],dp[i+1][k] + dp[k+1][j] + (k - i + 1)*(sum[j] - sum[k]) + (k - i)*a[i]);
}
}
printf("Case #%d: %d\n",cas++,dp[1][n]);
}
return 0;
}
④:HDU5693:http://acm.hdu.edu.cn/showproblem.php?pid=5693
思路:任何大于1的数都可以拆成2x+3y的形式,所以只需要考虑删除2或3个元素的情况。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 303;
int dp[N][N],a[N];
bool yes[N][N];
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int n,m;
scanf("%d %d",&n,&m);
memset(dp,0,sizeof(dp));
memset(yes,0,sizeof(yes));
for(int i = 1; i <= n; i++)
scanf("%d",a+i);
for(int i = 1; i <= m; i++)
{
int x;
scanf("%d",&x);
for(int j = 1; j <= n; j++)
{
for(int k = j + 1; k <= n; k++)
{
if(a[k] - a[j] == x)
yes[j][k] = 1;
}
}
}
for(int i = n - 1; i >= 1; i--)
{
for(int j = i + 1; j <= n; j++)
{
if(yes[i][j] && dp[i+1][j-1] == j - i - 1)//中间元素都已经被删除
dp[i][j] = j - i + 1;
for(int k = i + 1; k < j; k++)
{
int f1 = 0,f2 = 0;
if(yes[i][k] && dp[i+1][k-1] == k - i - 1)//删除i,k两个元素
dp[i][j] = max(dp[i][j],k - i + 1 + dp[k+1][j]), f1 = 1;
if(yes[k][j] && dp[k+1][j-1] == j - k - 1)//删除k,j两个元素
dp[i][j] = max(dp[i][j],j - k + 1 + dp[i][k-1]), f2 = 1;
if(f1 && f2 && a[j] - a[k] == a[k] - a[i])//删除i,j,k三个元素
dp[i][j] = max(dp[i][j],j - i + 1);
dp[i][j] = max(dp[i][j],dp[i][k] + dp[k+1][j]);
}
}
}
printf("%d\n",dp[1][n]);
}
return 0;
}
⑤HDU5151:http://acm.hdu.edu.cn/showproblem.php?pid=5151
题意:有n个人,n把椅子有红蓝两种颜色,n个人按顺序依次选座位,当一把椅子相邻的两把椅子非空且颜色不同时不能入座,问有几种每个人都能选到座位的方法。
思路:dp[i][j]表示对区间[i, j]的椅子有多少不同的方案,枚举k作为区间中最后一个被选的位置。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 103;
const int mod = 1e9 + 7;
ll dp[N][N],c[N][N];
int a[N];
int main()
{
int n;
c[0][0] = 1;
for(int i = 1; i <= 100; i++)
{
c[i][0] = 1;
for(int j = 1; j <= i; j++)
c[i][j] = (c[i-1][j-1] + c[i-1][j]) % mod;//组合数递推公式
}
while(~scanf("%d",&n))
{
memset(dp,0,sizeof(dp));
for(int i = 1; i <= n; i++)
scanf("%d",a+i);
for(int i = n; i >= 1; i--)
{
dp[i][i] = 1;
for(int j = i + 1; j <= n; j++)
{
for(int k = i; k <= j; k++)
{
if(k == i)
dp[i][j] = (dp[i][j] + dp[k+1][j]) % mod;
else if(k == j)
dp[i][j] = (dp[i][j] + dp[i][k-1]) % mod;
else
{
if(a[k-1] == a[k+1])
dp[i][j] = (dp[i][j] + c[j-i][k-i] *dp[i][k-1] % mod * dp[k+1][j] % mod) % mod;//在j-i个人中选k-i个人坐在k左侧,其余坐右侧
}
}
}
}
printf("%d\n",dp[1][n]);
}
return 0;
}
⑥HDU4293:http://acm.hdu.edu.cn/showproblem.php?pid=4293
题意:一开始理解错了,以为询问回答的是自己在自己队伍里的位置,其实应该是自己队伍的前后各有多少人,问最多有多少人说的是真话。
思路:又是面向题解...dp[i][j]表示区间[i , j]的人为一组时[i , j]内最多有多少人说真话,如果a + b + 1> n显然是假的,并且dp[i][j]不能超过其区间长度,ans[i]表示前i个人中最多多少人说真话。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 503;
int ans[N],dp[N][N];
int main()
{
int n,a,b;
while(~scanf("%d",&n))
{
memset(dp,0,sizeof(dp));
memset(ans,0,sizeof(ans));
for(int i = 1; i <= n; i++)
{
scanf("%d %d",&a,&b);
if(a + b + 1 <= n && dp[a+1][n-b] < n - a - b)
dp[a+1][n-b]++;
}
for(int j = 1; j <= n; j++)
{
for(int i = 0; i < j; i++)
ans[j] = max(ans[j],ans[i] + dp[i+1][j]);
}
printf("%d\n",ans[n]);
}
return 0;
}