刷题总结——array(ssoj)
题目:
题目描述
给定 2 个正整数序列 A1, A2,序列长度分别为 L1, L2。
你可以进行以下的一次操作:
1. 选择两个数 K1,K2(1≤K1≤L1, 1≤K2≤L2);
2. 移去 A1 中最后 K1 个数,得到这 K1 个数的和 S1,L1 对应减少 K1;
3. 移去 A2 中最后 K2 个数,得到这 K2 个数的和 S2,L2 对应减少 K2;
此次操作的费用为:(S1-K1) * (S2-K2)。
进行以上操作直至两个序列都为空,求最小的费用总和。
注意:序列为空当且仅当两个序列同时为空。
输入格式
第一行是两个正整数 L1和 L2,表示 A1 与 A2 的长度。
第二行 L1 个整数,表示序列 A1[1..L1]。
第三行 L2 个整数,表示序列 A2[1..L2]。
输出格式
输出一个整数,表示最小费用。
样例数据 1
备注
【样例说明】
第一次选取 K1=1,K2=1。费用为 (3-1)*(2-1) = 2。
第二次选取 K1=2,K2=1。费用为 (1+2-2)*(1-1) = 0。
所以,总费用为 2。
【数据范围】
对 20% 的输入数据:1≤L1,L2≤20
对 40% 的输入数据:1≤L1≤400;1≤L2≤150
对 100% 的输入数据:1≤L1,L2,A1[1..L1],A2[1..L2]≤5,000
题解:
很好的一道dp题·····
首先容易想到,为了消除每次sum-k中k带来的影响,我们可以将所有元素-1,这样每次计算的时候直接sum相乘即可····
然后考虑消除的策略···
打个比方l1=l2=4···我们如果要消除a1[l1]到a1[2]和a2[l2]到a2[3]这两段区间的数的话···最好的策略肯定不是直接一次性消除···而是先消除a1[l1]和a2[l2]这两个数,再消除a1[3]到a1[2]和a2[3]这两段····因此不难发现··每次消除的话a1和a2的区间长度有一个一定为1!
所以我们可以将区间消除转化为要么消除两段末端a1[x],a2[y]中其中一个··要么同时消除两个末端··且此时对答案的贡献为a1[x]*a2[y];
得出dp方程
f[i][j]=min(f[i][j+1],f[i+1][j],f[i+1][j+1])+a1[i+1]*a2[j+1]
其中f[i][j]表示两段分别剩余i,j个数时的最少费用
代码:
#include<iostream> #include<cstdio> #include<cstdlib> #include<cmath> #include<ctime> #include<cctype> #include<cstring> #include<string> #include<algorithm> using namespace std; const int N=5001; const int inf=0x3f3f3f3f; inline int R() { char c;int f=0,i=1; for(c=getchar();(c<'0'||c>'9')&&c!='-';c=getchar()); if(c=='-') c=getchar(),i=-1; for(;c<='9'&&c>='0';c=getchar()) f=(f<<3)+(f<<1)+c-'0'; return f*i; } int l1,l2,a1[N],a2[N]; int f[N][N]; int main() { //freopen("a.in","r",stdin); l1=R(),l2=R(); memset(f,inf,sizeof(f)); for(int i=1;i<=l1;i++) a1[i]=R(),a1[i]--; for(int i=1;i<=l2;i++) a2[i]=R(),a2[i]--; f[l1-1][l2-1]=a1[l1]*a2[l2]; for(int i=l1-1;i>=0;i--) for(int j=l2-1;j>=0;j--) { if(i==l1-1&&j==l2-1) continue; f[i][j]=min(f[i][j+1],min(f[i+1][j+1],f[i+1][j]))+a1[i+1]*a2[j+1]; } cout<<f[0][0]<<endl; return 0; }