前言:
- 区间dp的题目一般有几个显然的模式,比如n的范围小于1000,且通过手动模拟发现没法贪心的题。
- 对于dp方程一般可以往 对区间内的贡献或者是对区间外的贡献这两个角度去考虑。(有时需要逆向思维去考虑)
- 对于dp状态的增加维,一般用于处理区间边界的状态。
- 做区间dp的题经常会用到前缀和来预处理状态之间转移的关系。
正文:
A ZOJ-3537
题解:
这题可以划分为两个问题,一个是判断凸多边形,另一个是区间dp求解最优划分三角形。
几何部分
套用模板(模板大致思路就是维护一个类似单调栈的东西分别求下凸壳以及上凸壳,求出的ch数组为逆时针序的多边形)
dp部分
dp[i][j]
表示该区间内的点连成的多边形分割成三角形的最小花费。
不难想到一种分割dp[i][j]=dp[i][k]+dp[k][j]+cost(i,k)+cost(j,k)
也就是对与区间i-j中取出一个点进行分割区间发现会多出来 i k 、 j k ik、jk ik、jk两条边。
代码:
#include <map>
#include <set>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <ctime>
#include <string>
#include <vector>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
#define PB push_back
#define MP make_pair
#define INF 1073741824
#define inf 1152921504606846976
#define pi 3.14159265358979323846
//#pragma comment(linker,"/STACK:10240000,10240000")
const int N=3e5+7,M=2e6;
const long long mod=1e9+7;
inline int read(){
int ret=0;char ch=getchar();bool f=1;for(;!isdigit(ch);ch=getchar()) f^=!(ch^'-');for(;isdigit(ch);ch=getchar()) ret=(ret<<1)+(ret<<3)+ch-48;return f?ret:-ret;}
ll gcd(ll a,ll b){
return b?gcd(b,a%b):a;}
ll ksm(ll a,ll b,ll mod){
int ans=1;while(b){
if(b&1) ans=(ans*a)%mod;a=(a*a)%mod;b>>=1;}return ans;}
ll inv2(ll a,ll mod){
return ksm(a,mod-2,mod);}//ÄæÔª
//int head[N],NEXT[N],ver[N],tot;void link(int u,int v){ver[++tot]=v;NEXT[tot]=head[u];head[u]=tot;}
const db EPS=1e-9;
inline int sign(db a){
return a<-EPS?-1:a>EPS;}
inline int cmp(db a,db b){
return sign(a-b);}
#define cross(p1,p2,p3) ((p2.x-p1.x)*(p3.y-p1.y)-(p3.x-p1.x)*(p2.y-p1.y))
#define ocross(p1,p2,p3) sign(cross(p1,p2,p3))
struct P{
ll x,y;
P(){
}
P(ll _x,ll _y) : x(_x), y(_y){
}
P operator+(P p){
return {
x+p.x,y+p.y};}
P operator-(P p){
return {
x-p.x,y-p.y};}
P operator*(ll d){
return{
x*d,y*d};}
P operator/(ll d){
return{
x/d,y/d};}
bool operator<(P p)const {
int c=cmp(x,p.x);
if(c) return c==-1;
return cmp(y,p.y)==-1;
}
bool operator==(P o)const {
return cmp(x,o.x)==0&&cmp(y,o.y)==0;
}
};
P p[340],ch[340];
ll dp[340][340];
int n;
ll m;
ll Abs(ll x){
if(x<0) return -x;
return x;
}
ll det(ll a,ll b,ll c,ll d){
return a*d-b*c;
}
ll cost(int x,int y){
//cout<<Abs(p[x].x+p[y].x)*Abs(p[x].y+p[y].y)%m<<endl;
return (Abs(ch[x].x+ch[y].x)*Abs(ch[x].y+ch[y].y))%m;
}
bool cmp_sort(P a,P b){
if(a.x==b.x){
return a.y<b.y;
}
return a.x<b.x;
}
int ConvexHull(){
sort(p,p+n);
int cnt = 0;
for(int i = 0; i < n; i++){
while(cnt > 1 && ocross(ch[cnt-2],ch[cnt-1],p[i])<=0) cnt--;
ch[cnt++] = p[i];
}
int k = cnt;
for(int i = n-2; i >= 0; i--){
while(cnt > k && ocross(ch[cnt-2],ch[cnt-1],p[i])<=0) cnt--;
ch[cnt++] = p[i];
}
if(n > 1) cnt--;
return cnt;
}
int main(){
//freopen("1.txt","r",stdin);
while(~scanf("%d%lld",&n,&m)){
for(int i=0;i<n;i++){
scanf("%lld%lld",&p[i].x,&p[i].y);
}
int mark;
if(ConvexHull()<n){
puts("I can't cut.");
continue;
}
memset(dp,0x3f,sizeof(dp));
for(int i=0;i<n;i++){
dp[i][i]=0;
dp[i][(i+1)%n]=0;
}
for(int i=2;i<n;i++){
for(int j=i-2;j>=0;j--){
//cout<<i<<' '<<j<<endl;
for(int k=j+1;k<i;k++){
//cout<<k<<' '<<i<<' '<<j<<endl;
dp[j][i]=min(dp[j][i],dp[j][k]+dp[k][i]+(k-j>=2?cost(j,k):0)+(i-k>=2?cost(i,k):0));
}
}
}
printf("%lld\n",dp[0][n-1]);
}
//cout << "time: " << (long long)clock() * 1000 / CLOCKS_PER_SEC << " ms" << endl;
return 0;
}
LightOj-1422
题解:
可以把穿衣脱衣的过程看出一个栈,之后又可以想象成区间的覆盖,这样就可以想到一种很套路的区间dp转移方程。即若a[i]==a[j] dp[i][j]=min(dp[i+1][j],dp[i][j-1])
之后加上区间合并的操作即可。
代码:
#include <map>
#include <set>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <ctime>
#include <string>
#include <vector>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
#define PB push_back
#define MP make_pair
#define INF 1073741824
#define inf 1152921504606846976
#define pi 3.14159265358979323846
//#pragma comment(linker,"/STACK:10240000,10240000")
const int N=3e5+7,M=2e6;
const long long mod=1e9+7;
inline int read(){
int ret=0;char ch=getchar();bool f=1;for(;!isdigit(ch);ch=getchar()) f^=!(ch^'-');for(;isdigit(ch);ch=getchar()) ret=(ret<<1)+(ret<<3)+ch-48;return f?ret:-ret;}
ll gcd(ll a,ll b){
return b?gcd(b,a%b):a;}
ll ksm(ll a,ll b,ll mod){
int ans=1;while(b){
if(b&1) ans=(ans*a)%mod;a=(a*a)%mod;b>>=1;}return ans;}
ll inv2(ll a,ll mod){
return ksm(a,mod-2,mod);}//逆元
//int head[N],NEXT[N],ver[N],tot;void link(int u,int v){ver[++tot]=v;NEXT[tot]=head[u];head[u]=tot;}
int dp[120][120];
int a[120];
int main(){
//freopen("1.txt","r",stdin);
int t;
int ti=0;
int n;
scanf("%d",&t);
while(t--){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n;i++){
dp[i][i]=1;
}
for(int i=1;i<=n;i++){
for(int j=i-1;j>=1;j--){
if(a[i]==a[j]){
dp[j][i]=min(dp[j+1][i],dp[j][i-1]);
}
for(int k=j;k<i;k++){
dp[j][i]=min(dp[j][i],dp[j][k]+dp[k+1][i]);
}
}
}
printf("Case %d: %d\n",++ti,dp[1][n]);
}
//cout << "time: " << (long long)clock() * 1000 / CLOCKS_PER_SEC << " ms" << endl;
return 0;
}
POJ-2955
题解:
简单的括号匹配问题,与上题类似,处理好头尾状态之后区间合并即可
代码:
#include <map>
#include <set>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <ctime>
#include <string>
#include <vector>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
#define PB push_back
#define MP make_pair
#define INF 1073741824
#define inf 1152921504606846976
#define pi 3.14159265358979323846
//#pragma comment(linker,"/STACK:10240000,10240000")
const int N=3e5+7,M=2e6;
const long long mod=1e9+7;
inline int read(){
int ret=0;char ch=getchar();bool f=1;for(;!isdigit(ch);ch=