2018年模板大集合!!!!没有一个优秀的模板就是等着被摩擦[DP部分]

日常wa点:

1.dp时for的i是从大到小还是从小到大,注意是否影响后续性质。1

2.单调队列如果值受到i的影响,那么应该把结果都存下来,而不是只单纯考虑最有值,因为有可能后面进来点会超前面。1

3.基环树注意多个森林。1

4.DP[i]=DP[i-j*w[i]]+xxx可以变成dp[i]=dp[j+w[i]*k]+xxx的形式

5.使用1<<n,的时候注意如果n可以大于30,记得1要加LL

6.插头dp适用:超小数据范围,网格图,连通性。

填表法就是利用状态转移方程和上一个状态来推导出现在的状态(相当于知道已知条件,将答案填入)

刷表法就是利用当前的状态,把有关联的下一状态都推出来。

基环树DP:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+7;
const int N = 1e6+10;   //基环树中点的个数
const int E = 1e6+10;   //基环树中无向边的个数
struct Edge{int next,to;}e[E*2];
int head[N], cnt;
bool vis[N];
int val[N], ringPt1, ringPt2, not_pass; //ringPt1,ringPt2 -> not_pass边的端点
long long dp[2][N];
void add(int u,int v){
    e[++cnt].next = head[u];
    e[cnt].to = v;
    head[u] = cnt;
}
void getDP(int rt, int fa) {
    dp[0][rt] = 0,  dp[1][rt] = val[rt];
    for(int i=head[rt];i!=-1;i=e[i].next) {
        if(e[i].to == fa)   continue;
        if(i == not_pass || i == (not_pass^1))  continue;
        getDP(e[i].to, rt);
        //具体树形dp策略(根据实际修改)
        dp[0][rt] += max(dp[0][e[i].to], dp[1][e[i].to]);
        dp[1][rt] += dp[0][e[i].to];
    }
}
void dfs(int rt, int fa) {
    vis[rt] = 1;
    for(int i=head[rt];i!=-1;i=e[i].next) {
        if(e[i].to == fa)   continue;
        if(!vis[e[i].to])   dfs(e[i].to, rt);
        else {
            //记录基环上一条特定边的标号
           //e[not_pass]为该边
            //e[not_pass]^1为该边的反向边
            not_pass = i;
            ringPt1 = e[i].to;  ringPt2 = rt;
        }
    }
}
void init() {
    memset(head,-1,sizeof(head));
    cnt = 1;    //为配合基环边及其反向边的记录,特将其初始化为1
    memset(vis,0,sizeof(vis));
}
int main() {
    int i,j,k,f1,f2,f3,f4,t1,t2,t3,t4,m;
    int T;
    //freopen("in","r",stdin);
    init();
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d %d",&t1,&t2);
        val[i]=t1;
        add(i,t2);add(t2,i);
    }
    long long ans = 0 ;
    long long qq;
    for(int i=1;i<=n;i++){
        if(vis[i]==1)continue;
        dfs(i, -1); //从i开始搜完这一棵树,
        getDP(ringPt1, -1); //pt1是尾巴
        qq= dp[0][ringPt1];
        getDP(ringPt2, -1);
        qq=max(qq,dp[0][ringPt2]);
        ans += qq;
    }
    printf("%lld\n",ans);
}

把一个环取出来(这里每个cir就表示为一个完整的环):

 for(int i = 1; i <= n; i++){
        if(!vis[i]){
            top = 0;
            getCir(i);
        }
    }
void getCir(int u){
    if(vis[u] == 1) return ;
    if(vis[u] == -1){//为-1的时候表示为环,把环记录下来 ,cntCir表示有多条环
        cntCir++;
        for(int i = top; i >= 1; i--){
//            cout <<cntCir <<"     "<<sta[i] <<endl;
            cir[cntCir].pb(sta[i]);
            vvis[sta[i]] = 1;
            if(sta[i] == u) break; //找到标记的点,并且回去也就仅仅回到这个标记的点
        }
        return;
    }
    vis[u] = -1;
    sta[++top] = u;
    getCir(f[u]);
    top--;
    vis[u] = 1;
}

状态压缩二进制:

枚举一个集合的全部子集合。

        sub=sup; //枚举sup的所有子集合
        do{
            sub=(sub-1)&sup;
        }while(sub!=sup);

枚举个数为i个1的全部集合。

comb=(1<<i)-1;
    while(comb<1<<n){
        x=comb&-comb,y=comb+x;
        comb=((comb&~y)/x>>1)|y;//这一行赋值之前为每次的答案
    }

多重背包单调队列优化,之前就-k*v[i],后面k表大的时候再加k*v[i]是这个差值在num[i]个数的范围内,

因为每次都把值存入list1中,所以顺序是正确的:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+7;
int w[maxn],v[maxn],num[maxn];
int list1[maxn];
int val[maxn];
int dp[maxn],deq[maxn];
int main(){
    int i,j,k,f1,f2,f3,f4,t1,t2,t3,t4,n,m,T;
    //freopen("in.txt","r",stdin);
    //freopen("out1.txt","w",stdout);
    scanf("%d\n",&T);
    while(T--){
    memset(dp,0,sizeof(dp));
    scanf("%d %d",&n,&m);
    for(i=1;i<=m;i++){
        scanf("%d %d %d",&w[i],&v[i],&num[i]);
    }
    int s,t;
    for(i=1;i<=m;i++){
        for(j=0;j<w[i];j++){
            s=t=0; //t为头,s为尾,都不代表数字,期间的才为内容
            for(k=0;j+k*w[i]<=n;k++){//s表示的为最优内容,刚开始为0
            val[k]=dp[j+k*w[i]]-k*v[i];//也就表示从0开始获取
            while(t>s&&val[k]>=val[list1[t-1]]){//t-1表示头,比其优就挤出来
                t--;
            }
            deq[t]=k; //从0开始,因为s是第一个结果,不从0开始不知道s表示什么
            list1[t++]=k;
            dp[j+k*w[i]]=max(dp[j+k*w[i]],val[list1[s]]+k*v[i]);
            while(t>s&&k-deq[s]>=num[i]){ //每次都是值会删除一个值
            s++;
            }
        }
    }
    }
    printf("%d\n",dp[n]);
    }
    return 0;
}

poj3709,斜率优化:

题意:给一个序列,每个序列为递增序列。每次可以把一个数字的值-1,问最少需要几次操作可以把这个序列做到每个数字至少有k个。

显然dp,dp[i]表示i以及之前的全部处理的最小代价。

那么dp[i]=dp[j]+sum[i]-sum[j-1]-a[j]*(i-j+1),这里j表示被选择作为值的那个下标,那么j的范围是1<=j<=(i-k+1)

这里可以转换为dp[i]=sum[i]-a[j]*i+a[j]*j-a[j]-sum[j-1],化成这么一个形式,有i的相当为x,a[j]为斜率。

斜率显然具有单调性,这里就相当于y=kx+b,b可以是包含有j的函数。

具有单调性,找最小值,那么就画出实际的直线判断取值,这里注意乘一个负数要改变符号

ll check(ll a,ll b,ll c){
    ll a1,a2,a3,b1,b2,b3;
    a1=-A[a];b1=dp[a-1]-sum[a]+a*A[a];
    a2=-A[b];b2=dp[b-1]-sum[b]+b*A[b];
    a3=-A[c];b3=dp[c-1]-sum[c]+c*A[c];
    return (a2-a1)*(b3-b2)>=(b2-b1)*(a3-a2);//-斜率单调递减,+斜率单调增
}
ll f(ll x,ll b){
   return -A[b]*x-(1-b)*A[b]-sum[b-1]+dp[b-1];
}
int main(){
    ll i,j,k,f1,f2,f3,f4,t1,t2,t3,t4,n,m,T;
    //freopen("in.txt","r",stdin);
    //freopen("out1.txt","w",stdout);
    scanf("%lld\n",&T);
    while(T--){
        scanf("%lld %lld",&n,&m);
        for(i=1;i<=n;i++){
            scanf("%lld",&A[i]);
            sum[i]=sum[i-1]+A[i];
        }
        for(i=1;i<m+m;i++){
            dp[i]=sum[i]-i*A[1];
        }
        ll s,t;
        s=t=0;
        for(i=m+m;i<=n;i++){
            while(t>s+1&&check(list1[t-2],list1[t-1],i-m+1)){//t>s+1表示队列中至少两个数字            {
                t--; //这根线被踢出来
            }
            list1[t++]=i-m+1;
            while(t>s+1&&f(i,list1[s])>=f(i,list1[s+1])){ //单调队列没有长度限制,这里只是对比谁更优
            s++;//更优的话,之前的就不要了
            } //两根及其以上直线才有对比,因为目前这根已经存进去了
            dp[i]=sum[i]+f(i,list1[s]);
        }
        printf("%lld\n",dp[n]);
    }
    return 0;
}

插头dp(求能网格中能满足成环的种数):

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL LIM=300005,Has=299989;
LL n,m,e1,e2,las,now,tt;LL ans;
LL mp[15][15],bin[15],tot[2];LL js[2][LIM];
LL h[300005],a[2][LIM],ne[LIM];
//tot:状态总数,js:该状态的方案总数,a:各种状态
void ins(LL zt,LL num) {//卓越的哈希技术
    LL tmp=zt%Has+1;
    for(LL i=h[tmp];i;i=ne[i])
        if(a[now][i]==zt) {js[now][i]+=num;return;}
    ne[++tot[now]]=h[tmp],h[tmp]=tot[now];
    a[now][tot[now]]=zt,js[now][tot[now]]=num;
}
void work(){
    tot[now]=1,js[now][1]=1,a[now][1]=0;//初始状态为0,个数为1
    for(LL i=1;i<=n;++i){
        for(LL j=1;j<=tot[now];++j) //状态二进制最左边为最小一位,当换行的时候最后一个必定为0
        a[now][j]<<=1;//则换行的时候最左边那个新的线为0
        for(LL j=1;j<=m;++j) {
            las=now,now^=1;
            memset(h,0,sizeof(h)),tot[now]=0;
          //  cout <<"----------------------"<<endl;
            for(LL k=1;k<=tot[las];++k) {
                LL zt=a[las][k],b1=(zt>>(j-1))%2,b2=(zt>>j)%2;
               // cout <<i<<" "<<j<<"   status "<<zt <<" "<<b1<<" "<<b2<<endl;
                //提取关键格子上的两段轮廓线状态,zt表示上一个状态,
                LL num=js[las][k]; //这个状态的数量
                if(!mp[i][j]){
                if(!b1&&!b2)//1个障碍可以有的状态为原状态无右和下的插头
              //  cout <<"障碍" << "++" <<zt<< endl;
                ins(zt,num); //表示这里是一个障碍
                }else if(!b1&&!b2){//对于这个状态,只有右下的状态可以选择,但是可以选的条件是右与下均为空地
                if(mp[i+1][j]&&mp[i][j+1]){
                    ins(zt+bin[j-1]+bin[j],num);//转移为插头的状态j-1与j各出现一个
                    //cout << "00++ " <<zt+bin[j-1]+bin[j] << "   "<<num <<endl;
                }
                }else if(!b1&&b2){ //b1为0,但是b2存在
                    if(mp[i][j+1]){
                    ins(zt,num); //转移成连右边的插头
                   // cout << "01-right " <<zt << "   "<<num <<endl;
                    }
                    if(mp[i+1][j]){
                   // cout << "01-botton" << zt-bin[j]+bin[j-1] <<"   "<< num <<endl;
                    ins(zt-bin[j]+bin[j-1],num); //转移成下面的插头
                    }
                }else if(b1&&!b2) {
                    if(mp[i][j+1]){
                    ins(zt-bin[j-1]+bin[j],num);//由于左边转移到右边
                    //cout << "10-right" << zt-bin[j-1]+bin[j]  <<"   "<< num <<endl;
                    }if(mp[i+1][j]){
                   // cout << "10-botton" << zt <<" "<<num <<endl;
                    ins(zt,num);//转移到下面,状态不改变
                    }
                }else if(b1==1&&b2==1){ //因为少了一个,也就是2个联通块匹配在了一次,那么状态中有一个独立的联通块就改为不独立了
                    ins(zt-bin[j-1]-bin[j],num);
                    //cout << "11 " << zt-bin[j-1]-bin[j]  <<" "<<num <<endl;
                }
            }
        }
    }
}
char s1[1000];
int main(){
    //freopen("in.txt","r",stdin);
    //freopen("out1.txt","w",stdout);
    LL len1,len2,len3;
    LL T,i,j;
    scanf("%lld",&T);
    LL num=0;
    bin[0]=1;for(i=1;i<=14;++i)bin[i]=bin[i-1]<<1;
    while(T--){
    num++;
    scanf("%lld%lld",&n,&m);
    now=0;
    for(i=1;i<=n;i++){
        for(j=1;j<=m;j++){
            scanf("%lld",&mp[i][j]);
        }
    }
    work();
    LL res=0;
    for(LL k=1;k<=tot[now];++k) {
                LL zt=a[now][k];
                if(zt==0)res+=js[now][k];
    }
    printf("Case %lld: There are %lld ways to eat the trees.\n",num,res);
    }
    return 0;
}

多个单调队列,记录数组s[],t[]分别表示每个单调队列的位置:

#include<bits/stdc++.h>
using namespace std;
const int maxn=2005;
int min1[maxn];
struct tt1{
    int key,r;
};
tt1 q2[maxn][2005];
int dp[2005][2005];
vector<int>q3[maxn];
int s[2005];
int t[2005];
int main(){
    int i,j,k,f1,f2,f3,f4,t1,t2,t3,t4,n,m;
    int T,K,num;
    //freopen("in.txt","r",stdin);
    scanf("%d",&T);num=0;
    while(T--){
    scanf("%d %d %d",&n,&m,&K);
    memset(s,0,sizeof(s));
    memset(t,0,sizeof(t));
    memset(min1,0,sizeof(min1));
    for(i=1;i<=K+1;i++)
        q2[i][0].key=q2[i][0].r=0;
    for(i=1;i<=K;i++)
        for(j=1;j<=n;j++)
            dp[i][j]=0;
    for(i=1;i<=n;i++){
        q3[i].clear();
    }
    num++;
    printf("Case #%d: ",num);

    for(i=1;i<=m;i++){
        scanf("%d %d",&t1,&t2);
        q3[t1].push_back(t2);
    }
    int min2=0;
    int res=0;
    for(i=1;i<=n;i++){
        for(j=0;j<q3[i].size();j++){
            int f1=q3[i][j];
            if(f1<min2)continue;
            min2=f1;
            for(k=K;k>=1;k--){
               dp[k][f1]=max(dp[k][f1],min1[k-1]+f1-i+1);
                dp[k][f1]=max(dp[k][f1],q2[k-1][s[k-1]].key-max(0,q2[k-1][s[k-1]].r-i+1)+f1-i+1);
                res=max(res,dp[k][f1]);
                if(t[k]==0||dp[k][f1]>=q2[k][t[k]-1].key){
                while(t[k]>s[k]&&q2[k][t[k]-1].key-max(0,q2[k][t[k]-1].r-i+1)
                    <=dp[k][f1]-max(0,f1-i+1)){
                   t[k]--;
                    }
                    q2[k][t[k]].key=dp[k][f1];
                    q2[k][t[k]].r=f1;
                    t[k]++;
                }
            }
        }
        for(j=1;j<=K;j++){
            min1[j]=max(min1[j],dp[j][i]);
        }
        f2=i+1;
        for(k=1;k<=K;k++)
            while(t[k]>t[k]&&q2[k][s[k]].key-max(0,q2[k][s[k]].r-f2+1)
                  <=q2[k+1][s[k+1]].key-max(0,q2[k+1][s[k+1]].r-f2+1))
            s[k]++;
    }
    printf("%d\n",res);
    }
    return 0;
}

数位DP板子(不要4和62):

ll dfs(ll len,ll if4,ll if6,bool limit){
    if(if4) return 0;
    if(len==0)return 1;
    if(!limit&&dp[len][if6])return dp[len][if6]; //,没有限制长度并且后面记录过了
    int up_bound,cnt=0;
    if(limit)
    up_bound=digit[len];
    else
    up_bound=9;
    for(int i=0;i<=up_bound;i++){
        if(if6&&i==2)continue; //处理掉多的部分
        cnt+=dfs(len-1,i==4, i==6,limit&&i==up_bound);
    }
    if(!limit)dp[len][if6]=cnt; //注意这里需要不为limit,不然后面会有限制
    return cnt;
}
ll solve(ll x){
    int k=0;
    while(x){
        digit[++k]=x%10;
        x/=10;
    }
    dfs(k,false,false,true);
}

数位DP(hdu6148):

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+7;
typedef long long ll;
const int mod=1e9+7;
int dp[maxn][13][2];//目前到第i位,上一位为j,目前是只能往上1,上下均可为0
int digit[maxn];
int dfs(int len,int num1,int mark,bool limit){
    if(len==0)return 1; //不管是什么数字进来,这里的结果就是1
    if(!limit&&dp[len][num1][mark])return dp[len][num1][mark]; //,没有限制长度并且后面记录过了
    int up_bound,cnt=0;
    up_bound=limit?digit[len]:9;
    for(int i=0;i<=up_bound;i++){
        if(num1==11&&i==0){ //这么写是把前导0特殊处理
        cnt=(cnt+dfs(len-1,11,0,limit&&i==up_bound))%mod;
        continue;
        }
        if(i<num1&&mark)continue;
        if(i<=num1)
        cnt+=dfs(len-1,i,mark,limit&&i==up_bound);
        else
        cnt+=dfs(len-1,i,1,limit&&i==up_bound);
        cnt%=mod;
    }
    if(!limit)dp[len][num1][mark]=cnt; //注意这里需要不为limit,不然后面会有限制
    return cnt;
}
char s1[maxn];
int solve(){
    int k=0;
    int len1=strlen(s1);
    for(int i=1;i<=len1;i++){
        digit[len1-i+1]=s1[i-1]-'0';
    }
    return dfs(len1,11,0,true);
}
int main(){//V形山
    int i,j,k,f1,f2,f3,f4,t1,t2,t3,t4,n,m,T;
    //freopen("in.txt","r",stdin);
    cin >>T;
    while(T--){
    //memset(dp,0,sizeof(dp)); //对于数位dp,有时候这里可以省略
    cin >> s1;
    int res=solve();
    cout <<(res+mod-1)%mod<<endl;
    }
}

区间DP(四边形不等式):

满足两个性质
1、区间包含的单调性:如果对于 i≤i'<j≤j',有 w(i',j)≤w(i,j'),那么说明w具有区间包含的单调性。(可以形象理解为如果小区间包含于大区间中,那么小区间的w值不超过大区间的w值)
2、四边形不等式:如果对于 i≤i'<j≤j',有 w(i,j)+w(i',j')≤w(i',j)+w(i,j'),我们称函数w满足四边形不等式。(可以形象理解为两个交错区间的w的和不超过小区间与大区间的w的和)
下面给出两个定理:
    1、如果上述的 w 函数同时满足区间包含单调性和四边形不等式性质,那么函数 m 也满足四边形不等式性质
       我们再定义 s(i,j) 表示 m(i,j) 取得最优值时对应的下标(即 i≤k≤j 时,k 处的 w 值最大,则 s(i,j)=k)。此时有如下定理

    2、假如 m(i,j) 满足四边形不等式,那么 s(i,j) 单调,即 s(i,j)≤s(i,j+1)≤s(i+1,j+1)。

然后存一个s[i][j]表示这个区间最优值的下标,然后以后每次就k就不用枚举了
using namespace std; 
int dp1[105][105];
int dp2[105][105];
int sum[105];
int qq[105];
int s[105][105];
int main(){
    freopen("in.txt","r",stdin);
    int i,j,k,f1,f2,f3,f4,t1,t2,t3,t4,n,m;
    cin >> n;
    for(i=1;i<=n;i++){
        cin >> sum[i];
        sum[i]+=sum[i-1];
    }
    int len1;
    for(i=0;i<=n+1;i++)s[i][i]=i;
    for(len1=1;len1<n;len1++){
        for(i=1;i<n;i++){
            j=i+len1;
            if(j>n)break;
            dp1[i][j]=1e9+7;
            for(k=s[i][j-1];k<=s[i+1][j];k++){
                if(dp1[i][j]>dp1[i][k]+dp1[k+1][j]){
                    dp1[i][j]=dp1[i][k]+dp1[k+1][j];
                    s[i][j]=k;
                }
            }
            dp1[i][j]+=sum[j]-sum[i-1];
        }
    }
    for(i=0;i<=n+1;i++)s[i][i]=i;
    for(len1=1;len1<n;len1++){
        for(i=1;i<n;i++){
            j=i+len1;
            if(j>n)break; //总长度
            //for(k=i;k<j;k++)//枚举中间的k
            //dp[i][k],dp[k+1][j]
            for(k=s[i][j-1];k<=s[i+1][j];k++){
                if(dp2[i][j]<dp2[i][k]+dp2[k+1][j]){
                    dp2[i][j]=dp2[i][k]+dp2[k+1][j];
                    s[i][j]=k;
                }
            }
            dp2[i][j]+=sum[j]-sum[i-1];
        }
    }
    cout << dp1[1][n] << endl;
    cout << dp2[1][n] << endl;
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值