BZOJ3810:[Coci2015]Stanovi dp

思路:

首先有一个性质:当前的矩形必定可以被划分成两个子矩形.

无脑dp.

记录四个状态表示当前矩形的四条边是不是原来的大矩形的边.

然后记忆化搜索即可.

(要非常注意常数优化)

#include<cstdio>
#include<cstring>
#include<cctype>
#include<climits>
#include<iostream>
#include<algorithm>
using namespace std;
 
#define INF 1ll<<60
 
#define N 310
long long f[2][2][2][2][N][N];
int n,m,k;
 
inline long long calc(int w,int h,int a,int b,int c,int d){
    if(w>h){
        swap(w,h);
        int aa=c,bb=d,cc=b,dd=a;
        a=aa,b=bb,c=cc,d=dd;
        if(a>b)swap(a,b);
        if(c>d)swap(c,d);
    }
    if(f[a][b][c][d][w][h]!=-1)return f[a][b][c][d][w][h];
    if(!a&&!b&&!c&&!d)return f[a][b][c][d][w][h]=INF;
    long long re=(long long)(w*h-k)*(w*h-k);
    if(a+b+c>0&&a+b+d>0)
        for(int i=1;i<w;++i)re=min(re,calc(i,h,a,b,c,0)+calc(w-i,h,a,b,0,d));
    if(a+c+d>0&&b+c+d>0)
        for(int i=1;i<h;++i)re=min(re,calc(w,i,a,0,c,d)+calc(w,h-i,0,b,c,d));
    return f[a][b][c][d][w][h]=re;
}
 
int main(){
    scanf("%d%d%d",&n,&m,&k);
    register int i,j;
     
    memset(f,-1,sizeof f);
    printf("%lld",calc(n,m,1,1,1,1));
     
    return 0;
}

BZOJ1560:[JSOI2009]火星藏宝图 dp

思路:

首先不难想到排序后dp,可是这样就\(O(n^2)\)超时了.

一开始想的是按照列从前往后做,然后给每一个之前的列维护一个单调队列.可是发现这样是\(O(m^3)\)的.

我们可以注意到一条性质:由于\(a,b>0\)时,\((a+b)^2>a^2+b^2\),且价值均\(>0\),所以路径上相邻两个点形成的矩形中不能存在别的点.

也就是说对于之前的每一列,只有最下面的才有用.

我们在扫的时候顺便记录一下这个就好了.

时间复杂度\(O(nm)\).

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
using namespace std;

#define N 200010
#define M 1010
struct Pos{
	int x,y,lab;
	bool operator<(const Pos&B)const{return x<B.x||(x==B.x&&y<B.y);}
}P[N];

int w[N],f[N],g[M],x[N],y[N];
inline int sqr(int x){return x*x;}

int main(){
	int n,m;scanf("%d%d",&n,&m);register int i,j;
	
	for(i=1;i<=n;++i)scanf("%d%d",&P[i].x,&P[i].y),x[i]=P[i].x,y[i]=P[i].y,P[i].lab=i,scanf("%d",&w[i]);
	sort(P+1,P+n+1);
	
	f[P[1].lab]=w[P[1].lab],g[1]=P[1].lab;
	for(i=2;i<=n;++i){
		f[P[i].lab]=-1<<30;
		for(j=1;j<=P[i].y;++j)if(g[j])f[P[i].lab]=max(f[P[i].lab],f[g[j]]+w[P[i].lab]-sqr(P[i].y-j)-sqr(P[i].x-x[g[j]]));
		g[P[i].y]=P[i].lab;
	}
	
	for(i=1;i<=n;++i)if(x[i]==m&&y[i]==m)printf("%d",f[i]);
	return 0;
}

BZOJ1190:[HNOI2007]梦幻岛宝珠 dp

思路:

首先很显然注意到应该按照2的幂次分组dp.

我的想法乱七八糟,就不提了.

发一个详细的网址:http://blog.csdn.net/PoPoQQQ/article/details/43953609

顺便吐槽我太弱.

 

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
using namespace std;
 
#define N 110
struct cost{
    int a,b;
    inline void init(int x){
        b=0;
        for(;x%2==0;x>>=1)++b;
        a=x;
    }
};
cost c[N];int v[N];
 
long long f[31][1010];
 
#define Max(x,y) ((x)>(y)?(x):(y))
 
int main(){
    int n,w,x;
    register int i,j,k;
    while(scanf("%d%d",&n,&w)&&(n+w>0)){
        memset(f,0,sizeof f);
        for(i=1;i<=n;++i)scanf("%d%d",&x,&v[i]),c[i].init(x);
         
        for(i=1;i<=n;++i)for(j=min(1000,w>>c[i].b);j>=c[i].a;--j)f[c[i].b][j]=Max(f[c[i].b][j],f[c[i].b][j-c[i].a]+v[i]);
         
        for(i=0;i<=30;++i)for(j=1;j<=1000&&j<=(w>>i);++j)f[i][j]=Max(f[i][j],f[i][j-1]);
         
        int re=0;
        for(k=1;k<=30&&(1<<k)<=w;++k){
            for(j=min(1000,w>>k);j>=0;--j)
                for(i=0;i<=j;++i)
                    f[k][j]=Max(f[k][j],f[k][j-i]+f[k-1][min(2*i+((w>>(k-1))&1),1000)]),re=Max(re,f[k][j]);
        }
         
        printf("%d\n",re);
    }
     
    return 0;
}

BZOJ3628:[JLOI2014]天天酷跑 dp

思路:

绝对是逗逼题,今年四月份的时候我居然都不会做...果然是幼儿园水平无力啊...

枚举Maxh,以及Maxcombo(什么枚举?没错就是枚举)

用一个Hash表存一下状态,然后随便分层dp就过了.

具体的状态设计见代码.

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
 
int n,m,c1,c2;
 
int w[100010][21];
int Maxh,Maxn;
int res,ansh,ansn;
 
int f[2][21][21][6];
struct State{
    int y,h,t;
    State(){}
    State(int _y,int _h,int _t):y(_y),h(_h),t(_t){}
};
struct HashSet{
    int s[21][21][6],cnt;
    State sav[21*21*6];
    inline void reset(){cnt=0,memset(s,0xc0,sizeof s);}
    inline int Max(){
        int res=-1<<30;
        for(int j=1;j<=cnt;++j)res=max(res,s[sav[j].y][sav[j].h][sav[j].t]);
        return res;
    }
    inline void insert(State x,int w){
        if(s[x.y][x.h][x.t]==0xc0c0c0c0)s[x.y][x.h][x.t]=w,sav[++cnt]=x;
        else if(w>s[x.y][x.h][x.t])s[x.y][x.h][x.t]=w;
    }
}S[2];
inline void relax(int lab,int x,int y,int h,int t,int lastw){
    if(w[x][y]==-1)return;
    S[lab].insert(State(y,h,t),lastw+w[x][y]);
}
inline void Solve(){
    int now=0,last=1,ans=0xc0c0c0c0;
    S[now].reset(),S[now].insert(State(1,0,Maxn),0);
    register int i,j;
    State p;int w;
    for(i=1;i<=n;++i){
        swap(now,last),S[now].reset();
        for(j=1;j<=S[last].cnt;++j){
            p=S[last].sav[j],w=S[last].s[p.y][p.h][p.t];
            if(p.y==1){
                relax(now,i,1,0,Maxn,w);
                relax(now,i,2,Maxh-1,Maxn-1,w);
            }
            else if(p.h==0){
                if(p.t)relax(now,i,p.y+1,Maxh-1,p.t-1,w);
                relax(now,i,p.y-1,0,p.t,w);
            }
            else relax(now,i,p.y+1,p.h-1,p.t,w);
        }
    }
    ans=max(ans,S[now].Max()-(Maxn-1)*c2-(Maxh-1)*c1);
    if(ans>res)res=ans,ansn=Maxn,ansh=Maxh;
}
int main(){
    scanf("%d%d%d%d",&n,&m,&c1,&c2);register int i,j;
    for(j=1;j<=m;++j)for(i=1;i<=n;++i)scanf("%d",&w[i][j]);
     
    res=0xc0c0c0c0;
    for(Maxn=1;Maxn<=5;++Maxn)
        for(Maxh=1;Maxh*Maxn<m;++Maxh)
            Solve();
     
    if(res==0xc0c0c0c0)puts("mission failed");else printf("%d %d %d\n",res,ansn,ansh);
     
    return 0;
}

BZOJ2553:[BeiJing2011]禁忌 AC自动机+期望dp

思路:

首先考虑如果一个串固定时,如何求出最多的出现次数.

当模式串存在覆盖关系时,很显然可以舍弃掉一些.那么现在所有的串之间都不存在覆盖关系.

那么我们可以从前向后扫描,如果出现一个完整的模式串,答案加1,同时将指针移到下一个位置继续刚才的操作.

由于不存在覆盖关系,这样的贪心是很显然的,不信的话可以自己画图看看.

 

考虑将模式串建成AC自动机.令\(P[i][j]\)表示串长为\(i\),现在处于AC自动机上的节点\(j\)上的概率.这个很容易用矩阵乘法搞定.

那么我们的答案是是什么呢?

考虑从某个节点转移到某个完全包含某个模式串的节点,那么此时答案+1,同时状态回到根节点.我们只要在矩阵中加一维计数器来搞就行了.

 

其实没说什么= =  不懂自己看代码.

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
using namespace std;
 
int ch[80][26],fail[80],end[80],cnt,q[80],fr,ta;
 
char s[20],sav[6][20];int len[6];bool covered[6];
 
typedef long double f4;
 
struct Matrix{
    f4 d[81][81];int w,h;
    Matrix(){}
    Matrix(int _w,int _h):w(_w),h(_h){}
     
    void operator*=(const Matrix&B){
        register int i,j,k;
        Matrix res(w,B.h);
        for(i=0;i<res.w;++i)for(j=0;j<res.h;++j){
            res.d[i][j]=0;for(k=0;k<h;++k)res.d[i][j]+=d[i][k]*B.d[k][j];
        }
        *this=res;
    }
};
 
bool cover(int a,int b){
    if(len[a]<len[b])return 0;
    register int i,j;
    for(i=1;i+len[b]-1<=len[a];++i){
        bool ok=1;
        for(j=1;j<=len[b];++j)if(sav[a][i+j-1]!=sav[b][j])ok=0;
        if(ok)return 1;
    }
    return 0;
}
 
int main(){
    int n,L,k;scanf("%d%d%d",&n,&L,&k);register int i,j;
     
    for(i=1;i<=n;++i)scanf("%s",sav[i]+1),len[i]=strlen(sav[i]+1);
    for(i=1;i<=n;++i)for(j=i+1;j<=n;++j)if(cover(i,j))covered[i]=1;
     
    bool allcovered=1;for(i=1;i<=n;++i)if(!covered[i])allcovered=0;
    if(allcovered)covered[1]=0;
     
    int p,y;
    for(i=1;i<=n;++i)if(!covered[i]){
        p=0;for(j=1;j<=len[i];++j){if(!ch[p][y=sav[i][j]-'a'])ch[p][y]=++cnt;p=ch[p][y];}
        end[p]=1;
    }
     
    for(j=0;j<k;++j)if(ch[0][j])q[ta++]=ch[0][j];
    int u,v,r;
    while(fr^ta){
        u=q[fr++];
        for(j=0;j<k;++j){
            if((v=ch[u][j])){
                q[ta++]=v;
                for(r=fail[u];r&&!ch[r][j];r=fail[r]);
                end[v]|=end[fail[v]=ch[r][j]];
            }else ch[u][j]=ch[fail[u]][j];
        }
    }
     
    Matrix res(1,cnt+2);res.d[0][0]=1;
     
    Matrix add(cnt+2,cnt+2);add.d[cnt+1][cnt+1]=1;
    for(i=0;i<=cnt;++i){
        for(j=0;j<k;++j){
            if(end[ch[i][j]])add.d[i][cnt+1]+=(f4)1/k,add.d[i][0]+=(f4)1/k;
            else add.d[i][ch[i][j]]+=(f4)1/k;
        }
    }
     
    for(;L;L>>=1,add*=add)if(L&1)res*=add;
     
    double output=res.d[0][cnt+1];
    printf("%.6lf",output);
     
     
    return 0;
}

BZOJ3174:[Tjoi2013]拯救小矮人 贪心+dp

思路:

假设在能逃出相同多数目的小矮人的情况下,我们更愿意让逃生能力更强的小矮人留到最后.

逃生能力就是指\(a_i+b_i\),也即在下面垫的总高度确定时更容易达到更高的高度.

这样我们就有了一个贪心选择顺序,我们设\(f[i]\)表示逃生了\(i\)个小矮人时下面垫的总高度的最大值,并依据这个判定能否再逃出一人即可.

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
using namespace std;
 
#define N 2010
struct Case{int a,b;bool operator<(const Case&B)const{return a+b<B.a+B.b;}}S[N];
 
int f[N];
int main(){
    int n,m;scanf("%d",&n);register int i,j;for(i=1;i<=n;++i)scanf("%d%d",&S[i].a,&S[i].b);
    sort(S+1,S+n+1);
    scanf("%d",&m);
    memset(f,-1,sizeof f);for(f[0]=0,i=1;i<=n;++i)f[0]+=S[i].a;
    int ans=0;
    for(i=1;i<=n;++i){
        for(j=ans;j>=0;--j){
            if(f[j]+S[i].b>=m)f[j+1]=max(f[j+1],f[j]-S[i].a);
             
        }if(f[ans+1]>=0)++ans;
    }
    printf("%d\n",ans);
    return 0;
}

BZOJ3357:[Usaco2004]等差数列 dp

思路:

我们令\(f[i][j]\)表示等差数列最后两项的下标分别为\(i,j\)时的最长长度.

显然我们有:

\[f[i][j]=max_{k=1}^{i-1}\{f[k][i]+1\}(2a[i]=a[j]+a[k])\]

重要的就是如何快速找出\(k\)的位置.

我们呢就可以将权值离散化,记录一下每个权值在序列中出现的位置.

于是我们要找的\(k\),显然就是权值相同时标号小于\(i\)且最大的一个.

因此我们维护一个vector然后二分就好了.

(注意\(n=1\)233)

 

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<vector>
#include<set>
#include<map>
using namespace std;
 
#define N 2010
vector<int>v[N];
 
int a[N],b[N],id;
map<int,int>M;
 
int f[N][N];
 
int main(){
    int n;scanf("%d",&n);if(n==1){puts("1");return 0;}register int i,j;for(i=1;i<=n;++i)scanf("%d",&a[i]),b[i]=a[i];
    sort(b+1,b+n+1);for(b[0]=-1<<30,i=1;i<=n;++i)if(b[i]!=b[i-1])M[b[i]]=++id;
    for(i=1;i<=n;++i)v[M[a[i]]].push_back(i);
    int nowv,ins,siz;
    for(j=2;j<=n;++j){
        for(i=1;i<j;++i){
            f[i][j]=2;
            nowv=M[2*a[i]-a[j]];
            if(nowv){
                siz=v[nowv].size();
                if(v[nowv][0]<i){
                    int L=0,R=siz-1,mid;
                    while(L<R){
                        mid=(L+R+1)>>1;
                        if(v[nowv][mid]<i)L=mid;else R=mid-1;
                    }f[i][j]=max(f[i][j],f[v[nowv][L]][i]+1);
                }
            }
        }
    }
    int res=0;for(i=1;i<=n;++i)for(j=i+1;j<=n;++j)res=max(res,f[i][j]);
    printf("%d",res);
    return 0;
}

BZOJ3866:The Romantic Hero dp

思路:

令\(f[i][j]\)表示最后一个数的位置\(\leq{i}\),异或起来等于\(j\)的序列数,\(g[i][j]\)表示第一个数的位置\(\geq{i}\),与起来等于\(j\)序列数.

我们差分之后就很容易得到结尾、开头位置确定的异或、与的序列个数了.

这两个东西知道之后,随便维护个前缀和什么的就行了.

(大常数\(O(n^2)\),目前倒数第一)

#include<cstdio>
#include<cstring>
#include<cctype>
#include<climits>
#include<iostream>
#include<algorithm>
using namespace std;
  
#define N 1010
#define M 1024
int f[N][M],g[N][M],a[N],add[M][N];
  
static const int mod=(1e9)+7;
  
inline void inc(int&x,int y){if((x+=y)>=mod)x-=mod;}
inline int dec(int x,int y){return (x-y+mod)%mod;}
int main(){
    int T;scanf("%d",&T);
    register int i,j;int n;
    while(T--){
        scanf("%d",&n);for(i=1;i<=n;++i)scanf("%d",&a[i]);
        for(memset(f,0,sizeof f),i=1;i<=n;++i){
            inc(f[i][a[i]],1);
            for(j=0;j<M;++j)inc(f[i][a[i]^j],f[i-1][j]);
            for(j=0;j<M;++j)inc(f[i][j],f[i-1][j]);
        }
        for(memset(g,0,sizeof g),i=n;i>=1;--i){
            inc(g[i][a[i]],1);
            for(j=0;j<M;++j)inc(g[i][a[i]&j],g[i+1][j]);
            for(j=0;j<M;++j)inc(g[i][j],g[i+1][j]);
        }
          
        for(i=n;i>=1;--i)for(j=0;j<M;++j)f[i][j]=dec(f[i][j],f[i-1][j]);
        for(i=1;i<=n;++i)for(j=0;j<M;++j)g[i][j]=dec(g[i][j],g[i+1][j]);
          
        for(j=0;j<M;++j)for(i=n;i>=1;--i)add[j][i]=add[j][i+1],inc(add[j][i],g[i][j]);
          
        int ans=0;
        for(j=0;j<M;++j)for(i=1;i<=n;++i)inc(ans,(long long)f[i][j]*add[j][i+1]%mod);
          
        printf("%d\n",ans);
    }
      
    return 0;
}

BZOJ3831:[Poi2014]Little Bird dp+单调队列

思路:

一开始是用的线段树优化dp,具体的就不说了.

我一开始觉得有的需要+1,有的不需要加,单调队列没办法做.

不过有一条性质,就是如果两条决策,其中一个的步数已经比另一个决策少,那么我们选择这个决策是一定不劣于另一个决策的.

否则若步数相同,我们当然选择保留高度较高的那个决策.

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
using namespace std;

namespace Fio{
    inline int getc(){
        static const int L=1<<15;static char buf[L],*S=buf,*T=buf;
        if(S==T){T=(S=buf)+fread(buf,1,L,stdin);if(S==T)return EOF;}
        return*S++;
    }
    template<typename T>inline void Get(T&x){
        int c;while(!isdigit(c=getc())&&c!='-');bool sig=c=='-';
        x=sig?0:c-'0';while(isdigit(c=getc()))x=(x<<1)+(x<<3)+c-'0';
        if(sig)x=-x;
    }
}
#define N 1000010
int h[N];
int q[N],fr,ta,f[N];
int main(){
	int n;Fio::Get(n);register int i,j;for(i=1;i<=n;++i)Fio::Get(h[i]);
	int Q;Fio::Get(Q);int lim;
	while(Q--){
		 Fio::Get(lim);
		 fr=ta=0;q[ta++]=1;f[1]=0;
		 for(i=2;i<=n;++i){
		 	while(fr^ta&&i>q[fr]+lim)++fr;f[i]=f[q[fr]]+(h[q[fr]]<=h[i]);
		 	while(fr^ta&&((f[i]<f[q[ta-1]])||(f[i]==f[q[ta-1]]&&h[i]>h[q[ta-1]])))--ta;
		 	q[ta++]=i;
		 }
		 printf("%d\n",f[n]);
	}return 0;
}

BZOJ1492:[NOI2007]货币兑换Cash 斜率优化+cdq分治