DP——由蒟蒻到神犇的进阶之路

开始更新咯

DP专题【题目来源BZOJ】

一、树形DP

1.bzoj2286消耗战

题解:因为是树形结构,一个点与根节点不联通,删一条边即可,

   于是我们就可以简化这棵树,把有用的信息建立一颗虚树,然后开始DP即可

  1 /*
  2 思路: 
  3 */
  4 #include<algorithm>
  5 #include<cstdio>
  6 #include<cmath>
  7 #include<iostream>
  8 #include<cstring>
  9 #define ll long long 
 10 #define inf 1e60
 11 #define N 250005    
 12 #define M 550005
 13 using namespace std;
 14 int n,m,k,tot,top,tmp,ind;
 15 int a[N];
 16 int now[N],pre[M],v[M],val[M];
 17 int now1[N],pre1[M],v1[M];
 18 int fa[N][22],deep[N],dfn[N],bin[22];
 19 int z[M],st[M];
 20 ll f[N],mx[N];
 21 int read()
 22 {
 23     int x=0,f=1; char ch; 
 24     while (ch=getchar(),ch<'0'||ch>'9') if (ch=='-') f=-1;
 25     while (x=x*10+ch-'0',ch=getchar(),ch>='0'&&ch<='9'); 
 26     return x*f;
 27 }
 28 void ins(int a,int b,int c){
 29     ++tot; pre[tot]=now[a]; now[a]=tot; v[tot]=b; val[tot]=c;
 30 }
 31 void insert(int a,int b){
 32     if (a==b) return; 
 33     ++tot; pre1[tot]=now1[a]; now1[a]=tot; v1[tot]=b;
 34 }  
 35 void dfs(int x)
 36 {
 37     dfn[x]=++ind;
 38     for (int i=1; bin[i]<=deep[x]; i++)
 39         fa[x][i]=fa[fa[x][i-1]][i-1];
 40     for (int p=now[x]; p; p=pre[p])
 41     {
 42         int son=v[p];
 43         if (son==fa[x][0]) continue;
 44         deep[son]=deep[x]+1;
 45         mx[son]=min(mx[x],(ll)val[p]);
 46         fa[son][0]=x;
 47         dfs(son);
 48     }        
 49 }
 50 int lca(int x,int y)
 51 {
 52     if(deep[x]<deep[y]) swap(x,y);
 53     int t=deep[x]-deep[y];
 54     for (int i=0; bin[i]<=t; i++) 
 55     if (bin[i]&t) x=fa[x][i];
 56     for (int i=19; i>=0; i--)
 57         if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
 58     if (x==y) return x; return fa[x][0];
 59 }
 60 void prework()
 61 {
 62     bin[0]=1;
 63     for (int i=1; i<=21; i++) bin[i]=bin[i-1]*2;
 64      tmp=0; mx[1]=inf; dfs(1);
 65 }
 66 bool cmp(int a,int b){return dfn[a]<dfn[b];
 67 }
 68 void work()
 69 {
 70     sort(a+1,a+k+1,cmp);
 71     top=tot=0;
 72     z[1]=a[1]; top++;
 73     for (int i=2; i<=k; i++) if (lca(z[top],a[i])!=z[top]) z[++top]=a[i];
 74     st[1]=1; tmp=1;
 75     for (int i=1; i<=top; i++)
 76     {
 77         int now=z[i],f=lca(now,st[tmp]);
 78         for (;;)
 79         {
 80             if (deep[f]>=deep[st[tmp-1]])
 81             {
 82                 insert(f,st[tmp--]);
 83                 if (st[tmp]!=f) st[++tmp]=f; 
 84                 break;
 85             }
 86             insert(st[tmp-1],st[tmp]); tmp--;
 87         }
 88         if (st[tmp]!=now) st[++tmp]=now;
 89      }
 90      while (--tmp) insert(st[tmp],st[tmp+1]);
 91 }
 92 void dp(int x)
 93 {
 94     f[x]=mx[x]; ll sum=0;
 95     for (int p=now1[x]; p; p=pre1[p]) 
 96     {
 97         int son=v1[p]; dp(son); sum+=f[son];
 98     }
 99     now1[x]=0;
100     if (sum==0) f[x]=mx[x]; else f[x]=min(f[x],sum);
101 }
102 void init()
103 {
104     n=read();
105     for (int i=1; i<n; i++)
106     {
107         int u=read(),v=read(),val=read();
108         ins(u,v,val); ins(v,u,val);
109     }
110 //    m=read();
111     prework();
112 }
113 void solve()
114 {
115     m=read();
116     for (int i=1; i<=m; i++) 
117     {
118         k=read(); for (int j=1; j<=k; j++) a[j]=read(); work();
119         dp(1); printf("%lld
",f[1]);
120     }
121 }
122 int main()
123 {
124     init();
125     solve();
126     return 0;
127 }
View Code

2.bzoj1827奶牛大集会

题解:树形DP,先以1号点为根节点,然后再次树形DP求出其他节点为根的答案即可

 1 #include<algorithm>
 2 #include<iostream>
 3 #include<cstring>
 4 #include<cmath>
 5 #include<cstdio>
 6 #define ll long long 
 7 #define inf 1e60
 8 #define N  200005
 9 #define M  500005
10 using namespace std;
11 int n,m;
12 int now[N],pre[M],v[M],val[M],tot;
13 ll f[N];
14 int size[N],num[N],dis[N];
15 ll ans,sum;
16 void ins(int a,int b,int c)
17 {
18     ++tot; pre[tot]=now[a]; now[a]=tot; v[tot]=b; val[tot]=c;
19 }
20 int read()
21 {
22     int x=0,f=1; char ch; 
23     while (ch=getchar(),ch<'0'||ch>'9') if (ch=='-') f=-1;
24     while (x=x*10+ch-'0',ch=getchar(),ch>='0'&&ch<='9');
25     return x*f; 
26 }
27 void init()
28 {
29     n=read(); 
30     for (int i=1; i<=n; i++) num[i]=read(),sum+=num[i];
31     for (int i=1; i<n; i++)
32     {
33         int u=read(),v=read(),val=read();
34         ins(u,v,val); ins(v,u,val);
35     }
36 }
37 void dfs(int x,int fa)
38 {
39     size[x]=num[x];
40     for (int p=now[x]; p; p=pre[p])
41     {
42         int son=v[p];
43         if (son==fa) continue;
44         dis[son]=dis[x]+val[p];
45         f[son]=1ll*dis[son]*num[son];
46         dfs(son,x);
47         size[x]+=size[son];
48         f[x]+=f[son];
49     }
50 }
51 void treedp(int x,int fa)
52 {
53     for (int p=now[x]; p; p=pre[p])
54     {
55         int son=v[p];
56         if (son==fa) continue;
57         f[son]=f[x]-1ll*size[son]*val[p]+1ll*(sum-size[son])*val[p];
58         treedp(son,x);
59     }
60 }
61 void solve()
62 {
63     dfs(1,0);
64     //cout<<" "<<sum<<endl;
65     treedp(1,0);
66     ans=inf;
67     for (int i=1; i<=n; i++) ans=min(ans,f[i]);
68     printf("%lld
",ans);
69 }
70 int main()
71 {
72     init(); 
73     solve();
74 }
75 /*
76     
77 */
View Code

3.bzoj1596电话网络

题解:树形DP,f[i][1]代表以i为根的子树都被覆盖的代价且在i放,[0]为都被覆盖i不放,

   [2]为子树被覆盖,i没被覆盖的代价

   f[i][1]=min(f[son][0],f[son][1],f[son][2])+1;

   f[i][0]=最小的f[son][1]+min(f[son][0],f[son][1]);

   f[x][2]+=f[son][0];

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
#define ll long long 
#define inf 100000000
#define N  10005
#define M  30005
using namespace std;
int n;
int pre[M],v[M],now[N],tot;
int f[N][3];
void ins(int a,int b){++tot; pre[tot]=now[a]; now[a]=tot; v[tot]=b;
}
int read()
{
    int x=0,f=1; char ch; 
    while (ch=getchar(),ch<'0'||ch>'9') if (ch=='-') f=-1;
    while (x=x*10+ch-'0',ch=getchar(),ch>='0'&&ch<='9');
    return x*f; 
}
void dfs(int x,int fa)
{
    int sum=0;
    f[x][1]=1; f[x][0]=inf;
    for (int p=now[x]; p; p=pre[p])
    {
        int son=v[p];
        if (son==fa) continue;
        dfs(son,x);
        f[x][1]+=min(f[son][0],min(f[son][1],f[son][2]));
        f[x][2]+=f[son][0];
    }
    for (int p=now[x]; p; p=pre[p])
    {
        int son=v[p]; if (son==fa) continue;
        int who=son; sum=f[son][1];
        for (int j=now[x]; j; j=pre[j]) if ((v[j]!=fa) && (v[j]!=who)) sum+=min(f[v[j]][0],f[v[j]][1]);
        f[x][0]=min(f[x][0],sum);
    }
}
int main()
{
    n=read();
    for (int i=1; i<n; i++)
    {
        int u=read(),v=read(); 
        ins(u,v); ins(v,u);
    }
    dfs(1,0);
    printf("%d
",min(f[1][0],f[1][1]));
}
View Code

4.bzoj1864三色二叉树

题解:f[i][0]代表i为绿色的答案,f[i][1]为i为其他颜色的答案

   f[i][0]=max(f[ls[i]][0]+f[rs[i]][1],f[ls[i]][1]+f[rs[i]][0]);

   f[i][1]=f[ls[i]][0]+f[rs[i]][1]+1;

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdio>
#define ll long long 
#define inf 1000000000
#define N 300005
using namespace std;
int ls[N],rs[N];
int n,m,size,ans1,ans2;
int f[N][2];
void read(int x)
{
    char ch=getchar();
    if (ch=='0') return;
    if (ch=='1') ls[x]=++size,read(ls[x]);
    if (ch=='2') ls[x]=++size,read(ls[x]),rs[x]=++size,read(rs[x]);
}
void dp1(int x)
{
    if (!x) return ;
    dp1(ls[x]); dp1(rs[x]);
    f[x][1]=f[ls[x]][0]+f[rs[x]][0]+1;
    f[x][0]=max(f[ls[x]][0]+f[rs[x]][1],f[ls[x]][1]+f[rs[x]][0]);
}
void dp2(int x)
{
    if (!x) return;
    dp2(ls[x]); dp2(rs[x]);
    f[x][1]=f[ls[x]][0]+f[rs[x]][0]+1;
    f[x][0]=min(f[ls[x]][0]+f[rs[x]][1],f[ls[x]][1]+f[rs[x]][0]);
}
void solve()
{
    dp1(1); ans1=max(f[1][0],f[1][1]);
    memset(f,0,sizeof(f));
    dp2(1); ans2=min(f[1][0],f[1][1]);
    printf("%d %d
",ans1,ans2);
}
int main()
{
    read(++size);
    solve();
}
View Code

5.bzoj1060时态同步

题解:f[i]代表以i为子树的儿子时间同步所需长度 

   ans+=maxsonf-(f[son]+val[p])

   :val[p]为边权,maxsonf为i为子树的最长的长度

#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#define ll long long 
#define inf 0x7ffffff
#define N 500005
#define M 1000005
using namespace std;
int now[N],pre[M],v[M],val[M],f[N];
int n,m,s,tot;
ll ans;
int read()
{
    int x=0,f=1; char ch;
    while (ch=getchar(),ch<'0'||ch>'9') if (ch=='-') f=-1;
    while (x=x*10+ch-'0',ch=getchar(),ch>='0'&&ch<='9');
    return x*f;
}
void ins(int a,int b,int c)
{
    ++tot; pre[tot]=now[a]; now[a]=tot; v[tot]=b; val[tot]=c;
}
void work(int x,int fa)
{
    for (int p=now[x]; p; p=pre[p])
    {
        int son=v[p];
        if (son==fa) continue;
        work(son,x);
        f[x]=max(f[x],f[son]+val[p]);
    }
    for (int p=now[x]; p; p=pre[p])
    {
        int son=v[p];
        if (son==fa) continue;
        ans+=f[x]-(f[son]+val[p]);
    }
}
int main()
{
    n=read();
    s=read();
    for (int i=1; i<n; i++)
    {
        int u=read(),v=read(),val=read();
        ins(u,v,val); ins(v,u,val);
    }
    work(s,0); printf("%lld
",ans);
}
View Code

6.bzoj2435道路修建

题解:ans+=val[p]*size[i]*(n-size[i])  size[i]为i为根的子树大小

#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#define ll long long 
#define inf 0x7ffffff
#define N 1000005
#define M 2000005
using namespace std;
int now[N],pre[M],v[M],val[M],size[N];
int n,tot;
ll ans;

int read()
{
    int x=0,f=1; char ch;
    while (ch=getchar(),ch<'0'||ch>'9') if (ch=='-') f=-1;
    while (x=x*10+ch-'0',ch=getchar(),ch>='0'&&ch<='9');
    return x*f;
}
void ins(int a,int b,int c)
{
    ++tot; pre[tot]=now[a]; now[a]=tot; v[tot]=b; val[tot]=c;
}
void dfs(int x,int fa)
{
    size[x]=1;
    for (int p=now[x]; p; p=pre[p])
    {
        int son=v[p];
        if (son==fa) continue;
        dfs(son,x); size[x]+=size[son];
        ans+=1ll*val[p]*(abs(n-size[son]-size[son]));
    }
}
int main()
{
    n=read();
    for (int i=1; i<n; i++)
    {
        int u=read(),v=read(),val=read();
        ins(u,v,val); ins(v,u,val);
    }
    dfs(1,0);
    printf("%lld
",ans);
}
View Code

7.bzoj2427软件安装

题解:缩点+反边+dp【详见代码】

#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#define ll long long 
#define inf 2000000000000
#define N 205
#define M 605
using namespace std;
int n,m;
int cost[N],val[N],depend[N],belong[N];
struct data{
    int tot,pre[M],now[N],v[M];
}a,b;
int tot,dfn[N],low[N],z[N],id;
bool vis[N],in[N];
int sc[N],sv[N],type;
int f[N][M];
int read()
{
    int x=0,f=1; char ch;
    while (ch=getchar(),ch<'0'||ch>'9') if (ch=='-') f=-1;
    while (x=x*10+ch-'0',ch=getchar(),ch>='0'&&ch<='9');
    return x*f;
}
void ins1(int u,int v)
{
//    cout<<" "<<u<<" "<<v<<endl;
    ++a.tot; a.pre[a.tot]=a.now[u]; a.now[u]=a.tot; a.v[a.tot]=v;
}
void ins2(int u,int v)
{
    //cout<<" "<<u<<" "<<v<<endl;
    ++b.tot; b.pre[b.tot]=b.now[u]; b.now[u]=b.tot; b.v[b.tot]=v; 
}
void tarjan(int x){
    int son=0;
    dfn[x]=low[x]=++id;
    z[++tot]=x; vis[x]=true;
    for (int p=a.now[x]; p; p=a.pre[p])
    {
        son=a.v[p];
        if (!dfn[son])
        {
            tarjan(son); low[x]=min(low[x],low[son]);
        } else if (vis[son]) low[x]=min(low[x],dfn[son]);
    }
    if (low[x]==dfn[x])
    {
        ++type;
        while (son!=x) 
        {
            son=z[tot--]; vis[son]=false; belong[son]=type; 
            sv[type]+=val[son]; sc[type]+=cost[son];    
        }    
    }
}
void init_pre()
{
    n=read(); m=read();
    for (int i=1; i<=n; i++) cost[i]=read();
    for (int i=1; i<=n; i++) val[i]=read();
    for (int i=1; i<=n; i++)
    {
        int x=read();
        if(x)ins1(x,i);
    }
    for (int i=1; i<=n; i++)
    {
        if (!dfn[i]) tarjan(i);
    }
    for (int i=1; i<=n; i++)
    {
        for (int p=a.now[i]; p; p=a.pre[p])
        {
            int son=a.v[p];
            if (belong[son]!=belong[i]) ins2(belong[i],belong[son]),in[belong[son]]=true;
        }
    }
    for (int i=1; i<=type; i++)
    {
        if (!in[i]) ins2(type+1,i);
    }
    type++;
}
void dp(int x)
{
    for (int p=b.now[x]; p; p=b.pre[p])
    {
        int son=b.v[p];
        dp(son);
        for (int j=m-sc[x]; j>=0; j--) 
            for (int k=0; k<=j; k++) f[x][j]=max(f[x][j],f[x][j-k]+f[son][k]);
    }
    for (int j=m; j>=0; j--)
    {
        if (j>=sc[x]) f[x][j]=f[x][j-sc[x]]+sv[x]; else f[x][j]=0;
    }
}
void solve()
{
    dp(type);
    printf("%d
",f[type][m]);
}
int main()
{
    init_pre();
    solve();
}
View Code

8.bzoj1369GEM

题解:开始智障了,以为只有整棵树为1或2的情况,于是GG了,实践证明数值个数不会超过5.

#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#define ll long long 
#define inf 0x7ffffff
#define N 10005  
#define M 20005 
using namespace std;
int n,m,tot;
int pre[M],v[M],now[N];
int f[N][21];
int read()
{
    int x=0,f=1; char ch;
    while (ch=getchar(),ch<'0'||ch>'9') if (ch=='-') f=-1;
    while (x=x*10+ch-'0',ch=getchar(),ch>='0'&&ch<='9');
    return x*f;
}
void ins(int a,int b) {++tot; pre[tot]=now[a]; now[a]=tot; v[tot]=b;
}
void dp(int x,int fa)
{
    for (int i=1; i<=5; i++) f[x][i]=i;
    for (int p=now[x]; p; p=pre[p])
    {
        int son=v[p];
        if (son==fa) continue;
        dp(son,x);
    }
    for (int i=1; i<=5; i++)
    {
        for (int p=now[x]; p; p=pre[p])
        {
            int son=v[p]; if (son==fa) continue; int sum=inf;
            for (int j=1; j<=5; j++)
            {
                if (j==i) continue;
                sum=min(sum,f[son][j]);
            }
            f[x][i]+=sum;
        }
    }
}
void solve()
{
    dp(1,0); int ans=inf;
    for (int i=1; i<=5; i++) ans=min(ans,f[1][i]); printf("%d
",ans);
        
}
int main()
{
    n=read();
    for (int i=1; i<n; i++)
    {
        int u=read(),v=read(); ins(u,v); ins(v,u);
    }
    solve(); 
}
View Code

9.bzoj3573米特运输

题解:可以发现一个特点,当我们确定一个点的时候,整棵树都可以确定,于是我们枚举留下留哪一个点且记录根的值,n-一样的 即为答案

#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#define ll long long 
#define inf 0x7ffffff
#define N 500005
#define M 1000005
using namespace std;
int n;
int pre[M],v[M],now[N],a[N];
double s[N],val[N];
int in[N];
int tot,tmp,ans;
int read()
{
    int x=0,f=1; char ch;
    while (ch=getchar(),ch<'0'||ch>'9') if (ch=='-') f=-1;
    while (x=x*10+ch-'0',ch=getchar(),ch>='0'&&ch<='9');
    return x*f;
}
void ins(int a,int b)
{
    ++tot; pre[tot]=now[a]; now[a]=tot; v[tot]=b;
}
void dp(int x,int fa)
{
    for (int p=now[x]; p; p=pre[p])
    {
        int son=v[p];
        if (son==fa) continue;
        s[son]=s[x]+log(in[x]);
        dp(son,x);
    } 
}
int main()
{
    n=read();
    for (int i=1; i<=n; i++) a[i]=read();
    for (int i=1; i<n; i++)
    {
        int u=read(),v=read(); ins(u,v); ins(v,u);
        in[u]++; in[v]++;
    }
    for (int i=2; i<=n; i++) in[i]--;
    s[1]=log(1);
    dp(1,0);
    for (int i=1; i<=n; i++)
    {
        val[i]=s[i]+log(a[i]);
    }
    sort(val+1,val+n+1); ans=n*2; tmp=1;
    for (int i=2; i<=n; i++)
    {
        if (val[i]-val[i-1]<1e-5) tmp++;
        else tmp=1;
        ans=min(ans,n-tmp);
    }

    printf("%d
",ans);
}
View Code

10.bzoj3522Hotel

题解:因为我没妹子所以就不屑于写这种题【虐狗啊】

   枚举节点,以这个节点为中心,于是我们只要知道到这个节点的距离为J的有多少,然后组合排列的转换,搞一搞就好了

#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#define ll long long 
#define inf 0x7ffffff
#define N 5005
#define M 10005
using namespace std;
int pre[M],v[M],now[N];
int tmp[N],dis[N],s1[N],s2[N];
int tot,n,m,mx;
ll ans;
int read()
{
    int x=0,f=1; char ch;
    while (ch=getchar(),ch<'0'||ch>'9') if (ch=='-') f=-1;
    while (x=x*10+ch-'0',ch=getchar(),ch>='0'&&ch<='9');
    return x*f;
}
void ins(int a,int b){++tot; pre[tot]=now[a]; now[a]=tot; v[tot]=b;
}
void dp(int x,int fa)
{
    tmp[dis[x]]++; mx=max(mx,dis[x]);
    for (int p=now[x]; p; p=pre[p])
    {
        int son=v[p]; if (son==fa) continue;
        dis[son]=dis[x]+1;
        dp(son,x);            
    }
}
int main()
{
    n=read();
    for (int i=1; i<n; i++)
    {
        int u=read(),v=read();
        ins(u,v); ins(v,u);
    }
    ans=0;
    
    for (int i=1; i<=n; i++)
    {
        memset(s1,0,sizeof(s1));
        memset(s2,0,sizeof(s2));
        for (int p=now[i]; p; p=pre[p])
        {
            int son=v[p];
            mx=dis[son]=1; dp(son,i);
            for (int j=1; j<=mx; j++)
            {
                ans+=s2[j]*tmp[j];
                s2[j]+=tmp[j]*s1[j];
                s1[j]+=tmp[j];
                tmp[j]=0;
            }
        }
    }
    printf("%lld
",ans);
}
View Code

 小结:在树形DP过程中当我们不需要那些没有节点我们可以利用虚树来解决;

    树形DP一般是在一棵树上的,且状态较多,贪心无法解决的问题;

    树形DP一般可以看出,但是转移方程需要细想;

二、区间动规

1.bzoj1260涂色

题解:f[i][j]为i--j的涂色最小值

   f[i][j]=f[i+1][j]+a[i+1]==a[i]

       =f[i][j-1]+a[j-1]==a[j]

       =f[i+1][j-1](a[i]==a[j]==a[i+1]==a[j-1])

       =f[i+1][j-1]+(a[i]==a[j]) [ a[i+1]!=a[i]||a[j]!=a[j-1] ]

   或者:a[i]==a[j] 

      则:f[i][j]=min(f[i+1][j],f[i][j-1])

        f[i][j]=f[i+1][j-1]+1;

#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#define ll long long 
#define inf 0x7ffffff
#define N 100
using namespace std;
char s[N];
int f[N][N];
int n,ans;
void dp()
{
    for (int i=1; i<=n; i++) for (int j=1; j<=n; j++) f[i][j]=inf/2;
    for (int i=1; i<=n; i++) f[i][i]=1;
    for (int len=1; len<n; len++)
    {
        for (int i=1; i<=n; i++)
        {
            int j=i+len;
            if (j>n) break;
            if (s[i]==s[j])
            {
                if (len==1) f[i][j]=1;
                else
                {
                    f[i][j]=min(f[i+1][j],f[i][j-1]);
                    f[i][j]=min(f[i][j],f[i+1][j-1]+1);
                }
             }
             else
             {
                 for (int k=i; k<j; k++) f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);    
            }
        }
    }
}
int main()
{
    scanf("%s",s+1);
    n=strlen(s+1);
    dp();
    printf("%d
",f[1][n]);
}
View Code

2.bzoj1090字符串压缩

题解:f[i][j]---->i到j被压缩的最小长度;

   f[i][j]=min(f[i][k]+f[k+1][j]);

   same(i,j)代表i到j可以被压缩,【同下题解释】

   f[i][j]=i--j的最小段数的位数+2+i--j的最小段长;

   注:他们可以被压缩,但是我们不一定要全部压缩

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#define N 105
using namespace std;
char s[N];
int n;
bool mark[N][N];
int f[N][N];
bool same(int l,int r,int ll,int rr)
{
    if ((r-l+1)%(rr-ll+1)!=0) return false ;
    int tmp=(rr-ll+1);
    for (int i=l; i<=r; i++)
    {
        int tmp=rr-ll+1;
        if (s[i]!=s[(i-l)%tmp+ll]) return false;
    }
    return true;
}
int get(int x)
{
    int t=0;
    while (x) { x/=10; t++;
    } return t;
}
int dp(int l,int r)
{
    int tmp=(r-l+1);
    if (tmp==1) return tmp;
    if (mark[l][r]) return f[l][r];
    mark[l][r]=1;
    for (int i=l; i<r; i++)
    {
        tmp=min(tmp,dp(l,i)+dp(i+1,r));
        if (same(i+1,r,l,i)) tmp=min(tmp,dp(l,i)+2+get((r-i)/(i-l+1)+1));    
    }
    return f[l][r]=tmp;
}
int main()
{
    scanf("%s",s+1); n=strlen(s+1);
    printf("%d
",dp(1,n));
}
View Code

3.bzoj1068压缩

题解:f[i][j][t]---->i到j的这一段中间插了(T==1) M的最小压缩长度;

   f[i][j][1]=min(f[i][k][1]+f[k+1][j][1]);

   f[i][j][t]=min(f[i][k][t]+j-k);

   same(i,j) f[i][j][t]=f[i][(i+j)/2][0]+1;

 1 /*
 2     f[i][j][t]---->i到j的这一段中间插了(T==1) M; 
 3 */
 4 #include<algorithm>
 5 #include<cstdio>
 6 #include<cstring>
 7 #include<cmath>
 8 #define N 105
 9 using namespace std;
10 int n;
11 char s[N];
12 int f[N][N][2];
13 bool mark[N][N][2];
14 bool same(int l,int r)
15 {
16     int tmp=r-l+1;
17     if (tmp&1) return false;
18     for (int i=l; i<=(l+r)/2; i++)
19     {
20         if (s[i]!=s[i+tmp/2]) return false; 
21     }
22     return true;
23 }
24 int dp(int l,int r,int t)
25 {
26     int tmp=r-l+1;
27     if (tmp==1) return 1;
28     if (mark[l][r][t]) return f[l][r][t];
29     mark[l][r][t]=1;
30     if (t==1) for (int i=l; i<r; i++) tmp=min(tmp,dp(l,i,1)+dp(i+1,r,1)+1);
31     for (int i=l; i<r; i++) tmp=min(tmp,dp(l,i,t)+r-i);
32     if (same(l,r)) tmp=min(tmp,dp(l,(l+r)/2,0)+1);
33     return  f[l][r][t]=tmp;
34 } 
35 int main()
36 {
37     scanf("%s",s+1);
38     n=strlen(s+1);
39     printf("%d
",dp(1,n,1));
40 }
View Code

 小结:区间DP一般处理区间覆盖、压缩之类问题,但是容易与数据结构弄混,

   导致代码长度大;

三、状压

/*

序:因为状压dp方式类似,

  故具体方程不予列出,

  细节详见代码;

*/

 1.bzoj1725牧场的安排

题解:f[i][j]代表第i行状态为j的情况数;

   转移见代码;

#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#define ll long long 
#define inf 0x7ffffff
#define N 13
#define M 4096
#define base 100000000
using namespace std;
int f[N][M],val[N];
int maxn,n,m,ans;
int read()
{
    int x=0,f=1; char ch;
    while (ch=getchar(),ch<'0'||ch>'9') if (ch=='-') f=-1;
    while (x=x*10+ch-'0',ch=getchar(),ch>='0'&&ch<='9');
    return x*f;
}
void dp()
{
    for (int i=0; i<=maxn; i++) 
    if((i&(i>>1))==0&&(i|val[1])==val[1])
        f[1][i]=1;//,cout<<"    "<<i<<endl;
    //    if (((i & (i>>1))==0) && (i|val[1])==val[1]) f[1][i]==1,cout<<" dd"<<endl;
    for (int i=2; i<=n; i++)
    {
        for (int j=0; j<=maxn; j++)
        {
            if (f[i-1][j])
            for (int k=0; k<=maxn; k++)
            {
                //if (((k&(k>>1))==0) && (k|val[i])==val[i] && (k&j)==0) f[i][k]=(f[i][k]+f[i-1][j])%base,cout<<" dd "<<endl;
                if((j&k)==0&&(k|val[i])==val[i]&&(k&(k>>1))==0)
                    f[i][k]=(f[i][k]+f[i-1][j])%base;
            }
        }
    } 
}
int main()
{
    n=read(); m=read();
    for (int i=1; i<=n; i++)
    {
        for (int j=1; j<=m; j++)
        {
            int x=read();
            val[i]<<=1; val[i]+=x;
        }
    }
    maxn=(1<<m)-1;
    dp();
    for (int i=0; i<=maxn; i++)
    {
        ans=(ans+f[n][i])%base;
    }
    printf("%d
",ans);
}
View Code

2.bzoj1087互不侵犯king

题解:同上,具体见代码;

#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#define ll long long 
#define inf 0x7ffffff
#define N 10
#define M 525
#define KK 9*9+5
using namespace std;
int n,K,maxn;
ll ans; 
ll f[N][M][KK];
int t[N],num[M]; 
bool bo[M],can[M][M];
int read()
{
    int x=0,f=1; char ch;
    while (ch=getchar(),ch<'0'||ch>'9') if (ch=='-') f=-1;
    while (x=x*10+ch-'0',ch=getchar(),ch>='0'&&ch<='9');
    return x*f;
}
void prework()
{
    t[1]=1; for (int i=2; i<=n; i++) t[i]=t[i-1]<<1; maxn=(1<<n)-1;
    for (int i=0; i<=maxn; i++)
    {
        if (i&(i>>1)) continue;
        int x=0;
        for (int j=1; j<=n; j++) if (i & t[j]) x++;
        num[i]=x; bo[i]=1;
    }
    for (int i=0; i<=maxn; i++) if (bo[i])
        for (int j=0; j<=maxn; j++) if (bo[j])
        {
            if ((i&j)==0 && ((i<<1) & j)==0 && ((j<<1) & i)==0) can[i][j]=1;
        }
}
void dp()
{
    for (int i=0; i<=maxn; i++) if (bo[i]) f[1][i][num[i]]=1;
    for (int i=1; i<n; i++)
        for (int j=0; j<=maxn; j++) if (bo[j])
            for (int k=0; k<=maxn; k++) if (bo[k])
            if (can[j][k])
            {
                for (int p=num[j]; p+num[k]<=K; p++) f[i+1][k][p+num[k]]+=f[i][j][p];
            }
}
int main()
{
    n=read(); K=read();
    prework();
    dp();
    for (int i=0; i<=maxn; i++) ans+=f[n][i][K];
    printf("%lld
",ans);
    return 0;
}
View Code

3.bzoj1688疾病管理

题解:f[i]疾病状态为i的最大选取牛数

   j|val[k]==i;f[i]=max(f[j|val[k]]+1);

   注:val[k]为第k头牛的疾病状态;

#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#define ll long long 
#define inf 0x7ffffff
#define M    42768
using namespace std;
int n,d,k,maxn;
int f[M],val[M];
int read()
{
    int x=0,f=1; char ch;
    while (ch=getchar(),ch<'0'||ch>'9') if (ch=='-') f=-1;
    while (x=x*10+ch-'0',ch=getchar(),ch>='0'&&ch<='9');
    return x*f;
}
bool check(int x)
{
    int cnt=0;
    while (x) { cnt+=x&1; x>>=1;
    }
    return cnt<=k;
}
int main()
{
    n=read(); d=read(); k=read();
    for (int i=1; i<=n; i++) 
    {
        int x=read(),a;
        while (x--) a=read(),val[i]|=(1<<(a-1));
    }
    maxn=(1<<d)-1;
    for (int i=1; i<=n; i++)
        for (int j=maxn; j; j--) f[j|val[i]]=max(f[j|val[i]],f[j]+1);
    int ans=0;
    for (int i=0; i<=maxn; i++) if (check(i)) ans=max(ans,f[i]); printf("%d
",ans);
}
/*
6 3 2
0
1 1
1 2
1 3
2 2 1
2 2 1
*/
View Code

4.bzoj2073PRZ

题解:f[i]为状态为i,本次选择重量为j的最小时间

   sc[j]<M; f[i]=min(f[i],f[j^i]+sw[j]);

   sc[j],sw[j]分别为状态为j的重量与时间

#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#define ll long long 
#define inf 0x7ffffff
#define N 20
#define M 70000
using namespace std;
int W,n,maxn;
int f[M],st[M],sw[M];
int t[N],w[N];
int read()
{
    int x=0,f=1; char ch;
    while (ch=getchar(),ch<'0'||ch>'9') if (ch=='-') f=-1;
    while (x=x*10+ch-'0',ch=getchar(),ch>='0'&&ch<='9');
    return x*f;
}
/*
    颓废死亡与坚持成功,请自己选择; 
*/
void prework()
{
    maxn=(1<<n)-1;
    for (int i=0; i<=maxn; i++)
    {
        int x=i,z=1;
        while (x)
        {
            if (x&1) sw[i]+=w[z],st[i]=max(st[i],t[z]);
            x>>=1; z++;
        }    
    } 
}
void dp()
{
    f[0]=0;
    for (int i=1; i<=maxn; i++)
    {
        f[i]=inf/3;
        for (int j=i; j; j=i&(j-1)) if (sw[j]<=W)
        f[i]=min(f[i],f[i^j]+st[j]); 
    }
}
int main()
{
    W=read(); n=read();
    for (int i=1; i<=n; i++)
    {
        t[i]=read(); w[i]=read();
    }
    prework(); 
    dp();
    printf("%d
",f[maxn]);
}
View Code

5.bzoj1231混乱的奶牛

题解:f[i][j]为状态为i,末尾奶牛为j的答案;

   详见代码;

#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#define ll long long 
#define inf 0x7ffffff
#define N 17
#define M 70000
using namespace std;
int n,m,maxn;
int s[N],bin[N];
ll f[M][N],ans;
int read()
{
    int x=0,f=1; char ch;
    while (ch=getchar(),ch<'0'||ch>'9') if (ch=='-') f=-1;
    while (x=x*10+ch-'0',ch=getchar(),ch>='0'&&ch<='9');
    return x*f;
}
/*
    颓废死亡与坚持成功,请自己选择; 
*/
int main()
{
    n=read(); m=read();
    for (int i=1; i<=n; i++) s[i]=read();
    bin[1]=1; for (int i=2; i<=n; i++) bin[i]=bin[i-1]<<1;
    for (int i=1; i<=n; i++) f[bin[i]][i]=1;
    maxn=(1<<n)-1;
    for (int i=1; i<=maxn; i++)
    {
        for (int j=1; j<=n; j++) if (i&bin[j])
        for (int k=1; k<=n; k++) if ((bin[k]|i)!=i && abs(s[j]-s[k])>m)
        f[bin[k]|i][k]+=f[i][j];
    }
    for (int i=1; i<=n; i++) ans+=f[maxn][i];
    printf("%lld
",ans);
}
View Code

6.bzoj1072排列

题解:f[i][j]状态为i 余数为 j的状态数

   f[i|bin[k-1]][(j*10+a[k])%d]+=f[i][j]

   最后排列组合去重;

#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#define ll long long 
#define inf 0x7ffffff

using namespace std;
int bin[20];
int Case,d,len,maxn;
int a[15],v[15],tot[15],f[1025][1005];
char s[15];
int read()
{
    int x=0,f=1; char ch;
    while (ch=getchar(),ch<'0'||ch>'9') if (ch=='-') f=-1;
    while (x=x*10+ch-'0',ch=getchar(),ch>='0'&&ch<='9');
    return x*f;
}
/*
    颓废死亡与坚持成功,请自己选择; 
*/
int main()
{
    Case=read();
    bin[0]=1;for(int i=1;i<20;i++)bin[i]=bin[i-1]<<1;
    while (Case--)
    {
        scanf("%s",s+1); d=read();
        len=strlen(s+1); 
        for (int i=0; i<=9; i++) v[i]=1,tot[i]=0;
        for (int i=1; i<=len; i++)
        {
            a[i]=s[i]-'0'; tot[a[i]]++; v[a[i]]*=tot[a[i]];
        }
        for (int i=0; i<bin[len]; i++) 
            for (int j=0; j<d; j++) f[i][j]=0;
        f[0][0]=1; 
        for(int i=0;i<bin[len];i++)
        for(int k=0;k<d;k++)
            if(f[i][k])
                for(int x=1;x<=len;x++)
                    if((bin[x-1]&i)==0)
                        f[i|bin[x-1]][(a[x]+k*10)%d]+=f[i][k];
        for (int i=0; i<=9; i++) f[bin[len]-1][0]/=v[i];
        printf("%d
",f[bin[len]-1][0]);    
    }
}
View Code

小结:一般看到数据在20左右,就马上要想到状压DP;

四、斜率优化

1.bzoj1911特别行动队

题解:

待更------------------------

   

  

原文地址:https://www.cnblogs.com/HQHQ/p/5714737.html