1.石子合并
【问题描述】
在一圆形操场四周摆放N 堆石子, 现要将石子有次序地合并成一堆.规定每次只能选相邻的
两堆合并成一堆,并将新的一堆的石子数,记为该次合并的得分。
编一程序,由文件读入堆数N 及每堆石子数,
(1)选择一种合并石子的方案,使得做N-1 次合并,得分的总和最少
(2)选择一种合并石子的方案,使得做N-1 次合并,得分的总和最大
【输入】
第1 行:1 个整数n,表示石子的数量
第2 行:n 个用空格分开的整数,表示各堆石子的数量。
【输出】
第1 行:1 个整数,表示最小的归并代价
第2 行:1 个整数,表示最大的归并代价
【输入输出样例1】
3
13 7 8
43
49
【数据说明】
对于20%的数据,n ≤ 10
对于40%的数据,n ≤ 20
对于70%的数据,n ≤ 50
对于100%的数据,n ≤ 100,每堆石子数量不超过10000
分析:
很明显经典的动态规划题,只是这次是环形的而已,把数组加倍,变成线性的。
定义dp[i][j] 从 i 到 j 的最值
定义l 枚举长度 2–2*n i 起始 j终止
则 1< i< 2*n-l-1 j=i+l-1
枚举断点k
dp[i][j]=max/min(dpmax[i][k]+dpmax[k+1][j]+sum[j]-sum[i-1])
sum 为前缀和。
#include<cstdio>
#include<cstring>
#include<iostream>
#define mem(a,x) memset(a,x,sizeof(a))
using namespace std;
const int inf=0x3f3f3f3f;
int n;
int a[105],sum[205];
int dpmax[205][205],dpmin[205][205];
void init(){
freopen("stone.in","r",stdin);
freopen("stone.out","w",stdout);
}
void readdata(){
scanf("%d",&n);
for(int i=1;i<=n ;i++)
{
scanf("%d",&a[i]);
sum[i]=sum[i-1]+a[i];//前缀和
}
//for(int i=1 ;i<=n;i++)
// cout<<sum[i]<<" ";
for(int i=n+1 ;i<=2*n-1 ;i++)
sum[i] = sum[i-1] +sum[i-n]-sum[i-n-1];
}
void work(){
for(int l=2;l<=2*n;l++)
{
for(int i=1 ;i<=2*n-l+1 ;i++ )
{
int j=i+l-1;
int maxv=-inf,minv=inf;
for(int k=i ;k<j; k++)
{
maxv=max(maxv,dpmax[i][k]+dpmax[k+1][j]+sum[j]-sum[i-1]);
minv=min(minv,dpmin[i][k]+dpmin[k+1][j]+sum[j]-sum[i-1]);
}
dpmin[i][j]=minv;
dpmax[i][j]=maxv;
}
}
}
int main(){
init();
readdata();
work();
int ansmin=inf,ansmax=-inf;
for(int i=1;i<=n;i++)
{
ansmin=min(ansmin,dpmin[i][i+n-1]);
ansmax=max(ansmax,dpmax[i][i+n-1]);
}
printf("%d\n",ansmin);
printf("%d",ansmax);
return 0;
}
2.子序列
【问题描述】
在一场训练赛中,姜神带领他们队AK 全场后,显得特别无聊,无意中写下n 个正整数,突
然灵机一动想出了一道美妙的题目。
姜神从这n 个正整数中选出一些数,组成一个非递减的子序列,很容易就可以知道,有多少
个由正整数组成的长度跟这个子序列相等的序列比当前这个子序列小。
一个序列x = x1,x2,…xr 比另一个序列y = y1,y2,…yr 小,当且仅当,
对于任意的i ( 1 ≤ i ≤ r),xi ≤ yi
现在姜神想知道,对于所有不同的非递减子序列,一共有多少个这样的序列。
【输入】
第1 行:1 个正整数n
第2 行:n 个用空格分开的整数
【输出】
第1 行:1 个整数, 表示一共有多少个序列满足要求,由于结果可能很大,输出除以
1000000007 后的余数。
【输入输出样例1】
3
1 2 2
13
分析:
这题求数量当然也可以看做是一个动态规划。
然而难点在于去重。
要全过还需要离散化。
#include <cstdio>
#include <algorithm>
using namespace std;
#define MAXN 100010
#define mo 1000000007
typedef long long LL;
int n;
LL Tree[MAXN],f[MAXN],dec[MAXN];
struct node
{
int val,i,hash;
bool operator<(const node &a) const{
return i<a.i;
}
}a[MAXN];
bool CMP(node a,node b)
{
return a.val<b.val;
}
void Update(int i,LL val)
{
for(;i<=n;Tree[i]=(Tree[i]+val)%mo,i+=i & -i);
}
LL Pretot(int i)
{
LL res;
for(res=0;i;res=(Tree[i]+res)%mo,i-=i & -i);
return res;
}
void Solve()
{
int i,res,ans=0;
scanf("%d",&n);
for(i=1;i<=n;++i){
scanf("%d",&a[i].val);
a[i].i=i;
}
sort(a+1,a+1+n,CMP);
for(i=1;i<=n;++i){
if(a[i].val==a[i-1].val)
a[i].hash=a[i-1].hash;
else a[i].hash=a[i-1].hash+1;
}
sort(a+1,a+1+n);
for(i=1;i<=n;++i){
res=(Pretot(a[i].hash)*a[i].val%mo+a[i].val)%mo;
res-=dec[a[i].hash];
res+= res<0?mo:0;
dec[a[i].hash]=(dec[a[i].hash]+res)%mo;;
ans=(0LL+ans+res)%mo;
Update(a[i].hash,res);
}
printf("%d\n",ans);
}
int main()
{
Solve();
return 0;
}
3. Leego 的最长上升子序列
最长上升子序列 定义略。
有一天,Leego 拿到了一个序列,他知道一个序列的最长上升子序列可以有多个,所以它把
这个序列的每个下标i 分成了3 类:
1. i a 不属于任何最长上升子序列
2. i a 属于至少一个但非所有最长上升子序列
3. i a 属于所有最长上升子序列
想在Leego 请你帮助他,写一个程序,将所有下标i 分类。
【输入】
第1 行:1 个整数n,表示序列的长度
第2 行:n 个用空格分开的整数,表示序列的元素
【输出】
输出一个含n 个字符的字符串,第i 个字符表示下标i 的类型。
【输入输出样例1】
1
4
3
【输入输出样例2】
4
1 3 2 5
3223
【输入输出样例3】
4
1 5 2 3
3133
分析:
此题可以顺着跑一遍 然后倒着跑一遍。
然后判断是否同时出现过。