CSU1592 石子归并
题意:
现在有n堆石子,第i堆有ai个石子。现在要把这些石子合并成一堆,每次只能合并相邻两个,每次合并的代价是两堆石子的总石子数。求合并所有石子的最小代价。
(n<=100)
思路:
1.从小到大枚举区间长度
2.枚举区间起点(起点+长度可求出终点)
3.枚举区间分割点
4.取min
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=105;
const int inf=0x3f3f3f3f;
int d[maxm][maxm];//d[i][j]表示合并区间[i,j]的最小代价
int sum[maxm];
int a[maxm];
int n;
signed main(){
int T;
cin>>T;
while(T--){
memset(d,inf,sizeof d);
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
d[i][i]=0;
}
for(int i=1;i<=n;i++){//前缀和,用于快速计算区间和
sum[i]=sum[i-1]+a[i];
}
for(int len=2;len<=n;len++){//枚举区间长度
for(int i=1;i<=n;i++){//枚举起点
int j=i+len-1;//终点
if(j>n)break;
for(int k=i;k<=j-1;k++){//枚举分割点k,合并d[i][k]和d[k+1][j];
d[i][j]=min(d[i][j],d[i][k]+d[k+1][j]+sum[j]-sum[i-1]);
}
}
}
cout<<d[1][n]<<endl;
}
return 0;
}
hdu 3506 Monkey Party(环形石子归并+四边形不等式优化)
题意:
现在有n堆石子围成一圈,第i堆有ai个石子。现在要把这些石子合并成一堆,每次只能合并相邻两个,每次合并的代价是两堆石子的总石子数。求合并所有石子的最小代价。
(n<=1000)
思路:
环形石子归并
把n复制一遍扩展到2n即可
但是n很大,O(n3)已经不能满足要求了,所以需要利用四边形不等式优化
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=2e3+5;
int d[maxm][maxm];//d[i][j]表示合并区间[i,j]的最小代价
int s[maxm][maxm];//s[i][j]表示合并区间[i,j]的最优分割点k
int sum[maxm];
int a[maxm];
int n;
signed main(){
int n;
while(cin>>n){
for(int i=1;i<=n+n;i++){//初始化为inf
for(int j=i+1;j<=n+n;j++){
d[i][j]=1e9;
}
}
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
a[i+n]=a[i];
}
for(int i=1;i<=n+n;i++){
d[i][i]=0;
s[i][i]=i;
sum[i]=sum[i-1]+a[i];
}
for(int len=2;len<=n;len++){
for(int i=1;i<=n+n;i++){
int j=i+len-1;
if(j>n+n)break;
for(int k=s[i][j-1];k<=s[i+1][j];k++){
int temp=d[i][k]+d[k+1][j]+sum[j]-sum[i-1];
if(d[i][j]>temp){
d[i][j]=temp;
s[i][j]=k;
}
}
}
}
int ans=1e9;
for(int i=1;i<=n;i++){
ans=min(ans,d[i][i+n-1]);
}
printf("%d\n",ans);
}
return 0;
}
poj3186 Treats for the Cows
题意:
给n个数字,每次可以取出最左边或者最右边的数字,假设第i次取的数字是x,则可以获得i*x的收益,问取完所有数的最大收益。
思路:
令d(i,j)表示取完区间[i,j]的最大收益
每一次都只有取左边和取右边两种:
1.取左边:d(i+1,j)+value1
2.取右边:d(i,j-1)+value2
取max就行了
从小区间到大区间递推即可
code:
#include<cstdio>
#include<iostream>
using namespace std;
const int maxm=2e3+5;
int d[maxm][maxm];//d[i][j]表示区间[i,j]全部取出的最大收益
int a[maxm];
signed main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
d[i][i]=a[i]*n;//只有一个的时候,最大值为最后一天卖出去的价格a[i]*n
}
for(int len=2;len<=n;len++){
for(int i=1;i<=n;i++){
int j=i+len-1;
if(j>n)break;
d[i][j]=max(d[i+1][j]+(n-len+1)*a[i],d[i][j-1]+(n-len+1)*a[j]);
}
}
cout<<d[1][n]<<endl;
return 0;
}
poj2955 Brackets(括号匹配)
题意:
给一个括号字符串
问最多匹配多少组括号
思路:
合并两个小区间时:
1.强行合并,直接把两个小区间的匹配数相加
2.合并之前判断大区间两端点能否匹配,但是如果两端点可以匹配不一定就比强行合并的更优,
例如:序列1:"()",序列2:"()"
序列1的左括号和序列2的右括号可以匹配,这时候总匹配数为“)”和“(”的匹配数+1,
显然不比序列1和序列2直接合并的匹配数2更优,不过某些情况下还是能更优的,例如"((“和”])"
因此考虑第二种情况的时候第一种情况也要计算(刚开始我没考虑全)
code:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxm=105;
char s[maxm];
int d[maxm][maxm];
bool check(int i,int j){//判断是否是一对括号
if(s[i]=='['&&s[j]==']')return 1;
if(s[i]=='('&&s[j]==')')return 1;
return 0;
}
signed main(){
while(scanf("%s",s+1)!=EOF){
if(s[1]=='e')break;
int n=strlen(s+1);
memset(d,0,sizeof d);
for(int len=2;len<=n;len++){
for(int i=1;i<=n;i++){
int j=i+len-1;
if(j>n)break;
if(check(i,j)){//如果两端匹配
d[i][j]=d[i+1][j-1]+1;
}
for(int k=i;k<j;k++){//强行合并两个区间
d[i][j]=max(d[i][j],d[i][k]+d[k+1][j]);
}
}
}
cout<<d[1][n]*2<<endl;
}
return 0;
}
poj3280 Cheapest Palindrome(修改为回文串的最小代价)
题意:
给定一个字符串S,字符串S的长度为M(M≤2000),字符串S所含有的字符的种类的数量为N(N≤26),然后给定这N种字符Add与Delete的代价,求将S变为回文串的最小代价和。
思路:
设d(i,j)为区间[i,j]变成回文串的最小代价
1.d(i,j)从d(i+1,j)转移来,这时候可以删除左边的s[i],或右边添加一个s[i],因此花费为add和del的最小值
2.d(i,j)从d(i,j-1)转移来,这时候可以删除右边的s[j],或左边添加一个s[j],因此花费为add和del的最小值
可以发现:一个字符修改的代价就是add和del的最小值,因此只需要记录两者的最小值
code:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int maxm=2e3+5;
char s[maxm];
int d[maxm][maxm];
int cost[26];
signed main(){
int n,m;
scanf("%d%d",&n,&m);//n种字符,字符串长度为m
scanf("%s",s+1);
for(int i=1;i<=n;i++){
char t[2];
scanf("%s",t);
int v=t[0]-'a';
int x,y;
scanf("%d%d",&x,&y);
cost[v]=min(x,y);
}
for(int i=1;i<=m;i++){
for(int j=i+1;j<=m;j++){
d[i][j]=1e9;
}
}
for(int len=2;len<=m;len++){
for(int i=1;i<=m;i++){
int j=i+len-1;
if(j>m)break;
if(s[i]==s[j]){
d[i][j]=d[i+1][j-1];
}else{
d[i][j]=min(d[i][j],d[i+1][j]+cost[s[i]-'a']);//从[i+1,j]转移
d[i][j]=min(d[i][j],d[i][j-1]+cost[s[j]-'a']);//从[i,j-1]转移
}
}
}
printf("%d\n",d[1][m]);
return 0;
}
LightOJ1422 Halloween Costumes
题意:
小灰灰参加圣诞节的一些派对,并且需要穿上对应派对的衣服,所以他需要多次换衣服,为了方便,他可以选择脱掉一些衣服或者穿上新衣服,比如说,他穿着超人的衣服,外面又穿着死侍的衣服,当他要参加超人服装派对时,他可以选择脱掉死侍的衣服(因为死侍衣服的里面有超人的衣服),或者他可以在穿一件超人的衣服,小灰灰是个爱干净的人,当他脱下死侍的衣服后,如果需要再穿死侍的衣服,他会选择再穿一件新的。(如果他先穿A衣服,又穿上B衣服,再穿一件C衣服,如果他想让最外面的衣服是A,他可以选择直接穿一件A,或者先把C脱掉,再把B脱掉)。
输入n,然后给n个派对需要的衣服(从左到右,顺序不能改变)
思路:
d(i,j)表示[i,j]需要的最少衣服数量
最差的结果是每天都换一件新的,即d(i,j)=j-i+1
思考如何才能少掉几件
发现当a[i]==a[j]的时候,第i天穿的不脱下来,第j天就能用上,这个情况d(i,j)=d(i,j-1)
否则枚举分割点强行合并区间取min
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=205;
int d[maxm][maxm];
int a[maxm];
signed main(){
int T;
cin>>T;
int cas=1;
while(T--){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){//初始化为每天都换一件新的
for(int j=i;j<=n;j++){
d[i][j]=j-i+1;
}
}
for(int len=2;len<=n;len++){
for(int i=1;i<=n;i++){
int j=i+len-1;
if(j>n)break;
if(a[i]==a[j]){//第i天和第j天相同
d[i][j]=d[i][j-1];
}else{
for(int k=i;k<j;k++){
d[i][j]=min(d[i][j],d[i][k]+d[k+1][j]);
}
}
}
}
printf("Case %d: ",cas++);
cout<<d[1][n]<<endl;
}
return 0;
}
hdu2476 String painter
题意:
有两个长度相等,都只由小写字母构成的字符串A和B.。有一把刷子,可以把字符串中连续的一段区间刷成一个相同的字符,例如,对于字符串"vandarkholme",把区间[3,6]刷成’d’,可以变成"vanddddholme"。把字符串A变成B最少要刷多少次?
思路:
似乎是bzoj1260的加难版
先区间dp计算出空串刷成B的次数
然后再计算A刷成B的次数
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=105;
int d[maxm][maxm];
char s[maxm];
char t[maxm];
int ans[maxm];
signed main(){
while(scanf("%s%s",s+1,t+1)!=EOF){
int n=strlen(s+1);
//计算空字符串转化为T
for(int i=1;i<=n;i++){
d[i][i]=1;
}
for(int len=2;len<=n;len++)
for(int i=1;i<=n;i++){
int j=i+len-1;
if(j>n)break;
d[i][j]=d[i+1][j]+(t[i]!=t[j]);//如果t[i]==t[j],则一次就能一起刷完
for(int k=i+1;k<j;k++){
d[i][j]=min(d[i][j],d[i+1][k]+d[k+1][j]+(t[i]!=t[k]));
}
}
}
//计算S转化为T
ans[1]=(s[1]!=t[1]);
for(int i=2;i<=n;i++){
ans[i]=d[1][i];
if(s[i]==t[i]){
ans[i]=ans[i-1];
}else{
for(int j=1;j<i;j++){
ans[i]=min(ans[i],ans[j]+d[j+1][i]);
}
}
}
cout<<ans[n]<<endl;
}
return 0;
}
Acwing284 金字塔
题意:
给一个字符串,问该字符串是多少种形态的树的dfs序,答案取模1e9
思路:
n个点的树一共有(n-1)条边,每条边下去的时候输出一个字母,回来的时候输出一个字母,加上最开始的根节点字母
那么n个点的树的dfs序一定是长度为2(n-1)+1即(2n-1)的字符串。
1.如果给定字符串是偶数长度,则一定无解。
2.d(i,j)表示区间[i,j]字符串作为dfs序的树的形态数量
枚举分割点k,表示s(i)进去s(k)出来,s(k)进去s(j)出来,(k+1)-(j-1)是最后一颗子树的序列,
(i+1)-(k-1)是除了最后一颗子树的序列(可以为空),i,k,j都是树根(需要满足s(i)=s(k)=s(j))。
因为满足乘法原理,所以方案数d(i,j)+=d(i,k)*d(k+1,j-1),遍历完所有k即可计算出区间内的所有方案
code:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<map>
using namespace std;
#define int long long
const int maxm=305;
const int mod=1e9;
char s[maxm];
int d[maxm][maxm];
signed main(){
scanf("%s",s+1);
int n=strlen(s+1);
if(n%2==0){//dfs序一定是2n+1个字符
cout<<0<<endl;
}else{
for(int i=1;i<=n;i++){//init
d[i][i]=1;
}
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]){//首尾相同才是dfs序
for(int k=i;k<j;k+=2){
if(s[k]==s[j]){//s[i]进去s[k]回来,s[k]进去s[j]回来,最后一棵子树区间为[k+1,j-1]
d[i][j]=(d[i][j]+d[i][k]*d[k+1][j-1])%mod;
}
}
}
}
}
cout<<d[1][n]<<endl;
}
return 0;
}
CodeForces607 B. Zuma
题意:
给一个长度为n的数组,每次操作可以选择一段回文子区间删除,然后把剩下的部分拼接在一起。
问最少多少次操作可以删掉整个数组
思路:
d(i,j)表示删掉区间[i,j]的最少次数
如果a(i)==a(j),则d(i,j)=d(i+1,j-1)
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=505;
int d[maxm][maxm];
int a[maxm];
signed main(){
int n=re;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
d[i][j]=j-i+1;
}
}
for(int len=2;len<=n;len++){
for(int i=1;i<=n;i++){
int j=i+len-1;
if(j>n)break;
if(len==2){
if(a[i]==a[j]){
d[i][j]=1;
}
continue;
}
if(a[i]==a[j]){
d[i][j]=d[i+1][j-1];
}
for(int k=i;k<j;k++){
d[i][j]=min(d[i][j],d[i][k]+d[k+1][j]);
}
}
}
cout<<d[1][n]<<endl;
return 0;
}
CodeForces1213 E. Array Shrinking
题意:
给一个长度为n的数组a
如果数组中存在两个位置i,j,abs(i-j)=1,且a(i)=a(j),那么可以将a(i)和a(j)合并为一个数a(i)+1
现在你可以任意进行操作的位置和顺序,问最后数组中最少剩下多少个数
思路:
d(i,j)表示[i,j]可以合并为那个数,如果不能合并则为0
用O(n3)的区间dp计算出d数组
f(i)表示前i个数最少剩下多少个数
再用O(n2)的dp配合d数组计算出f数组,f(n)就是答案
详见代码
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=505;
int d[maxm][maxm];//d[i][j]表示[i,j]可以合并成那个数,如果不能合并则为0
int f[maxm];//f[i]表示前i个数最少能分成几个数
int a[maxm];
signed main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
d[i][i]=a[i];
}
for(int len=2;len<=n;len++){
for(int i=1;i<=n;i++){
int j=i+len-1;
if(j>n)break;
d[i][j]=0;
for(int k=i;k<j;k++){
if(d[i][k]&&d[k+1][j]&&d[i][k]==d[k+1][j]){
d[i][j]=d[i][k]+1;
break;
}
}
}
}
if(d[1][n]){
cout<<1<<endl;
}else{
for(int i=1;i<=n;i++){
f[i]=f[i-1]+1;
for(int j=0;j<i;j++){
if(!d[j+1][i])continue;
f[i]=min(f[i],f[j]+1);
}
}
cout<<f[n]<<endl;
}
return 0;
}
CodeForces1232 F. Clear the String
题意:
给一个长度为n的字符串,每次能删除一个子串,前提是子串的字符相同,删除之后将剩下部分拼接在一起
问最少多少次删掉整个串
思路:
d(i,j)为删除[i,j]的最少次数
1.s(i)与s(j)相同,那么d(i,j)=min(d(i+1,j),d(i,j-1)),因为s(i)=s(j),再删除s(i)的时候一定可以顺便删除s(j),删除s(j)的时候可以也可以删除s(i),所以取min即可
2.s(i)与s(j)不同,那么d(i,j)=min(d(i+1,j),d(i,j-1))+1
3.枚举分割点k,d(i,j)=min(d(i,j),d(i,k)+d(k+1,j))
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=505;
int d[maxm][maxm];
char s[maxm];
int n;
signed main(){
cin>>n;
scanf("%s",s+1);
for(int i=1;i<=n;i++)d[i][i]=1;
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])d[i][j]=min(d[i][j-1],d[i+1][j]);
else d[i][j]=min(d[i][j-1],d[i+1][j])+1;
for(int k=i;k<j;k++){
d[i][j]=min(d[i][j],d[i][k]+d[k+1][j]);
}
}
}
cout<<d[1][n]<<endl;
return 0;
}
UVA10617 Again Palindrome
题意:
给定字符串,问有多少种删除字符的方法,使得删完之后的串是回文串
解法:
设d[i][j]为[i,j]的方案数
如果s[i]==s[j],那么要统计d[i+1][j]+d[i][j-1]
不过这样会把d[i+1][j-1]统计两次,
但是d[i+1][j-1]中的回文串可以加上s[i]和s[j]组成d[i+1][j-1]个回文串
因此d[i][j]=d[i+1][j]+d[i][j-1]+1;
否则d[i][j]=d[i+1][j]+d[i][j-1]-d[i+1][j-1];
code:
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
#define int long long
const int maxm=105;
int d[maxm][maxm];
char s[maxm];
int dp(int i,int j){
if(i>j)return 0;
if(i==j)return 1;
if(d[i][j]!=-1)return d[i][j];
if(s[i]==s[j])return d[i][j]=dp(i+1,j)+dp(i,j-1)+1;
else return d[i][j]=dp(i+1,j)+dp(i,j-1)-dp(i+1,j-1);
}
signed main(){
int T;cin>>T;
while(T--){
scanf("%s",s+1);
int n=strlen(s+1);
memset(d,-1,sizeof d);
cout<<dp(1,n)<<endl;
}
return 0;
}