一.You Are the One HDU 4283
题目大意:有一群屌丝,每个屌丝有个屌丝值,如果他第K个上场,屌丝值就为a[i]*(k-1),通过一个小黑屋来调整,但是先进屋的最后才能出来(栈),使得最后总屌丝值最小。
设第i个人第k个出场,根据1、2、3入栈1在第3个出场的规律,我们发现[i+1,k+i-1]这个区间的人(也就是本例中的2、3)均已出场。而剩下的还均未入场。
由此得状态转移方程:dp[i][j]=min(dp[i][j],dp[i+1,i+k-1]+dp[i+k,j]+(k-1)*a[i]+(sum[j]-sum[i+k-1])*k); //已出场人物的dp(即屌丝值)加上这个人的屌丝值加上后面的人的值以及后面的人的不断根据变化着的屌丝值总和sum(不断累加)?其实并不是很懂。
代码:
for (int i = 1; i <= n; i++)
for (int j = i+1; j <= n; j++)
dp[i][j] = INF; //全员最大
for (int len = 2; len <= n; len++)
{
for (int i = 1, j = len; j <= n; i++, j++)
{
for (int k = 1; k <= j-i+1; k++)//中间这个人的选择是在1到k前面这些人中间选的
dp[i][j] = min(dp[i][j], dp[i+1][i+k-1] + a[i]*(k-1) + dp[i+k][j] + (sum[j]-sum[i+k-1])*k);
}
}
二.HDU3506_Monkey Party
题意:输入n,表示有n只猴子围成了一个圈来相互认识,然后输入每只猴子要介绍认识另一只猴子所要花费的时间为代价。并且已知猴子A和猴子B互相认识,那么,猴子A所认识的其他猴子和猴子B所认识的其他猴子也都能够相互认识。故此时花费的代价就是所有AB猴子认识的猴子所用的时间和。
典型的石子归并问题。区别是这里的猴子围成了一个圈,但石子归并是一条线,只要稍改条件即可。
在这里我们用将n前面的n-1个猴子一个个移到n后面去,就可以将问题化成一条线的准确石子归并问题了。
代码如下(加了四边形优化的,普通版可以直接写石子归并略改的代码):
#include<iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;
#define rush() int T;scanf("%d",&T);while(T--)
typedef long long ll
const int maxn = 100*2;
const ll mod = 1e9+7;//科学计数
const int INF = 0x3f3f3f3f; //十六进制的超大的一个值
const double eps = 1e-9;
int a[maxn];
int sum[maxn];
int dp[maxn][maxn];
int s[maxn][maxn];
int main()
{
int n;
while(cin>>n)
{
sum[0]=0;
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n;i++)
{
cin>>a[i];
sum[i]=sum[i-1]+a[i];
s[i][i]=i;
dp[i][i]=0;
}
for(int i=1;i<n;i++)//四边形优化,看不懂
{
sum[i+n]=sum[i+n-1]+a[i];
s[i+n][i+n]=i+n;
dp[i+n][i+n]=0;
}
for(int len=2;len<=n;len++)
for(int i=1;i<=2*n-1;i++)
{
int j=i+len-1;
if(j>2*n-1) break;//经过了一圈的交朋友
for(int k=s[i][j-1];k<=s[i+1][j];k++)
{
if(dp[i][j]>dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1])
{
dp[i][j]=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1];
s[i][j]=k;
}
}
}
int ans=INF;
for(int i=1;i<=n;i++)
{
ans=min(ans,dp[i][i+n-1]);
}
printf("%d\n",ans);
}
return 0;
}
四方形优化,不懂请看:https://blog.csdn.net/NOIAu/article/details/72514812 ;
(我也不懂,代码搬运于:https://blog.csdn.net/my_sunshine26/article/details/77141398 )
三.整数划分
题目大意:给出两个整数 n , m ,要求在 n 中加入m - 1 个乘号,将n分成m段,求出这m段的最大乘积。
定义dp [ i ] [ j ]为从开始到 i 中加入 j 个乘号得到的最大值。我们依次计算添加从1到m-1个乘号的结果,只需要枚举放入x个乘号的位置得出最大值即可。
状态转移方程:dp [ i ] [ j ] = max (dp [ i ] [ j ] , dp [ k ] [ j-1 ] * a [ k+1 ] [ i ] ) ;
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
long long a[20][20];
long long dp[25][25];
int main()
{
int T,m;
cin>>T;
while(T--)
{
char s[22];
cin>>s;
cin>>m;
int l=strlen(s),ok=1;
memset(a,0,sizeof(a));
for(int i=1;i<l;i++)
{
if(s[i]=='0')
ok=0;
for(int j=i;j<l;j++)
{
a[i][j]=a[i][j-1]*10+(s[j]-'0');//把字符换成数字的所有可能列举出来
}
}
if(ok==0&&l-1==m||l-1<m)//里面有个零,并且恰好要放入间隔个乘号的时候,必定乘0,结果也是零
{
printf("0\n");continue;
}
long long x,ans;
memset(dp,0,sizeof(dp));
for(int i=0;i<l;i++)
dp[i][1]=a[1][i];
for(int j=2;j<=m;j++)//乘号的个数
{
for(int i=j;i<l;i++)//被乘数的数目
{
for(int k=1;k<i;k++)
{
dp[i][j]=MAX(dp[i][j],dp[k][j-1]*a[k+1][i]);//一个个枚举
}
}
}
printf("%lld\n",dp[l-1][m]);
}
return 0;
}
四.括号匹配(二)
给你一个字符串,里面只包含"(",")","[","]"四种符号,请问你需要至少添加多少个括号才能使这些括号匹配起来。
跟回文串匹配差不多,都是左右添加元素,使他们相匹配。只不过回文串的题目较难,因为字符数目太多,而括号只有两种。
显然,要使添加的括号尽量少,我们需要使原来的括号序列尽可能多得匹配,即先求最大匹配数量,那么还剩下一些没有匹配的括号,我们就需要依次加上一个括号使它们得到匹配。综上所述,所求=原序列括号数量-最大匹配括号数量。
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
#define mst(a,b) memset((a),(b),sizeof(a))
#define rush() int T;scanf("%d",&T);while(T--)
typedef long long ll;
const int maxn = 105;
const ll mod = 1e9+7;
const ll INF = 1e18;
const double eps = 1e-9;
char s[maxn];
int dp[maxn][maxn];
int main()
{
rush()
{
scanf("%s",s+1);
int n=strlen(s+1);
mst(dp,0);
for(int len=2;len<=n;len++)
for(int i=1;i<=n;i++)
{
int j=i+len-1;
if(j>n) break;
if(s[i]=='('&&s[j]==')'||s[i]=='['&&s[j]==']')
{
dp[i][j]=dp[i+1][j-1]+2;
}
for(int k=i;k<j;k++)
{
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k][j]);
}
}
printf("%d\n",n-dp[1][n]);
}
return 0;
}