DP总结(优化等)

留两个坑:1、测试51or 2、杂题3最大前缀和 (3、测试48group)

树DP感觉不像传统DP,所以分着写了。详见树总结。

欢迎大家在评论区提供新的方法!

FIR:优化

一、决策点信息维护

  1:单调性:单调队列、栈。

        常见斜率,凸壳。单调性可以二分。

    T:1、测试50施工。单调栈。

#include<bits/stdc++.h>
#define F(i,a,b) for(rg int i=a;i<=b;++i)
#define rg register
#define LL long long
#define il inline
#define pf(a) printf("%d ",a)
#define PF(a) printf("%lld ",a)
#define phn puts("")
using namespace std;
#define int LL 
int read();
/*
之前是(double)(b/(a*2))+0.5;
改成(double)b/(a*2)+0.5
或者1.0*b/(a*2)就A了。
*/
#define N 1000010
int n;LL c;
int h[N];
LL f[N];
const LL inf=1e15;
LL s1[N],s2[N];
int sta[N],top;
il LL cal(int i,int j,int mh){
    LL a=(i-j-1),b=2ll*(s1[i-1]-s1[j]),u=s2[i-1]-s2[j];
    if(j!=0){
        b+=c;u+=c*h[j];
    }
    if(i!=n+1){
        b+=c;u+=c*h[i];
    }
    LL t=1.0*b/(a*2)+0.5;
    t=max(1ll*mh,t);
    t=min(t,1ll*min(h[i],h[j]));
    return f[j]+t*t*a-t*b+u;
}
signed main(){
    n=read();c=read();
    F(i,1,n)h[i]=read();
    h[0]=h[n+1]=2e6;
    F(i,1,n){
        s1[i]=s1[i-1]+h[i];
        s2[i]=s2[i-1]+1ll*h[i]*h[i];
    }
    sta[top=1]=0;
    f[1]=0;sta[++top]=1;
    LL w;
    //PF(cal(4,2,h[3]));phn;return 0;
    F(i,2,n+1){
        f[i]=i==n+1?f[i-1]:f[i-1]+c*abs(h[i]-h[i-1]);
        while(top>1&&h[sta[top]]<=h[i]){
            w=cal(i,sta[top-1],h[sta[top]]);
            f[i]=min(f[i],w);
            --top;
        }
        sta[++top]=i;
    }
//    F(i,1,n+1)PF(f[i]);phn;
    printf("%lld\n",f[n+1]);
}
il int read(){
    int s=0,f=0;char ch;
    while(ch=getchar(),ch=='-'?f=1:0,!isdigit(ch));
    for(;isdigit(ch);s=s*10+(ch^48),ch=getchar());
    return f?-s:s;
}
/*
g++ 1.cpp -g
./a.out
4 2
1 3 2 4
*/
View Code

      2、测试51function。离线,单调栈维护斜率下凸壳,二分。  

#include<bits/stdc++.h>
#define F(i,a,b) for(rg int i=a;i<=b;++i)
#define rg register
#define LL long long
#define il inline
#define pf(a) printf("%lld ",a)
#define phn puts("")
using namespace std;
#define int LL 
#define N 500010
int read();
int n,que;
int s[N],a[N],c[N];
int min(int x,int y){return x<y?x:y;}
int max(int x,int y){return x>y?x:y;}
struct Q{
    int x,y,id;
    friend bool operator <(const Q& a,const Q& b){return a.y<b.y||(a.y==b.y&&a.x<b.x);}
}b[N];
int sta[N],top,ans[N];
double get(int i,int j){
    return 1.0*(c[j]-c[i])/(a[i]-a[j]);
}
void push(int x){
    while(top&&a[sta[top]]>=a[x])--top;
    while(top>1&&get(x,sta[top])>=get(sta[top],sta[top-1]))--top;
    /** 这里是凸包的维护。第二句:使交点在栈内单调递减。因为画图发现交点递减才成为凸包,否则上一条线可以去掉
    如果不去掉,决策会使用上一条线,导致挡住原本更优的决策
    所以下凸包的维护:1、斜率递增,pop掉斜率比他大的
              2、交点递减,防止不优的线影响决策
    */
    sta[++top]=x;
}
/** 可以证明,超过边界的不合法的决策点不优*/
int solve(int x,int y){
    int l=1,r=top,mid;
    while(l<r){
        mid=l+r>>1;
    //    if(y-sta[mid]+1>x){l=mid+1;continue;}
        if(get(sta[mid],sta[mid+1])>x-y)l=mid+1;
        else r=mid;
    }    
    l=sta[l];
//    pf(l);pf(x);pf(y);phn;
    /*if(x==100&&y==7){
        F(i,1,top)pf(a[sta[i]]);phn;while(1);
    }*/
    return s[y]-s[l]+(x-y+l)*a[l];
}
signed main(){
//    freopen("function2.in","r",stdin);    freopen("1.out","w",stdout);
    n=read(); F(i,1,n)s[i]=s[i-1]+(a[i]=read()),c[i]=i*a[i]-s[i];
    que=read(); F(i,1,que)b[i]=(Q){read(),read(),i};
    sort(b+1,b+que+1);
    int p=1;
    while(b[p].y==1){
        ans[b[p].id]=a[1]*b[p].x;++p;
    }    
    sta[top=1]=1;
    /**     s[y]0-s[i]+(x-y+i)*a[i] 
        先判两个端点。
    */
    int i,x,y;
    F(k,2,n){    
        push(k);
        if(top==1){
            i=sta[1];
            while(b[p].y==k){
                ans[b[p].id]=a[k]*b[p].x;
                ++p;
            }
            continue;
        }
        while(b[p].y==k){
            ans[b[p].id]=solve(b[p].x,k);
            ++p;
        }
    //    push(k);
    }    
    F(i,1,que)printf("%lld\n",ans[i]);
}
int read(){
    int s=0,f=0;char ch;
    while(ch=getchar(),ch=='-'?f=1:0,!isdigit(ch));
    for(;isdigit(ch);s=s*10+(ch^48),ch=getchar());
    return f?-s:s;
}
/*
g++ 1.cpp -g
./a.out
g++ dp.cpp -g
./a.out
10
10 9 1 7 4 6 8 5 2 3
10
4 2
100 2
6 3
1 4
3 5
1 7
100 7 
5 8
2 9
100 10
*/
View Code

  2:数据结构。线段树。用于维护决策点/dp值。维护若干相同的操作。

        进阶,可以线段树合并维护DP。

二、数学优化

  1、NTT

  2、矩阵,结合律,快速幂性质。

    T:测试52涂色游戏 组合+矩阵快速幂

  3、数论性质优化

  如:原根优化为循环矩阵

三、改变状态定义

  1、之前一道求数列乘积取模方案数:gcd(等等)相同函数值相同,减少状态数。

  2、测试33赤壁情:绝对位置变为相对位置

#include<bits/stdc++.h>
#define rg register
#define F(i,a,b) for(rg int i=a;i<=b;++i)
#define LL long long
#define il inline
#define pf(a) printf("%d ",a)
#define pd(a) printf("%.3lf ",a)
#define phn puts("")
using namespace std;
int read();
/*
Lxt
*/
int n,m,bmw;
il void out(double ans){
    int k=bmw;
    if(k==0)printf("%.0f\n",ans);
    else if(k==1)printf("%.1lf\n",ans);
    else if(k==2)printf("%.2lf\n",ans);
    else if(k==3)printf("%.3lf\n",ans);
    else if(k==4)printf("%.4lf\n",ans);
    else if(k==5)printf("%.5lf\n",ans);
    else if(k==6)printf("%.6lf\n",ans);
    else if(k==7)printf("%.7lf\n",ans);
    else if(k==8)printf("%.8lf\n",ans);
}
double f[2][65][8005][4];
#define dp f[p][j][k][l]
il void work1(){
    f[n&1][0][0][0]=1;
    rg int p=0;
    //递推
    for(int i=n;i>=1;--i){
        p=i&1;
        F(j,0,52){
            F(k,0,7505){
                F(l,0,2){
                    f[!p][j+1][k+i*2][l]+=dp*(j+1-l);
                    f[!p][j][k][l]+=dp*(j*2-l);
                    if(l<2){
                        f[!p][j+1][k+i][l+1]+=dp*(2-l);
                        if(k>=i)f[!p][j][k-i][l+1]+=dp*(2-l);
                    }
                    if(j>1&&k>=i*2)f[!p][j-1][k-i*2][l]+=dp*(j-1);
                    /*if(dp>1e-8){
                        if(j>1&&k>=i*2)printf("%d %d %d %d %d %.3f\n",i,!p,j-1,k-i*2,l,f[!p][j-1][k-i*2][l]);
                    }*/
                    /*
                    与其他段交点:0:新段 1:延长 2:连接
                    0和1特殊讨论边界点
                    */
                    dp=0;
                }
            }
        }
    }
    double ans=0;p=0;
    const int lxt=n*n/2;
    F(k,m,lxt){
        ans+=f[0][1][k][2];
    }
//    F(k,2,3)pd(f[0][1][k][2]);
    F(i,2,n)ans/=i;
    out(ans);
}
int floor(__float128 x){
    for(int i=9;i>=0;--i)if(x>=i)return i;
}
void print__float128(__float128 x,int ws){
    int sta[55];sta[0]=0;
    for(int i=1;i<=ws;++i)x*=10,sta[i]=floor(x),x-=floor(x);
    x*=10;if(floor(x)>=5)sta[ws]++;
    for(int i=ws;i;--i)if(sta[i]==10)sta[i]=0,sta[i-1]++;
    printf("%d.",sta[0]);
    for(int i=1;i<=ws;++i)putchar(sta[i]+48);
    puts("");
}
__float128 g[2][28][2502][4];
#define hp g[p][j][k][l]
il void work_30(){
    g[n&1][0][0][0]=1;
    rg int p=0;
    //递推
    for(int i=n;i>=1;--i){
        p=i&1;
        F(j,0,26){
            F(k,0,2002){
                F(l,0,2){
                    g[!p][j+1][k+i*2][l]+=hp*(j+1-l);
                    g[!p][j][k][l]+=hp*(j*2-l);
                    if(l<2){
                        g[!p][j+1][k+i][l+1]+=hp*(2-l);
                        if(k>=i)g[!p][j][k-i][l+1]+=hp*(2-l);
                    }
                    if(j>1&&k>=i*2)g[!p][j-1][k-i*2][l]+=hp*(j-1);
                    hp=0;
                }
            }
        }
    }
    __float128 ans=0;p=0;
    const int lxt=n*n/2;
    F(k,m,lxt){
        ans+=g[0][1][k][2];
    }
//    F(k,2,3)pd(g[0][1][k][2]);
    F(i,2,n)ans/=i;
    print__float128(ans,bmw);
}
int main(){
    n=read();m=read();bmw=read();
    if(bmw<=8)work1();
    else work_30();
}
il int read(){
    int s=0,f=0;rg char ch;
    while(ch=getchar(),ch=='-'?f=1:0,!isdigit(ch));
    for(;isdigit(ch);s=s*10+(ch^48),ch=getchar());
    return f?-s:s;
}
/*
g++ 1.cpp -g
time ./a.out
50 200 30
*/
/*

*/
View Code

  3、测试49折射:按照另一种顺序,时间复杂转移简单变为时间简单转移复杂

#include<bits/stdc++.h>
#define F(i,a,b) for(rg int i=a;i<=b;++i)
#define rg register
#define LL long lnog
#define il inline
#define pf(a) printf("%d ",a)
#define phn puts("")
using namespace std;
int read();
/*
数组尽量开大
5000~5400有3个点
*/
int n;
const int mod=1e9+7;
#define N 6001
struct node{
    int x,y;
    friend bool operator<(const node &a,const node &b){
        return a.x<b.x;
    }
}s[N];
int b[N];
il int MO(const int x){return x<mod?x:x-mod;}
int f[6002][2];
int main(){
    n=read();
    F(i,1,n){
        s[i].x=read();
        s[i].y=read();
    }
    sort(s+1,s+n+1);
    int ans=0;
    rg int y,w;    
    F(i,1,n){
        f[i][0]=f[i][1]=1;
        for(int j=i-1;j>=1;--j){
            if(s[j].y<s[i].y){
                f[i][0]=MO(f[i][0]+f[j][1]);
            }
            else{
                f[j][1]=MO(f[j][1]+f[i][0]);
            }
        }
    }
    F(i,1,n){
        ans=MO(ans+f[i][0]);ans=MO(ans+f[i][1]);
    //    printf("%d %d\n",f[i][0],f[i][1]);
    }
    ans=(ans-n+mod)%mod;
    printf("%d\n",ans);
}
il int read(){
    int s=0,f=0;char ch;
    while(ch=getchar(),ch=='-'?f=1:0,!isdigit(ch));
    for(;isdigit(ch);s=s*10+(ch^48),ch=getchar());
    return f?-s:s;
}
/*
g++ 2.cpp -g
./a.out
4
2 2
3 1
1 4
4 3
*/
View Code

四、网格/二维

  1、网格DP

    插头DP是一类网格DP。

    网格DP的一般做法是枚举n*m,扫一行,继承上一行信息。

    复杂度n*m*状态数。

    一般搭配状压使用。

    T:测试59的部分分(全场两人拿到这个子任务,另外一个是正解)

#include<bits/stdc++.h>
#define F(i,a,b) for(int i=a;i<=b;++i)
#define LL long long
#define pf(a) printf("%d ",a)
#define phn puts("")
using namespace std;
int n,all;
#define N 100010
int a[N],b[N];
LL f[2][2][1000010];
const int mod=1e9+7;
void cal(int x,int y){
    int p=((x-1)*n+y)&1,lm=min(a[x],b[y]);
    int u,v;
    for(int i=all;i>=0;--i){
        (f[p][0][i]+=f[!p][0][i]*lm)%=mod;
        (f[p][1][i]+=f[!p][1][i]*lm)%=mod;
        if(a[x]==b[y]){
            (f[p][1][i|(1<<(y-1))]+=f[!p][0][i]+f[!p][1][i])%=mod;
        }
        else if(a[x]<b[y]){
            (f[p][1][i]+=f[!p][0][i]+f[!p][1][i])%=mod;
        }
        else if(a[x]>b[y]){
            (f[p][0][i|(1<<(y-1))]+=f[!p][0][i])%=mod;
            (f[p][1][i|(1<<(y-1))]+=f[!p][1][i])%=mod;
        }
        f[!p][0][i]=f[!p][1][i]=0;
    }
}
void work1(){
    f[0][1][0]=1;all=(1<<n)-1;
    int p;
    F(i,1,n){
        p=((i-1)*n)&1;
        F(j,0,all){
            f[p][0][j]=f[p][1][j];f[p][1][j]=0;
        }
        F(j,1,n){
            cal(i,j);
        }
    }
    p=(n*n)&1;
    printf("%lld\n",f[p][1][all]);
}
int qpow(LL x,int k){LL s=1;for(;k;x=x*x%mod,k>>=1)if(k&1)s=s*x%mod;return s;}
int main(){
    scanf("%d",&n);F(i,1,n)scanf("%d",&a[i]);F(i,1,n)scanf("%d",&b[i]);
    if(n<=16)work1();
    else{
        LL ans=1;
        F(i,1,n){
            LL w=(qpow(i+1,n-i)-qpow(i,n-i)+mod)%mod;
            w=w*w%mod;
            ans=(w*i+qpow(i+1,2*(n-i)))%mod*ans%mod;
        }
        printf("%lld\n",ans);
    }
}
/*
g++ 2.cpp -g
./a.out
3
1 2 3
1 2 3
*/
View Code

 

  2、插头,轮廓线

   插头DP是网格+轮廓线。

   而轮廓线本身是一个独立的技巧。

   轮廓线可用Hash表优化状态数。

   来自学长的两道插头DP题的模板:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define F(i,a,b) for(int i=a;i<=b;++i)
#define LL long long
#define rg register
#define phn printf("\n")
#define pf(a) printf("%d ",a)
#define PF(a) printf("%lld ",a)
#define il inline
using namespace std;
int read();
int n,m;
const int cube=(int)1e9,mod=10007;
#define me(a) memset(a,0,sizeof(a))
struct data{
    int bit[6];
    il void Clear(){bit[0]=bit[1]=bit[2]=bit[3]=bit[4]=bit[5]=0;}
    data(){Clear();}
    il void Set(int x){Clear();while(x){bit[++bit[0]]=x%cube;x/=cube;}}
    il int &operator [](int x){return bit[x];}
    il void print(){
        printf("%d",bit[bit[0]]);
        for(int i=bit[0]-1;i>=1;--i)printf("%09d",bit[i]);phn;
    }
    il data operator + (data b){
        data c;c.Clear();
        c[0]=max(bit[0],b[0])+1;
        F(i,1,c[0]){
            c[i]+=bit[i]+b[i];c[i+1]=c[i]/cube;c[i]%=cube;
        }
        while(!c[c[0]])--c[0];
        return c;
    }
    il void operator +=(data b){*this=*this+b;}
    il void operator = (int x){Set(x);} 
}ANS;
struct hash{
    data val[mod];
    int head[mod],size,sta[mod],fir[mod];
    il void init(){
        me(val);me(sta);me(fir);me(head);size=0;
    }
    il data &operator [] (const int &state){
        int pos=state%mod,i;
        for(i=head[pos];i&&sta[i]!=state;i=fir[i]);
        if(!i){sta[++size]=state;fir[size]=head[pos];head[pos]=size;i=size;}
        return val[i];
    }    
}f[2];
il int find(int sta,int id){return (sta>>((id-1)<<1))&3;}
il void set(int &sta,int id,int val){
    id=(id-1)<<1;sta|=3<<id;sta^=3<<id;sta|=val<<id;
}    
il int link(int sta,int pos){
    int cnt=0,g=(find(sta,pos)==1)?1:-1;
    for(int i=pos;i&&i<=m+1;i+=g){   //??m+1
        int plug=find(sta,i);
        if(plug==1)++cnt;
        else if(plug==2)--cnt;
        if(cnt==0)return i;
    }
    return -1;
}
il void cal(int x,int y){
    int now=((x-1)*m+y)&1,last=now^1,tot=f[last].size;
    f[now].init();
    F(i,1,tot){
        int sta=f[last].sta[i];data val=f[last].val[i];
        int p1=find(sta,y),p2=find(sta,y+1);
        if(link(sta,y)==-1||link(sta,y+1)==-1)continue;
    //    pf(x);pf(y);phn;
        if(!p1&&!p2){
            if(x!=n&&y!=m){
                set(sta,y,1);set(sta,y+1,2);f[now][sta]+=val;
            }        
        }
        else if(p1&&!p2){
            if(x!=n)f[now][sta]+=val;
            if(y!=m)set(sta,y,0),set(sta,y+1,p1),f[now][sta]+=val;
        }
        else if(p2&&!p1){
            if(y!=m)f[now][sta]+=val;
            if(x!=n)set(sta,y+1,0),set(sta,y,p2),f[now][sta]+=val;
        }
        else {
            if(p1==1&&p2==2){if(x==n&&y==m){ANS+=val;}}
            else if(p1==2&&p2==1){
                set(sta,y,0);set(sta,y+1,0);f[now][sta]+=val;
            }
            else if(p1==1&&p2==1){
                int pos=link(sta,y+1);
                set(sta,y,0);set(sta,y+1,0);set(sta,pos,1);
                f[now][sta]+=val;
            }
            else if(p1==2&&p2==2){
                int pos=link(sta,y);
                set(sta,y,0);set(sta,y+1,0);set(sta,pos,2);
                f[now][sta]+=val;
            }
        }
    }
}
int main(){
    n=read();m=read();
    if(n==1||m==1){puts("1");return 0;}
    if(m>n){m^=n^=m^=n;}//
    f[0].init();f[0][0]=1;
    F(i,1,n){
        F(j,1,m)cal(i,j);
        if(i!=n){
            int now=(i*m)&1,tot=f[now].size;
            F(j,1,tot)f[now].sta[j]<<=2;
        }
    }
    ANS+=ANS;ANS.print();
}
il int read(){
    int s=0,f=0;char ch;
    while(ch=getchar(),ch=='-'?f=1:0,ch<'0'||ch>'9');
    while(ch>='0'&&ch<='9'){s=s*10+(ch^48);ch=getchar();}
    return s;
}
/*
g++ 1.cpp -g
./a.out
2 2
*/
邮递员,(手写高精,hash表)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define F(i,a,b) for(int i=a;i<=b;++i)
#define LL long long
#define rg register
#define cri const rg int
#define phn printf("\n")
#define pf(a) printf("%d ",a)
#define PF(a) printf("%lld ",a)
#define il inline
using namespace std;
int read();
int n,m;
//易错索引:h58,i<=m+1(轮廓线是m+1)
const int cube=(int)1e9,mod=30007,NX=120007;
#define ME(a) memset(a,0,sizeof(a))
LL ans;
LL valu[2][NX];
int head[2][mod],size[2],state[2][NX],fir[2][NX];
il void init(int g){
    memset(head[g],0,sizeof(head[g]));size[g]=0;//me(sta);me(fir);
}
il void add (const int &sta,const LL w,const int g){
    int pos=sta%mod,i;
    for(i=head[g][pos];i;i=fir[g][i]){
        if(state[g][i]==sta){valu[g][i]+=w;return;}
    }
    state[g][++size[g]]=sta;fir[g][size[g]]=head[g][pos];head[g][pos]=i=size[g];valu[g][i]=w;
}    
il int find(rg int sta,cri id){return (sta>>((id-1)<<1))&3;}
il void set(rg int &sta,rg int id,int val){
    id=(id-1)<<1;sta|=3<<id;sta^=3<<id;sta|=val<<id;
}    
il int link(cri sta,cri pos){
    int cnt=0,g=(find(sta,pos)==1)?1:-1;
    for(int i=pos;i&&i<=m+1;i+=g){   //m+1:
        int plug=find(sta,i);
        if(plug==1)++cnt;
        else if(plug==2)--cnt;
        if(cnt==0)return i;
    }
    return -1;
}
int a[42][42],endx,endy;
bool comp;
il void cal(const int x,const int y){
    int now=((x-1)*m+y)&1,last=now^1,tot=size[last];
    init(now);
    F(i,1,tot){
        rg int sta=state[last][i];LL val=valu[last][i];
        cri p1=find(sta,y),p2=find(sta,y+1);
    //    if(link(sta,y)==-1||link(sta,y+1)==-1)continue;
    //    pf(x);pf(y);phn;
        if(a[x][y]){
            if(!p1&&!p2)add(sta,val,now);
            continue;
        }
        if(!p1&&!p2){
            if(x!=n&&y!=m&&!a[x+1][y]&&!a[x][y+1]){
                set(sta,y,1);set(sta,y+1,2);add(sta,val,now);
            }        
        }
        else if(p1&&!p2){
            if(x!=n&&!a[x+1][y])add(sta,val,now);
            if(y!=m&&!a[x][y+1])set(sta,y,0),set(sta,y+1,p1),add(sta,val,now);
        }
        else if(p2&&!p1){
            if(y!=m&&!a[x][y+1])add(sta,val,now);
            if(x!=n&&!a[x+1][y])set(sta,y+1,0),set(sta,y,p2),add(sta,val,now);
        }
        else {
            if(p1==1&&p2==2){if(x==endx&&y==endy){ans+=val;comp=1;}}
            else if(p1==2&&p2==1){
                set(sta,y,0);set(sta,y+1,0);add(sta,val,now);
            }
            else if(p1==1&&p2==1){
                set(sta,link(sta,y+1),1);set(sta,y,0);set(sta,y+1,0);
                add(sta,val,now);
            }
            else if(p1==2&&p2==2){
                set(sta,link(sta,y),2);set(sta,y,0);set(sta,y+1,0);
                add(sta,val,now);
            }
        }
    }
}
int main(){
    n=read();m=read();
//    if(n==1||m==1){puts("1");return 0;}
//    if(m>n){m^=n^=m^=n;}//
    char ch[20];
    F(i,1,n){scanf("%s",ch+1);F(j,1,m){if(ch[j]=='*')a[i][j]=1;else {endx=i;endy=j;}}}
    init(0);add(0,1,0);
    F(i,1,n){
        F(j,1,m){
            cal(i,j);
            if(comp==1){
                printf("%lld",ans);return 0;
            }
        }
        if(i!=n){
            int now=(i*m)&1,tot=size[now];
            F(j,1,tot)state[now][j]<<=2;
        }
    }
    printf("%lld",ans);
}
il int read(){
    int s=0,f=0;char ch;
    while(ch=getchar(),ch=='-'?f=1:0,ch<'0'||ch>'9');
    while(ch>='0'&&ch<='9'){s=s*10+(ch^48);ch=getchar();}
    return s;
}
/*
g++ 1.cpp -g
./a.out
4 4
**..
....
....
..**
*/
Ural 1519 Formula 1,很多分类讨论

 

 

五、小技巧

  比较重要的:

        1、哈希表。对于可证明的状态数比较小,但是范围比较大的。

          DP可优化时间空间。记忆化dfs可优化空间。

          常用于状压、轮廓线。

          T:测试53 T2 状压期望

          (dfs写在hash里超帅的)

#include<bits/stdc++.h>
#define F(i,a,b) for(int i=a;i<=b;++i)
#define LL long long
#define PF(a) printf("%.6lf ",a)
#define pf(a) printf("%d ",a)
#define phn puts("")
using namespace std;
int read();
int n,m;
char c[50];
const int mod=533317;
inline double max(const double&a,const double&b){return a>b?a:b;}
struct Hash{
    #define M 20000010
    double f[M];int vis[M];
    int head[32][mod],fir[M],to[M],cnt;
    inline int get(const int &x,const int &y){
        int t=y%mod;
        for(int i=head[x][t];i;i=fir[i])
            if(to[i]==y)return i;
        to[++cnt]=y;fir[cnt]=head[x][t];
        return head[x][t]=cnt;
    }
    int chg(int j,int pos){
        int z=j&((1<<pos)-1);
        return (j>>(pos+1)<<pos)|z;
    }
    double dfs(int i,int j){
        if(i==n-m)return 0;
        int t=get(i,j);
        if(vis[t])return f[t];
        vis[t]=1;
        F(k,0,i-1){
            f[t]+=max(dfs(i-1,chg(j,k))+((j>>k)&1),
                     dfs(i-1,chg(j,i-1-k))+((j>>(i-1-k))&1));
        }
        f[t]/=i;
        return f[t];
    }
}q;
int main(){
    //pf(chg((1<<5)-1-(1<<3),2));
    n=read();m=read();
    scanf("%s",c+1);
    int b=0;
    F(i,1,n)c[i]=='W'?(c[i]=1,b|=(1<<(i-1))):c[i]=0;
    double ans=q.dfs(n,b);
    printf("%.6lf\n",ans);
}
int read(){
    int s=0,f=0;char ch;
    while(ch=getchar(),ch=='-'?f=1:0,!isdigit(ch));
    for(;isdigit(ch);s=s*10+(ch^48),ch=getchar());
    return f?-s:s;
}
/*
g++ 2.cpp -g 
./a.out
4 2 WBWB
*/
View Code

        2、拆式子。如拆abs(讨论大小关系)、min

  不很重要的:1、前缀和

        2、倒着扫

SEC:写法

一、概率期望类

  一般倒着转移。因为一般初状态一定,末状态有很多

  1、最优性期望:

    T:测试53T2。最优策略,直接在dfs时取两个下一步状态的max

  2、期望性质:

    测试32 chemistry:  (osu树上高次版)

    E(x+y)=E(x)+E(y)

    E(x&&y)=E(x)*E(y)

    E((x+y)^j)=sigma C(j,k)*E(x^k)*E(y^(j-k))

    把多次方的期望拆分。考虑两两合并产生的贡献。

    详见之前写的题解。

#include<bits/stdc++.h>
#define F(i,a,b) for(rg int i=a;i<=b;++i)
#define rg register
#define pf(a) printf("%lld ",a)
#define phn puts("")
#define LL long long
#define il inline
using namespace std;
#define int LL
int read();
#define N 200010
int n,m,p,q;
const int mod=1e9+7;
int a[N],f[N][12],g[N][12],C[15][15];
int to[N<<1],fir[N<<1],head[N<<1],cnt;
il int qpow(int x,int k){int s=1;for(;k;x=x*x%mod,k>>=1)if(k&1)s=s*x%mod;return s;}
il void add(int x,int y){
    to[++cnt]=y;fir[cnt]=head[x];head[x]=cnt;
}
void dfs(int x,int fa){
    g[x][0]=1;f[x][0]=1;
    g[x][1]=p*a[x]%mod;f[x][1]=p*a[x]%mod;
    F(i,2,m){
        g[x][i]=g[x][i-1]*a[x]%mod;
        f[x][i]=f[x][i-1]*a[x]%mod;
    }
    for(int i=head[x];i;i=fir[i]){
        int v=to[i];
        if(v==fa)continue;
        dfs(v,x);
        for(int j=m;j>=1;--j){
            (f[x][j]+=f[v][j])%=mod;
            F(k,1,j-1){
                (f[x][j]+=C[j][k]*g[x][k]%mod*g[v][j-k]%mod)%=mod;
            }
            //f[x][j]%=mod;
        }
        for(int j=m;j>=1;--j){
            (g[x][j]+=p*g[v][j])%=mod;
            F(k,1,j-1){
                (g[x][j]+=C[j][k]*g[x][k]%mod*g[v][j-k]%mod)%=mod;
            }
            //g[x][j]%=mod;
        }
    }
}
signed main(){
    C[0][0]=1;
    F(i,1,12){
        C[i][0]=1;
        F(j,1,i)C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
    }
    n=read();m=read();p=read();q=read();
    p=p*qpow(q,mod-2)%mod;
    int x,y;
    F(i,1,n)a[i]=read();
    F(i,2,n){
        x=read();y=read();add(x,y);add(y,x);
    }
    dfs(1,0);
    printf("%lld\n",f[1][m]);
}
il int read(){
    int s=0;rg char ch;
    while(ch=getchar(),!isdigit(ch));
    for(;isdigit(ch);s=s*10+(ch^48),ch=getchar());
    return s;
}
/*
g++ 1.cpp -g
time ./a.out
3 2 1 2
1 2 4
1 2
1 3
*/

复制代码
View Code

 

二、组合类

  T:测试52 涂色游戏 组合+矩阵

    这里主要想说其中一个系数。我用容斥做的,但是也可以递推求。两种写法。

  另附:对于一类容斥系数:

     要求恰好j种,容斥减去子集。

     系数:j:1  (C(j,j)) 

        j-1:-1*C(j,j-1)   

        j-2: 考虑上两个的贡献

          -1*C(j,j-2)+C(j,j-1)*C(j-1,j-2)

          化简后是 C(j,j-2)  ,含义没有弄清楚。感性上似乎可以理解。还是感性理解吧。

       j-k:经过归纳,发现是-1^(j-k)*C(j,j-k)

     (19,10,4)add:组合数部分可以看成不是容斥系数,而是表达式一部分。

           这样容斥系数就是+-1了。

#include<bits/stdc++.h>
#define F(i,a,b) for(int i=a;i<=b;++i)
#define LL long long
#define pf(a) printf("%lld ",a)
#define PF(a) printf("%lld ",a)
#define phn puts("")
using namespace std;
#define int LL
int read();
/** 为什么可以矩阵
取模优化错了。
有一个地方有减法,忘了+mod,出负数了。
有减法的地方一定要+mod
取模优化很快,几乎快了二倍,但是减法不加modu会出bug
*/
int n,m,p,q;
const int mod=998244353;
int f[105],c[105][105],s[105][105],a[105];
int max(int x,int y){return x>y?x:y;}
int MO(const int x){return x<mod?x:x-mod;}
int qpow(int x,int k){int s=1;for(;k;x=x*x%mod,k>>=1)if(k&1)s=s*x%mod;return s;}
/* int D[105][105];
int ans;
int T[100];
void dfs(int y,int x){
    if(y==m+1){++ans;return ;}
    int tx=x==n?1:x+1,ty=x==n?y+1:y;
    F(i,1,p){
        D[x][y]=i;
        if(x==n&y>1){
            int cnt=0;
            F(i,1,n)F(j,y-1,y)if(D[i][j]&&(!T[D[i][j]]))T[D[i][j]]=1,++cnt;
            F(i,1,n)F(j,y-1,y)T[D[i][j]]=0;
            if(cnt<q)continue;
        }    
        dfs(ty,tx);
    }
}*/
void jzchs(){
    const int ed=min(p,n);
    int g[105][105]={0};
    F(k,1,ed){
        F(i,1,ed){
            F(j,1,ed){
                g[i][j]=MO(g[i][j]+s[i][k]*s[k][j]%mod);
            }
        }
    }
    F(i,1,ed)F(j,1,ed)s[i][j]=g[i][j];
}
void jzcc(){    
    const int ed=min(p,n);
    int g[105]={0};
    F(k,1,ed){
        F(j,1,ed){
            g[j]=MO(g[j]+f[k]*s[k][j]%mod);
        }
    }
    F(j,1,ed)f[j]=g[j];
}
signed main(){
    n=read();m=read();p=read();q=read();
//    dfs(1,1);printf("%lld\n",ans);
    c[0][0]=s[0][0]=1;
    F(i,1,100){
        c[i][0]=1;
        F(j,1,i)c[i][j]=MO(c[i-1][j-1]+c[i-1][j]);
    }
    F(j,0,100){
        F(k,1,j)a[j]=MO(a[j]+c[j][k]*qpow(k,n)*((j-k)&1?-1:1)%mod+mod);
    }
    const int ed=min(p,n);
    F(k,1,ed){
        F(j,max(1,q-k),ed){
            F(t,0,j+k-q)s[k][j]=MO(s[k][j]+c[p-k][j-t]*c[k][t]%mod);
            s[k][j]=s[k][j]*a[j]%mod; /** */
        }
    }    
    F(j,1,ed)f[j]=c[p][j]*a[j]%mod;
    for(int k=m-1;k;k>>=1,jzchs())if(k&1)jzcc();
    /** 这里是m-1.*/
/*    F(i,2,m){
        F(j,1,ed){
            F(k,max(1,q-j),ed){
                f[i][j]=MO(f[i][j]+f[i-1][k]*s[k][j]%mod);
            }
        //    f[i][j]=f[i][j]*a[j]%mod;
        }
    }
//    F(i,1,m){    F(j,1,ed)pf(f[i][j]);phn; }/** */
    int ans=0;
    F(j,1,ed)ans=MO(ans+f[j]);
    ans=(ans+mod)%mod;
    printf("%lld\n",ans);
}
int read(){
    int s=0,f=0;char ch;
    while(ch=getchar(),ch=='-'?f=1:0,!isdigit(ch));
    for(;isdigit(ch);s=s*10+(ch^48),ch=getchar());
    return f?-s:s;
}    
/*
g++ 2.cpp -g
./a.out
3 4 4 4
*/
容斥
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int N=105;
const int mod=998244353;
int n,m,p,q;
ll C[N][N],f[N][N],dp[N][N];//i种颜色 填入j个空位
struct Mat{
    ll s[N][N];
    friend Mat operator *(const Mat &a,const Mat &b){
        Mat ans;
        for(int i=1;i<=p;++i)
            for(int j=1;j<=p;++j){
                ans.s[i][j]=0;
                for(int k=1;k<=p;++k) ans.s[i][j]=(ans.s[i][j]+a.s[i][k]*b.s[k][j])%mod;
            }
        return ans;
    }
    friend Mat operator ^(Mat base,int k){
        Mat ans;
        memset(ans.s,0,sizeof(ans.s));
        for(int i=1;i<=p;++i) ans.s[i][i]=1;
        while(k){
            if(k&1) ans=ans*base;
            base=base*base;
            k>>=1;
        }
        return ans;
    }
}g,t;
int main(){
    scanf("%d%d%d%d",&n,&m,&p,&q);
    for(int i=0;i<=p;++i){
        C[i][0]=1;
        for(int j=1;j<=i;++j) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
    }
    f[0][0]=1;
    for(int i=1;i<=p;++i) for(int j=i;j<=n;++j) f[i][j]=(f[i][j-1]+f[i-1][j-1])*i%mod;
    for(int i=1;i<=p;++i) t.s[1][i]=C[p][i]*f[i][n]%mod;
    for(int j=1;j<=p;++j)
        for(int k=1;k<=p;++k){
            ll tot=0;
            for(int d=max(q-k,0);d<=j;++d) tot+=C[p-k][d]*C[k][j-d]%mod;
            tot%=mod;
            g.s[k][j]=tot*f[j][n]%mod;
        }
    t=t*(g^(m-1));
    ll ans=0;
    for(int i=1;i<=p;++i) ans+=t.s[1][i];
    printf("%lld\n",ans%mod);
    return 0;
}
skyh的递推

 

转载于:https://www.cnblogs.com/seamtn/p/11593007.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值