DP总结

短暂的一年OI学习将要告别,回想一年DP从无到有,想总结一番.

DP:最优子结构+阶段状态转移.

①:区间DP

其实感觉挺套路的,枚举区间长,L,R,即可.

T1:石子合并

sol:从这个题学习到了两点,一个就是拆环为链的技巧,二个就是我们发现要先枚举len,在枚举L,R,len从小到大,这就体现了DP无后效性阶段性的转移,然后对阶段取max即可.

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define e exit(0)
#define re register
#define LL long long
const LL inf = 1e11;
LL n,Max,Min,val[301],sum[301],f[301][301],g[301][301];
inline LL fd(){
    LL s=1,t=0;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
    while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
    return s*t;
}
int main()
{
    n =  fd();
    for(re LL i=1;i<=n;++i){
        val[i] = fd();
        val[i+n] = val[i];
    }
    n = (n<<1);
    memset(g,0,sizeof(g));
    memset(f,0x7f,sizeof(f));
    for(re LL i=1;i<=n;++i)
        sum[i] = sum[i-1]+val[i];
    for(re LL i=1;i<=n;++i)
        f[i][i] = 0;
    for(re LL len=2;len<=n;++len){
        for(re LL L=1;L+len-1<=n;++L){
            LL R = L+len-1;
            for(re LL k=L;k<R;++k){
                f[L][R] = min(f[L][R],f[L][k]+f[k+1][R]);
                g[L][R] = max(g[L][R],g[L][k]+g[k+1][R]);
            }
            f[L][R] += sum[R]-sum[L-1];
            g[L][R] += sum[R]-sum[L-1];
        }
    }
    Min = 1e11,Max = -1;
    n = (n>>1);
    for(re LL i=1;i<=n;++i){
        Min = min(Min,f[i][i+n-1]);
        Max = max(Max,g[i][i+n-1]);
    }
    printf("%lld
%lld",Min,Max);
    return 0;
}
View Code

T2:Polygon

sol:照样拆环为链,按照区间DP,不过要多记录一维,f[L][R][0/1]表示区间最小/最大值,因为最大值可能由两个最小值(负数)相乘得来,最小值可能是最大值乘最小值(正 x 负).

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define e exit(0)
#define re register
#define LL long long
#define inf 1e11
#define Max(x,y)x>y?x:y
#define Min(x,y)x<y?x:y
const LL maxn = 51;
char fh[maxn],bri[maxn][maxn];
LL n,l1,l2,num[maxn],f[maxn][maxn][2];
inline LL fd(){
    LL s=1,t=0;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
    while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
    return s*t;
}
int main()
{
    freopen("POJ1179.in","r",stdin);
    freopen("POJ1179.out","w",stdout);
    n = fd();
    for(re LL i=1;i<=(n<<1);++i){
        if(i&1) scanf("%c",&fh[++l1]);
        else{
            num[++l2] = fd();
            num[l2+n] = num[l2];
        }
    }
    int id = 2;
    for(re LL i=1;i<=n;++i){
        bri[i][i+1] = fh[id++];
        if(id>n) id = 1;
        bri[i+n][i+1+n] = bri[i][i+1];
    }
    n = (n<<1);
    for(re LL i=1;i<=n;++i)
        for(re LL j=1;j<=n;++j)
            f[i][j][0] = inf,f[i][j][1] = -inf;
    for(re LL i=1;i<=n;++i)
        f[i][i][0] = f[i][i][1] = num[i];
    for(re LL len=2;len<=n;++len)
        for(re LL L=1;L+len-1<=n;++L){
            int R = L+len-1;
            for(re LL k=L;k<R;++k){
                if(bri[k][k+1] == 't'){
                    f[L][R][0] = min(f[L][R][0],f[L][k][0]+f[k+1][R][0]);
                    f[L][R][1] = max(f[L][R][1],f[L][k][1]+f[k+1][R][1]);
                }
                else if(bri[k][k+1] == 'x'){
                    f[L][R][0] = min(f[L][R][0],f[L][k][1]*f[k+1][R][0]);
                    f[L][R][0] = min(f[L][R][0],f[L][k][0]*f[k+1][R][1]);
                    f[L][R][0] = min(f[L][R][0],f[L][k][0]*f[k+1][R][0]);
                    f[L][R][0] = min(f[L][R][0],f[L][k][1]*f[k+1][R][1]);
                    f[L][R][1] = max(f[L][R][1],f[L][k][1]*f[k+1][R][1]);
                    f[L][R][1] = max(f[L][R][1],f[L][k][0]*f[k+1][R][0]);
                }
            }
        }
    n = (n>>1);
    LL ans = 0;
    for(re LL i=1;i<=n;++i)
        ans = max(ans,f[i][i+n-1][1]);
    printf("%lld
",ans);
    for(re LL i=1;i<=n;++i)
        if(f[i][i+n-1][1] == ans)
            printf("%lld ",i);
    return 0;
}
View Code

T3:能量项链

sol:照样拆环为链,但这个题比较诡异的地方就是注意拆环为链后头尾坐标,转移的时候注意head[L]*tail[k]*tail[R].

#include<iostream>
#include<cstdio>
using namespace std;
#define R register
int n,ans=-1,head[310],tail[310],f[310][310];
int main()
{    
//    freopen("s.in","r",stdin);
//    freopen("s.out","w",stdout);
    scanf("%d",&n);
    for(R int i=1;i<=n;++i)
    {
        scanf("%d",&head[i]);
        head[i+n]=head[i];
    }
    for(R int i=1;i<=2*n-1;++i)
        tail[i]=head[i+1];
    tail[2*n]=head[1];
    for(R int i=1;i<=2*n-1;++i)
        f[i][i]=0;
    for(R int len=1;len<=n-1;++len){
        for(R int i=1;i<=2*n-len;++i){
            int j=i+len;
            for(R int k=i;k<=j-1;++k)
                f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+head[i]*tail[k]*tail[j]);
        }
    }
    for(R int i=1;i<=n;++i)
        ans=max(ans,f[i][i+n-1]);
    printf("%d",ans);
    return 0;
}
View Code

T4:凸多边形的三角划分

给定一具有 N 个顶点从 1 到 N 编号的凸多边形,每个顶点的权均已知.问如何把 这个凸多边形划分成 N-2 个互不相交的三角形,使得这些三角形顶点的权的乘积之和最小?

忽略这张大图

sol:还是区间DP套路,f[L][R]表示顺序划分[L,R]子多边形最小的权值,转移时注意f[L][R] = min{f[L][k]+f[k][R]+val[L]*val[R]*val[k]}.显然这个题的难点是我们要将点'强行'定编号确定后效形,还要注意细节:k∈[L+1,R-1].

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define e exit(0)
#define re register
#define LL long long
long long n,val[110],f[110][110];
long long getv(LL x,LL y,LL z){return val[x]*val[y]*val[z];}
int main()
{
    freopen("division.in","r",stdin);
    freopen("division.out","w",stdout);
    scanf("%lld",&n);
    for(re LL i=1;i<=n;++i)
        scanf("%lld",&val[i]);
    memset(f,0x7f,sizeof(f));
    for(re LL len=2;len<=n;++len)
        for(re LL i=1;i+len-1<=n;++i){
            LL j=i+len-1;
            if(j==i+1) f[i][j]=0;
            for(re LL k=i+1;k<=j-1;++k)
                f[i][j]=min(f[i][j],f[i][k]+f[k][j]+getv(i,k,j));
        }
    printf("%lld",f[1][n]);
    return 0;
}
View Code

T5:看这里的T4,T3.

 ②状压DP:

做过的题都觉的挺套路的,难点都在如何用二进制运算去满足条件.

T1:广场铺砖

sol:其实这个题算状压DP'状态设计'中比较有意思的题.设横放的为'0',竖放的的为'1',这样做是为了上下转移判断合法,s3 = s1|s2,s3只能有偶数个'1'.且s1,s2上下不能同为1.

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define e exit(0)
#define R register
#define ll long long
int main()
long long W,H,num,sum,f[13][2050],check[2050];
{
    freopen("floor.in","r",stdin);
    freopen("floor.out","w",stdout);
    scanf("%lld%lld",&W,&H);
    if((W&1)&&(H&1)){
        printf("0");
        return 0;
    }
    for(R int i=0;i<(1<<H);++i){
        num=sum=0;
        for(R int j=0;j<H;++j)
            if((1<<j)&i)
                num|=sum,sum=0;
            else sum^=1;
        check[i]=num|sum ? 0: 1;
    }
    f[0][0]=1;
    for(R ll i=1;i<=W;++i)
        for(R ll j=0;j<(1<<H);++j)
        {
            f[i][j]=0;
            for(R ll k=0;k<(1<<H);++k)
                if(!(j&k)&&check[j|k])
                    f[i][j]+=f[i-1][k];
        }
    printf("%lld",f[W][0]);
    return 0;
}
View Code

T2:玉米地

sol:f[x][sta]第x行且状态为sta的总方案.涉及到状压DP计数的一个模板.只要上下&并不冲突,并不包含'贫瘠土地'即可.

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
#define e exit(0)
#define re register
#define LL long long
const LL maxn = (1<<13);
const LL mod = 100000000;
vector<LL> q[20];
LL m,n,Maxm,Maxn,ans,ma[20][20],f[20][maxn];
inline LL fd(){
    LL s=1,t=0;
    char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
    while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
    return s*t;
}
bool checkone(LL x,LL statue){
    bool flag = (statue & statue<<1)>0;
    if(flag) return false;
    LL len = q[x].size();
    for(re LL i=0;i<len;++i){
        LL y = q[x][i];
        if((1<<y)&statue) return false;
    }
    return true;
}
bool checktwo(LL s1,LL s2){
    if(s1&s2) return false;
    return true;
}
int main()
{
    m = fd(),n = fd();
    Maxm = (1<<m),Maxn = (1<<n);
    for(re LL i=0;i<m;++i)
        for(re LL j=0;j<n;++j){
            ma[i][j] = fd();
            if(ma[i][j] == 0)
                q[i].push_back(j);
        }
    f[0][0] = 1;
    for(re LL statue=1;statue<Maxn;++statue){
        if(!checkone(0,statue))
            continue;
        f[0][statue] = 1;
    }
    for(re LL i=1;i<m;++i){
        for(re LL s1=0;s1<Maxn;++s1){
            if(!checkone(i,s1)) continue;
            for(re LL s2=0;s2<Maxn;++s2){
                if(!checktwo(s1,s2)) continue;
                f[i][s1] = (f[i][s1]+f[i-1][s2])%mod;
            }
        }
    }
    for(re LL statue=0;statue<Maxn;++statue)
        ans = (ans+f[m-1][statue])%mod;
    printf("%lld",ans);
    return 0;
}
View Code

T3:互不侵犯

sol:f[x][sta][k]第x行且状态为sta,总共放了k个国王的方案.其实就'玉米地'多去判断&后的相邻位置是否都为'1'.

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define e exit(0)
#define re register
#define LL long long
const LL maxn = (1<<10);
LL n,k,Maxn,ans,num[maxn],f[10][100][maxn];
inline LL fd(){
    LL s=1,t=0;
    char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
    while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
    return s*t;
}
LL work(LL x){
    LL cnt = 0;
    while(x){
        ++cnt;
        x = x&(x-1);
    }
    return cnt;
}
bool checkone(LL x){return (x&x<<1)>0;}
bool checktwo(LL s1,LL s2){
    if(s1&s2) return false;
    LL s3 = s1|s2;
    if(checkone(s3)) return false;
    return true;
}
int main()
{
    n = fd(),k = fd();
    Maxn = (1<<n);
    for(re LL statue = 0;statue<Maxn;++statue)
        num[statue] = work(statue);
    f[0][0][0] = 1;
    for(re LL statue = 1;statue<Maxn;++statue){
        if(checkone(statue)) continue;
        f[0][num[statue]][statue] = 1;
    }
    for(re LL i=1;i<n;++i)
        for(re LL s1=0;s1<Maxn;++s1){
            if(checkone(s1)) continue;
            for(re LL s2=0;s2<Maxn;++s2){
                if(checkone(s2)) continue;
                if(!checktwo(s1,s2)) continue;
                LL s = num[s1];
                for(re LL sum=0;sum<=i*n;++sum){
                    if(!f[i-1][sum][s2]) continue;
                    LL nows = sum+s;
                    f[i][nows][s1] += f[i-1][sum][s2]; 
                }
            }
        }
    for(re LL s = 0;s<Maxn;++s)
        ans += f[n-1][k][s];
    printf("%lld",ans);
    return 0;
}
View Code

T4:炮兵阵地

sol:可以设计f[x][s1][s2],表示第x行,此行状态为s1,x-1行状态为s2,可以合法放的最多炮兵个数.其实可以提前处理好对于第x行合法的x-1行,x-2行,循环的次数会减少,'s2'也可以认为第几个合法的状态,这样时间空间都省下来了,对于每一行,也要判断左右相邻两格都不为1.

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
#define e exit(0)
#define re register
#define LL int
const LL maxn = (1<<10);
vector<LL> D_one[101];
vector<LL> D_two[maxn];
char c[101][10];
LL n,m,Maxm,ans,vis[maxn],num[maxn],f[100][550][550];
inline LL fd(){
    LL s=1,t=0;
    char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
    while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
    return s*t;
}
bool checkone(LL x){
    LL q[24],cnt = 0,id = 0;
    bool flag = (x&x<<1)>0;
    if(flag) return false;
    while(x){
        ++id;
        LL num = x%2;
        if(num == 1)
            q[++cnt] = id;
        x/=2;
    }
    sort(q+1,q+1+cnt);
    for(re LL i=2;i<=cnt;++i)
        if(q[i] - q[i-1]<=2)
            return false;
    return true;
}
bool checktwo(LL x,LL s){
    if(!checkone(s)) return false;
    LL len = D_one[x].size();
    for(re LL i=0;i<len;++i){
        LL t = D_one[x][i];
        if((1<<t)&s) return false;
    }
    return true;
}
bool checkIII(LL s1,LL s2){
    if(s1&s2) return false;
    return true;
}
LL work(LL x){
    LL cnt = 0;
    while(x){
        ++cnt;
        x = x&(x-1);
    }
    return cnt;
}
int main()
{
    n = fd(),m = fd();
    for(re LL i=0;i<n;++i)
        for(re LL j=0;j<m;++j){
            cin>>c[i][j];
            if(c[i][j] == 'H')
                D_one[i].push_back(m-j-1);
        }
    Maxm = (1<<m);
    for(re LL s = 0;s<Maxm;++s)
        num[s] = work(s);
    for(re LL i=0;i<n;++i)
        for(re LL s=0;s<Maxm;++s){
            if(!checktwo(i,s)) continue;
            D_two[i].push_back(s);
        }
    LL len = D_two[0].size();
    if(n == 1){
        for(re LL i=0;i<len;++i){
            LL s = D_two[0].size();
            ans = max(ans,num[s]);
        }
        printf("%d",ans);
        return 0;
    }
    for(re LL i=0;i<D_two[1].size();++i){
        LL s1 = D_two[1][i];
        for(re LL j=0;j<D_two[0].size();++j){
            LL s2 = D_two[0][j];
            if(!checkIII(s1,s2)) continue;
            f[1][i][j] = num[s1]+num[s2];
        }
    }
    for(re LL i=2;i<n;++i){
        LL len1 = D_two[i].size();
        for(re LL a=0;a<len1;++a){
            LL s1 = D_two[i][a];
            LL len2 = D_two[i-1].size();
            for(re LL b=0;b<len2;++b){
                LL s2 = D_two[i-1][b];
                if(!checkIII(s1,s2)) continue;
                LL len3 = D_two[i-2].size();
                for(re LL c=0;c<len3;++c){
                    LL s3 = D_two[i-2][c];
                    if(!checkIII(s2,s3)||!checkIII(s1,s3))
                        continue;
                    f[i][a][b] = max(f[i][a][b],f[i-1][b][c]+num[s1]);
                }
            }
        }
    }
    LL len1 = D_two[n-1].size(),len2 = D_two[n-2].size();
    for(re LL i=0;i<len1;++i)
        for(re LL j=0;j<len2;++j)
            ans = max(ans,f[n-1][i][j]);
    printf("%d",ans);
    return 0;
}
View Code

T5:愤怒的小鸟

sol:f[x]表示状态为x的用的最少的小鸟.我们任选两个合法的小鸟作为一条抛物线,找出状态s'表示这个抛物线能打到的猪.然后对每一个状态暴力用每一条抛数线更新即可.

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define e exit(0)
#define re register
#define LD double
const int maxn = (1<<19);
int T,n,m,Maxn,f[maxn],a[20][20];
struct DL{LD x,y;}Q[20];
LD Fabs(LD a){return a>0?a:-a;}
int main()
{
    scanf("%d",&T);
    while(T--){
        memset(a,0,sizeof(a));
        memset(f,0x3f,sizeof(f));
        scanf("%d%d",&n,&m);
        Maxn = (1<<n);
        for(re int i=0;i<n;++i)
            scanf("%lf%lf",&Q[i].x,&Q[i].y);
        for(re int i=0;i<n;++i)
            a[i][i] |= (1<<i);
        for(re int i=0;i<n;++i)
            for(re int j=i+1;j<n;++j){
                if(fabs(Q[i].x-Q[j].x)>1e-6){
                    LD x1 = Q[i].x,y1 = Q[i].y,x2 = Q[j].x,y2 = Q[j].y;
                    LD A = (x2*y1-x1*y2)/((x1-x2)*x1*x2);
                    LD B = (y1-A*x1*x1)/x1;
                    if(A > -1e-6) continue;
                    for(re int k=0;k<n;++k){
                        LD x = Q[k].x,y = Q[k].y;
                        if(fabs(A*x*x+B*x-y)<=1e-6)
                            a[i][j] |= (1<<k);
                    }        
                }
            }
        f[0] = 0;
        for(re int i=0;i<Maxn;++i){
            for(re int j=0;j<n;++j)
                for(re int k=j;k<n;++k)
                    f[i|a[j][k]] = min(f[i|a[j][k]],f[i]+1);
        }
        printf("%d
",f[Maxn-1]);
    }
    return 0;
}
View Code

T6:骑士

国际象棋中骑士的移动规则和中国象棋中的马是类似的,它先沿着一个方向移动两格,再沿着与刚才移动方向垂直的方向移动一格.路径上的棋子并不会影响骑士的移动,但是如
果一个骑士走到了一个放有棋子的格子,它就会攻击那个棋子.现在有一个 n*n 的棋盘,有 k 个骑士需要被摆到棋盘上去.那么使所有骑士互不攻击的摆放方式一共有多少种呢?

首先你要弄清骑士的攻击范围

然后这个题就完了.

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
#define e exit(0)
#define re register
const int maxn = (1<<8);
int n,k,Maxn;
double f[8][maxn][maxn][65];
vector<int> Q[maxn];vector<int> G[maxn];
inline int fd(){
    int s=1,t=0;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
    while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
    return s*t;
}
bool checkone(int s1,int s2){
    int s4 = (s1<<2),s5 = (s1>>2);
    if((s2&s4)||(s2&s5)) return false;
    return true;
}
bool checktwo(int s1,int s2){
    int s4 = (s1<<1),s5 = (s1>>1);
    if((s2&s4)||(s2&s5)) return false;
    return true;
}
int getsum(int x){
    int cnt = 0;
    while(x){++cnt;x = x&(x-1);}
    return cnt;
}
void propre(){
    memset(f,0,sizeof(f));
    for(re int i=0;i<Maxn;++i){
        int t = getsum(i);
        f[0][i][0][t] = 1;
    }
    for(re int s1=0;s1<Maxn;++s1)
        for(re int s2=0;s2<Maxn;++s2){
            if(!checkone(s1,s2)) continue;
            int t1 = getsum(s1),t2 = getsum(s2);
            f[1][s1][s2][t1+t2] += f[0][s2][0][t2];
        }
}
void slove(){
    for(re int i=2;i<n;++i){
        for(re int sta=0;sta<Maxn;++sta){
            int len1 = Q[sta].size(),len2 = G[sta].size(),p = getsum(sta);
            for(re int x=0;x<len1;++x){
                int s1 = Q[sta][x];
                for(re int y=0;y<len2;++y){
                    int s2 = G[sta][y];
                    if(!checkone(s1,s2)) continue;
                    for(re int t=0;t<=k;++t){
                        int s = t+p;
                        f[i][sta][s1][s] += f[i-1][s1][s2][t];
                    }
                }
            }
        }
    }
    double ans = 0;
    for(re int s1=0;s1<Maxn;++s1)
        for(re int s2=0;s2<Maxn;++s2){
            ans += f[n-1][s1][s2][k];
        }
    printf("%.0lf",ans);
}
int main()
{
    freopen("knight.in","r",stdin);
    freopen("knight.out","w",stdout);
    n = fd(),k = fd();
    Maxn = (1<<n);
    for(re int i=0;i<Maxn;++i)
        for(re int j=0;j<Maxn;++j){
            if(!checkone(i,j))continue;
            Q[i].push_back(j);
        }
    for(re int i=0;i<Maxn;++i)
        for(re int j=0;j<Maxn;++j){
            if(!checktwo(i,j)) continue;
            G[i].push_back(j);
        }
    propre();slove();
    return 0;
}
View Code

 树形DP:

可以想象树形父子关系的每一层就是一个DP转移阶段,天然后效性.

T1:没有上司的舞会

sol:f[x][0/1]表示第x个节点选不选,保证x即x的子树都合法的前提下,能得到的最大值快乐值.可知当x选时,x的儿子必不选,x不选时,x的儿子可选可不选.

#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
using namespace std;
#define e exit(0)
#define re register
const int maxn = 6010;
int n,cnt,rt,U[maxn],head[maxn],val[maxn],f[maxn][2];
struct bian{int to,next;}len[maxn];
inline int fd(){
    int s=1,t=0;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
    while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
    return s*t;
}
void add(int from,int to){
    len[++cnt].to = to;
    len[cnt].next = head[from];
    head[from] = cnt;
}
void dp(int x){
    for(re int k=head[x];k;k=len[k].next){
        int to = len[k].to;
        dp(to);
        f[x][1] += f[to][0];
        f[x][0] += max(f[to][1],f[to][0]);
    }
    f[x][1] += val[x];
}
int main()
{
    n = fd();
    for(re int i=1;i<=n;++i)
        val[i] = fd();
    for(re int i=1;i<=n-1;++i){
        int x = fd(),y = fd();
        add(y,x);
        ++U[x];
    }
    int x = fd(),y = fd();
    for(re int i=1;i<=n;++i)
        if(!U[i]) rt = i;
    dp(rt);
    printf("%d",max(f[rt][0],f[rt][1]));
    return 0;
}
View Code

T2:选课

sol:很经典的树上泛化背包问题,对此可以应用于多叉树,并不只限于二叉树,在code中有重要解释.

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define e exit(0)
#define re register
const int maxn = 401;
int n,m,cnt,val[maxn],head[maxn],f[maxn][maxn];
struct bian{int to,next;}len[maxn];
inline int fd(){
    int s=1,t=0;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
    while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
    return s*t;
}
void add(int from,int to){
    len[++cnt].to = to;
    len[cnt].next = head[from];
    head[from] = cnt;
}
void dp(int x){
    f[x][0] = 0;
    for(re int k=head[x];k;k=len[k].next){
        int to = len[k].to;
        dp(to);
                //倒序,类似于01背包的先后更新顺序.
        for(re int t=m;t>=0;--t)
            for(re int j=0;j<=t;++j){//倒序顺序即可.
                        
                if(t-j>=0)    f[x][t] = max(f[x][t],f[x][t-j]+f[to][j]);
            }
    }
    if(x != 0)
        for(re int t=m;t>0;--t)
            f[x][t] = f[x][t-1]+val[x];//不能取max,f[x][t]未更新前是儿子选t的状态,并不是它自己选t的状态
}
int main()
{
    n = fd(),m = fd();
    for(re int i=1;i<=n;++i){
        int x = fd();val[i] = fd();
        int y = i;
        add(x,y);
    }
    dp(0);
    printf("%d",f[0][m]);
    return 0;
}                                                        
View Code

 T3:通往自由的钥匙

通向自由的钥匙被放 n 个房间里,这 n 个房间由 n-1 条走廊连接.但是每个房间里都有 特别的保护魔法,在它的作用下,我无法通过这个房间,也无法取得其中的钥匙.虽然我可
以通过消耗能量来破坏房间里的魔法,但是我的能量是有限的.那么,如果我最先站在 1 号房间(1 号房间的保护魔法依然是有效的,也就是,如果不耗费能量,我无法通过 1 号房间,也无法取得房间中的钥匙),如果我拥有的能量为 P,我最多能取得多少钥匙?

sol:开始感觉与上面一题相似,其实不然,由于这个背包带权,很多细节不一样,在code中重要解释.

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define e exit(0)
#define re register
const int N = 301;
int n,p,cnt,cost[N],v[N],head[N],f[N][N];
struct bian{int to,next;}len[N];
inline int fd(){
    int s=1,t=0;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
    while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
    return s*t;
}
void add(int from,int to){
    len[++cnt].to = to;
    len[cnt].next = head[from];
    head[from] = cnt;
}
void Dfs(int x,int fa){
    for(re int j=p;j>=cost[x];--j)
        f[x][j] = max(f[x][j],f[x][j-cost[x]]+v[x]);
    //提前将v[x]的影响加上,先强制选x.
    for(re int k=head[x];k;k=len[k].next){
        int to = len[k].to;
        if(to == fa) continue;
        Dfs(to,x);
        //为什么一定要大于等于cost[x],而不是像上一个题大于等于0?由于这个背包带权,我们不能让不合法的状态(<cost[x])有值.
        //并且有强制选x的意思.
        for(re int m=p;m>=cost[x];--m)
            for(re int t=m;t>=cost[x];--t)
                f[x][m] = max(f[x][m],f[x][t]+f[to][m-t]);
    }
}
int main()
{
    freopen("key.in","r",stdin);
    freopen("key.out","w",stdout);
    n = fd(),p = fd();
    for(re int i=1;i<=n;++i)
        cost[i] = fd(),v[i] = fd();
    for(re int i=1;i<=n-1;++i){
        int x = fd(),y = fd();
        add(x,y),add(y,x);
    }    
    Dfs(1,0);
    printf("%d",f[1][p]);
    return 0;
}
View Code

T4:警卫安排

一个重要的基地被分为 n 个连通的区域.出于某种神秘的原因,这些区域以一个区域为核心,呈一颗树形分布.在每个区域安排警卫所需要的费用是不同的,而每个区域的警卫都可以望见其相邻的区域,只要一个区域被一个警卫望见或者是安排有警卫,这个区域就是安全的.你的任务是在确保所有区域都是安全的情况下,找到安排警卫的最小费用.

sol:T1的升级版.f[x][0/1/2]表示它被父亲,自己,儿子选,保证它即它的子树都合法的前提下,安排警卫的最小费用,f[x][0]可以转移f[son][1/2],f[x][1]可以转移f[son][0/1/2],f[x][2]则要强制选一个儿子,其他选最优情况.

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define e exit(0)
#define re register
const int maxn=730;
int n,m,from,to,root,cnt,val[maxn],head[maxn],du[maxn],cu[maxn],f[maxn][3];
struct bian{
    int to,next;
}len[maxn<<1];
inline int fd(){
    int s=1,t=0;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')
            s=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        t=t*10+c-'0';
        c=getchar();
    }
    return s*t;
}
void add(int from,int to){
    len[++cnt].to=to;
    len[cnt].next=head[from];
    head[from]=cnt;
}
void dp(int x){
    f[x][1] = val[x];
    f[x][2] = 0;
    for(re int k=head[x];k;k=len[k].next){
        int to=len[k].to;
        dp(to);
        f[x][2] += min(f[to][1],f[to][0]);
        f[x][1] += min(f[to][0],min(f[to][1],f[to][2]));
    }
    int ans = 2139062143;
    for(re int k=head[x];k;k=len[k].next){
        int to=len[k].to;
        int v=f[to][1];
        for(re int g=head[x];g;g=len[g].next){
            int t=len[g].to;
            if(t==to) continue;
            v += min(f[t][0],f[t][1]);
        }
        ans = min(ans,v);
    }
    f[x][0] = ans;
}
int main()
{
    freopen("security.in","r",stdin);
    freopen("security.out","w",stdout);
    n=fd();
    for(re int i=1;i<=n;++i){
        from=fd(),val[from]=fd(),m=fd();
        for(re int i=1;i<=m;++i){
            to=fd(),add(from,to);
            ++du[to],++cu[from];
        }
    }
    for(re int i=1;i<=n;++i)
        if(!du[i]){
            root=i;
            break;
        }
    memset(f,0x7f,sizeof(f));
    dp(root);
    printf("%d",min(f[root][0],f[root][1]));
    return 0;
}
View Code

T5:消防局的设立

sol:T4的升级版,多分情况讨论即可.设f[x][0/1/2/3/4].f[x][0]保证x的爷爷,x的父亲,x,即x的子树都安全的最少消防局数量;f[x][1]保证x的父亲,x,即x的子树都安全的最少消防局数量;f[x][0]保证x即x的子树都安全的消防局数量...要注意这里的f[x][0/1/2/3/4]与T4的定义是不同的.接下来的转移就贪心地分类讨论即可,借鉴一下LG题解.

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define e exit(0)
#define re register
#define inf 19260817
const int maxn = 1010;
int n,cnt,head[maxn],f[maxn][5];
struct bian{int to,next;}len[maxn];
inline int fd(){
    int s=1,t=0;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
    while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
    return s*t;
}
void add(int from,int to){
    len[++cnt].to = to;
    len[cnt].next = head[from];
    head[from] = cnt;
}
void DP(int x){
    f[x][0] = 1,f[x][3] = f[x][4] = 0;
    for(re int k=head[x];k;k=len[k].next){
        int to = len[k].to;
        DP(to);
        f[x][0] += f[to][4];
        f[x][3] += f[to][2];
        f[x][4] += f[to][3];
    }
    if(head[x] == 0)
        f[x][0] = f[x][1] = f[x][2] = 1;
    else{
        f[x][1] = f[x][2] = inf;
        for(re int k=head[x];k;k=len[k].next){
            int s = len[k].to;
            int v1 = f[s][0],v2 = f[s][1];
            for(re int g=head[x];g;g=len[g].next){
                int t = len[g].to;
                if(s == t) continue;
                v1 += min(f[t][2],f[t][3]),v2 += f[t][2];
            }
            f[x][1] = min(f[x][1],v1);
            f[x][2] = min(f[x][2],v2);
        }
    }
    for(re int i=1;i<=4;++i)
        f[x][i] = min(f[x][i],f[x][i-1]);
}
int main()
{
    n = fd();
    for(re int i=2;i<=n;++i){
        int x = fd(),y = i;
        add(x,y);
    }
    DP(1);
    printf("%d",f[1][2]);
    return 0;
}
View Code

T6:这里的T4.

 背包:

看似最基础的,但个人认为是最重要的.

T1:01背包.

sol:主要注意倒序转移,用之前的状态更新当前的状态,即代表物品在这个阶段只用一次.

#include<iostream>
#include<cstdio>
using namespace std;
#define R register
const int maxn=1010;
int v,m,ti[maxn],val[maxn],f[maxn];
inline int fd()
{
    int s=1,t=0;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')
            s=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        t=t*10+c-'0';
        c=getchar();
    }
    return s*t;
}
int main()
{
    //freopen("s.in","r",stdin);
    //freopen("s.out","w",stdout);
    v=fd(),m=fd();
    for(R int i=1;i<=m;++i)
        ti[i]=fd(),val[i]=fd();
    for(R int i=1;i<=m;++i)
        for(R int j=v;j>=ti[i];--j)
            f[j]=max(f[j],f[j-ti[i]]+val[i]);
    printf("%d",f[v]);
    return 0;
}
View Code

T2:完全背包.

sol:注意正序转移,用现在的状态更新现在状态,即代表物品在这个阶段使用多次.

#include<iostream>
#include<cstdio>
using namespace std;
#define R register
const int maxn=1e5+10;
int v,m,f[maxn],ti[maxn],val[maxn];
inline int fd()
{
    int s=1,t=0;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')
            s=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        t=t*10+c-'0';
        c=getchar();
    }
    return s*t;
}
int main()
{
//    freopen("s.in","r",stdin);
//    freopen("s.out","w",stdout);
    v=fd(),m=fd();
    for(R int i=1;i<=m;++i)
        ti[i]=fd(),val[i]=fd();
    for(R int i=1;i<=m;++i)
        for(R int j=ti[i];j<=v;++j)
            f[j]=max(f[j],f[j-ti[i]]+val[i]);
    printf("%d",f[v]);
    return 0;
}
View Code

T3:01背包变形

en,没有题面,就是将01背包的容积扩大到1e9范围,但Σ价值的范围控制在1e6内.

sol:改一下f[i]定义即可,f[i]表示能达到i价值时使用的背包最小值.

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define e exit(0)
#define re register
#define inf 19260817
const int maxn = 3e5;
int t,m,sumc,v[maxn],c[maxn],f[maxn];
inline int fd(){
    int s=1,t=0;
    char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
    while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
    return s*t;
}
int main()
{
    t = fd(),m = fd();
    for(re int i=1;i<=m;++i){
        v[i] = fd(),c[i] = fd();
        sumc += c[i];
    }
    for(re int i=1;i<=sumc;++i)
        f[i] = inf;
    f[0] = 0;
    for(re int i=1;i<=m;++i)
        for(re int j=sumc;j>=c[i];--j){
            f[j] = min(f[j],f[j-c[i]]+v[i]);
        }
    for(re int i=sumc;i>=0;--i){
        if(f[i]<=t){
            printf("%d",i);
            break;
        }
    }
    return 0;
}
View Code

T4:完全背包变形

sol:选择最小物品,利用迪杰斯特拉,去构造关于它的剩余系即可(我可能在滥用'剩余系').

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
#define e exit(0)
#define re register
#define inf 19260817
const int maxn = 6e4;
int n,mod,q,a[maxn+10],dis[maxn+10];
inline int fd(){
    int s=1,t=0;
    char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
    while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
    return s*t;
}
void dj(){
    for(re int i=0;i<maxn;++i)
        dis[i] = inf;
    dis[0] = 0;
    priority_queue<pair<int,int> >q;
    q.push(make_pair(0,0));
    while(q.size()){
        int v = q.top().first;
        int now = q.top().second;
        q.pop();
        v = -v;
        if(dis[now]!=v) continue;
        for(re int i=2;i<=n;++i){
            int id = (now+a[i])%mod;
            if(dis[id] > dis[now]+a[i]){
                dis[id] = dis[now]+a[i];
                q.push(make_pair(-dis[id],id));
            }
        }
    }
}
int main()
{
    n = fd();
    for(re int i=1;i<=n;++i)
        a[i] = fd();
    sort(a+1,a+1+n);
    mod = a[1];
    dj();
    q = fd();
    for(re int i=1;i<=q;++i){
        int num = fd();
        if(dis[num%mod]>num) printf("No
");
        else printf("Yes
");
    }    
    return 0;
}
View Code

 T5:多重背包

给定N种物品,其中第i种物品的体积为vi,价值为wi,并且有ci个.有一个容积为M的背包,选择若干个物品选入,体积不超过M,求最大价值.

sol:可以暴力拆ci为1,在做01背包,也可以将ci 二进制拆分,利用唯一分解定理,也可做.

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define e exit(0)
#define re register
const int M = 10001;
int n,lx,cost[M],num[M],val[M];
struct DL{int v,c;}Q[M<<2];
inline int fd(){
    int s=1,t=0;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
    while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
    return s*t;
}
int main()
{
    freopen("kkk.in","r",stdin);
    freopen("kkk.out","w",stdout);
    n = fd();
    for(re int i=1;i<=n;++i)
        cost[i] = fd(),val[i] = fd(),num[i] = fd();
    for(re int i=1;i<=n;++i){
        for(re int j=1;j<=num[i];j<<1){
            Q[++lx].c = val[i]*j;
            Q[lx].v = j*cost[i];
            num[i] -= j;
        }
        if(num[i] > 0){
            Q[++lx].c = val[i]*num[i];
            Q[lx].v = num[i]*j;
        }
    }
    return 0;
}
View Code

T6:分组背包

 sol:将组的类别看做转移的阶段,类比于01背包,但要注意颜色类别,体积,同一颜色物品三者顺序不能换.

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define e exit(0)
#define re register
int V,n,c,f[1001],vis[101],len[101];
struct BP{int cost,v;}Q[101][1001];
inline int fd(){
    int s=1,t=0;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
    while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
    return s*t;
}
int main()
{
    freopen("kkk.in","r",stdin);
    freopen("kkk.out","w",stdout);
    V = fd(),n = fd();
    for(re int i=1;i<=n;++i){
        int cost = fd(),v = fd(),col = fd();
        Q[col][++len[col]].cost = cost;
        Q[col][len[col]].v = v;
        if(!vis[col]){
            vis[col] = 1;
            ++c;
        }
    }
    for(re int i=1;i<=c;++i){
        for(re int m=V;m>=0;--m)
            for(re int k=1;k<=len[i];++k){
                //if(m < cost) continue;
                int cost = Q[i][k].cost,v = Q[i][k].v;
                if(m < cost) continue;
                f[m] = max(f[m],f[m-cost]+v);
            }
    }
    printf("%d",f[V]);
    return 0;
}
View Code

T7:coin

sol:如果强行用二进制拆分写会超时,单调队列? 没试过.设f[i]表示i面值是否能被表示,used[j]表示f[j]变为1在i阶段最少用多少枚硬币.同时考虑到这个题的贪心性质.前i种硬币能拼成面值j只有两种情况:①在i-1及以前的阶段就使f[j]变成1.②在i阶段,存在f[j-a[i]]变为1,used[j-a[i]]<c[i].

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define e exit(0)
#define re register
const int maxn = 1e5+5;
int n,m,a[maxn],b[maxn],f[maxn],used[maxn];
inline int fd(){
    int s=1,t=0;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
    while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
    return s*t;
}
int main()
{
    while(1){
        n = fd(),m = fd();
        if(n == 0&&m == 0)
            break;
        for(re int i=1;i<=n;++i)
            a[i] = fd();
        for(re int i=1;i<=n;++i)
            b[i] = fd();
        f[0] = 1;
        for(re int i=1;i<=n;++i){
            for(re int j=0;j<=m;++j)
                used[j] = 0;
            for(re int j=a[i];j<=m;++j)        
                if(!f[j]&&f[j-a[i]]&&used[j-a[i]] < b[i])
                    f[j] = 1,used[j] = used[j-a[i]]+1;
        }
        int ans = 0;
        for(re int i=1;i<=m;++i)
            if(f[i]){
                ans += 1;
                f[i] = 0;
            }
        printf("%d
",ans);
    }
    return 0;
}
View Code

T8:这里T3

sol:经典完全背包找第k大问题.

⑤:期望DP

E(x) = 总和/总方案数. E(x) = 单次方案概率*单次方案和.

期望是满足线性递推关系的.

T1:绿豆蛙的归宿

sol:这个题首先要明白要逆推,应为终点到终点的期望距离肯定是0,也就是我们已知了终点的期望状态.那我们建反向图,做一遍拓扑排序,按照题意递推即可.

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
#define e exit(0)
#define D double
#define re register
const int maxn = 1e5+10;
double f[maxn],ans;
int n,m,cnt,head[maxn],du[maxn],degree[maxn];
struct bian{int to,next,v;}len[maxn<<1];
inline int fd(){
    int s=1,t=0;
    char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
    while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
    return s*t;
}
void add(int from,int to,int v){
    len[++cnt].v = v;
    len[cnt].to = to;
    len[cnt].next = head[from];
    head[from] = cnt;
}
int main()
{
    n = fd(),m = fd();
    for(re int i=1;i<=m;++i){
        int from = fd(),to = fd(),v = fd();
        add(to,from,v);
        ++du[from],++degree[from];
    }
    queue<int> q;
    q.push(n);
    while(q.size()){
        int now = q.front();
        q.pop();
        for(re int k=head[now];k;k=len[k].next){
            int to = len[k].to;
            f[to] += (f[now]+len[k].v)/degree[to];
            --du[to];
            if(du[to] == 0)
                q.push(to);
        }
    }
    printf("%.2lf",f[1]);
    return 0;
}
View Code

T2:涂色.

 ps:只会一个很暴力的办法.

sol:首先设计f[s]为涂色涂成状态为s的概率,那么分k个阶段,每次选择两个点作为染色区间去暴力覆盖s的都新的状态s',注意s必须倒序(与01背包倒序原因一致),每次都成上n^2的逆元即可,最后累加状态s中1的个数 x f[s]即可.

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define e exit(0)
#define re register
#define LL long long
const LL mod = 998244353;
const LL maxn = (1<<16);
LL n,k,ans,Maxn,Ins,f[maxn]; 
inline LL fd(){
    LL s=1,t=0;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
    while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
    return s*t;
}
LL qsm(LL x,LL y){
    LL base = 1;
    while(y){
        if(y&1) base = (base%mod*x%mod)%mod;
        x = (x%mod*x%mod)%mod;
        y>>=1;
    }
    return base;
}
LL getnum(LL x){
    LL cnt = 0;
    while(x){
        ++cnt;
        x = x&(x-1);
    }
    return cnt;
}
int main()
{
    freopen("paint.in","r",stdin);
    freopen("paint.out","w",stdout);
    n = fd(),k = fd();
    Maxn = (1<<n);
    f[0] = 1,Ins = qsm(n*n,mod-2);
    for(re LL i=1;i<=k;++i)
        for(re LL sta=Maxn;sta>=0;--sta){
            LL ret = f[sta];
            for(re LL a=0;a<n;++a)
                for(re LL b=0;b<n;++b){
                    LL x = a,y = b;
                    if(x > y) swap(x,y);
                    LL s1 = (1<<x)-1,s2 = (1<<y+1)-1,s3,s4;
                    s3 = (s1^s2),s4 = (sta|s3);
                    f[s4] = (f[s4]%mod+(ret*Ins%mod)%mod)%mod;
                }
        }
    for(re LL sta=0;sta<Maxn;++sta)
        ans = (ans%mod+(getnum(sta)%mod*f[sta]%mod)%mod)%mod;
    printf("%lld",ans);
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/xqysckt/p/11850999.html