DP刷题记录和总结

来源:互联网 发布:id设计软件 编辑:程序博客网 时间:2024/05/19 03:41

今天开始刷dp专题,主要是做黄学长的blog上的题目和poj、bzoj上的dp题目,联赛还有两个月左右,fight!!!不苦不累,你要青春干嘛?!!
树形dp网址
有依赖型的树形背包

vijos1642

传送门

这道题是一道典型的有依赖型背包,必须要选了父亲才能选儿子,做这道题有两种方法,一种是O(n×m2)的树上背包合并,还有一种是dfs序的做法,鉴于网上讲dfs序的方法不多,这里主要介绍下dfs序的方法(看了我半天才搞懂),这个方法是O(n×m)的复杂度还是可以接受的,记得guak还有一种dp传址的方法,明天去搞懂再来补。
dfs序的方法就是考虑dfs序的性质,一个节点的编号到这个节点的编号加上它的子树大小-1就是它的子树的dfs序的区间范围。有了这个性质,我们考虑设dp状态为,dp[i][j]表示i+1>n 这个dfs序范围内的值已经处理好,到i这个dfs序的时候,选了j个节点,获得的最大收益。我们想想当前状态可以由dp[i+1][j1] 转移过来,这个是什么意思呢?如果我选当前的这个点,那么我可以由我的儿子转移过来,对于叶子结点,可以由它的兄弟节点转移过来。但是我开始看的时候有一个疑惑,就是当前这个点选的话,可以由它的任意一个儿子/兄弟转移过来,在dp方程中如何体现?手动模拟了一波发现有这么一个巧妙地性质,因为是连续枚举的dfs序,如果当前这个节点有儿子,它的dfs序+1一定是它的一个儿子,而它的儿子的最优值又是由它的兄弟或儿子转移过来的,所以在进行转移的时候,一定是当前子树的最优值,换句话说,dp的最优值全部记在能转移到它的离他的dfs序最近的那个节点上,然后还有一种转移就是由dp[i+size[i]][j]转移过来,表示不选这个节点,那么整颗子树都不能选,直接跳过就好了,这样一跳一定会跳到一个离他最近的有两个子节点以上的祖先的儿子上。所以总的转移方程就是:
dp[i][j]=max(dp[i+1][j1]+w[tid[i]],dp[i+size[i]][j])
O(n×m) 枚举一下就好了。

/*************************************************************************    > File Name: vijos1642.cpp    > Author: Drinkwater-cnyali    > Created Time: 2017/9/3 23:20:01************************************************************************/#include<iostream>#include<cstdio>#include<cstdlib>#include<cmath>#include<cstring>#include<algorithm>using namespace std;#define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)#define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)#define mem(a, b) memset((a), b, sizeof(a))int read(){    int sum = 0, fg = 1; char c = getchar();    while(c < '0' || c > '9') { if (c == '-') fg = -1; c = getchar(); }    while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }    return sum * fg;}const int maxn = 10000+10;int n,m;int be[maxn],ne[maxn],to[maxn],e;void add(int x,int y){    to[++e] = y;ne[e] = be[x];be[x] = e;}int dp[1010][1010];int tid[maxn],size[maxn],cnt,v[maxn];void dfs(int x,int fa){    tid[++cnt] = x;size[x] = 1;    for(int i = be[x]; i; i = ne[i])    {        int v = to[i];        if(v != fa)dfs(v,x),size[x] += size[v];    }}int main(){    n = read(),m = read();    REP(i,1,n)    {        v[i] = read();        int num = read();        REP(j,1,num)        {            int x = read();            add(i,x);        }    }    dfs(1,0);    mem(dp,-127);    REP(i,1,n)dp[i][0] = 0,dp[i][1] = v[tid[i]];    DREP(i,n-1,1)        REP(j,1,m)            dp[i][j] = max(dp[i+1][j-1]+v[tid[i]],dp[i+size[tid[i]]][j]);    int ans = -0x3f3f3f3f;    REP(i,1,m)ans=max(ans,dp[1][i]);    cout<<max(ans,0)<<endl;    return 0;}

HDU2196

传送门
这是一道非常经典的树形dp的题目,问的是每个点到离他最远的的点的距离.
感觉这题稍微变一下就是树的直径了.
在纸上画一下我们发现,如果我们事先用dp处理好当前这个点到他子树内部最远的点的距离,离他最远的点要么在他的子树里,要么一定跟他父亲相连.所以我们预先处理好最大值与次大值,次大值的作用是如果他父亲的最远点同时也在当前点的子树里,我们就不能用父亲的最大值来更新,必须要用次大值,这样的话,我们再dfs一遍,就好了.

void bfs(int x,int fa){    for(int i = be[x]; i;i = ne[i])    {        int v = to[i];        if(v != fa)        {            bfs(v,x);//更新最大值与次大值            if(dp[v][0] + w[i] >= dp[x][0])//最大值可以被更新            {                dp[x][1] = dp[x][0];num[x][1] = num[x][0];//更新次大值                dp[x][0] = dp[v][0] + w[i];num[x][0] = v;            }//次大值可以被更新            else if(dp[v][0] + w[i] > dp[x][1])dp[x][1] = dp[v][0] + w[i],num[x][1] = v;        }    }}
void dfs(int x,int fa){    for(int i = be[x]; i; i = ne[i])    {        int v = to[i];        if(v != fa)        {            if(num[x][0] == v)//在同一颗子树里            {                if(dp[v][1] < dp[x][1] + w[i])//次大值可以被更新,这里更新次大值的主要目的是用它来跟最大值比较                {                    dp[v][1] = dp[x][1] + w[i];                    num[v][1] = x;                }            }            else            {                if(dp[v][1] < dp[x][0] + w[i])                {                    dp[v][1] = dp[x][0] + w[i];                    num[v][1] = x;                }            }            if(dp[v][0] < dp[v][1])            {                swap(dp[v][0],dp[v][1]);                swap(num[v][0],num[v][1]);            }            dfs(v,x);        }    }}

URAL 1018

传送门
这道题也是十分经典,求的是树中每个点有权值,问只留下k个点剩下的最大权值和是多少?留下的点还是构成一棵树.
很显然,我们只需要处理每一颗子树就好了,所以状态很好设dp[i][j] 表示以i为根的子树里,选了j个点,最大的权值和.
转移也特别简单,我们观察到,设当前这个节点i的某个儿子为v.
dp[i][j]=max(dp[i][j],dp[i][jk]+dp[v][k]+w[i]) 前面一个部分表示你不选v这颗子树,后面表示选,但是选多少个需要枚举一下,k就是在v中选k个,所以i其他的子树只有j-k个保留.

void dfs(int x){    vis[x] = 1;size[x] = 1;    for(int i = be[x]; i; i = ne[i])    {        int v = to[i];        if(!vis[v])        {            dfs(v);            size[x] += size[v];            for(int j = size[x]; j >= 1; --j)                REP(k,1,j-1)                    dp[x][j] = max(dp[x][j],dp[x][j-k] + dp[v][k] + w[i]);        }    }}

poj3162

传送门
跟上上道题一样,但是加了一问,把距离处理出来之后,他问你最长的满足区间最大值-最小值<=m的区间长度。提供两个思路,第一个是指针加线段树,我们枚举区间的左右端点,不满足条件左端点++,不然更新答案,右端点++,这个也可以用单调队列来搞,应该很快,这道题我还稍微卡了卡常。另外的就是二分一个长度,然后nlogn的判断就好了。

/*************************************************************************     > Author: Drinkwater     > Created Time: 2017/9/6 18:11:55 ************************************************************************/#include<iostream>#include<cstdio>#include<cstdlib>#include<cmath>#include<cstring>#include<algorithm>using namespace std;typedef long long LL;typedef unsigned long long uLL;#define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)#define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)#define mem(a, b) memset((a), b, sizeof(a))template<typename T> inline bool chkmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; }template<typename T> inline bool chkmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }int read(){    register int sum = 0,fg = 0;char c = getchar();    while(c < '0' || c > '9') { fg |= c == '-'; c = getchar(); }    while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }    return fg ? -sum : sum;}const int inf = 1e9;const int maxn = 1000000+10;int n,m;int be[maxn<<1],ne[maxn<<1],to[maxn<<1],e,w[maxn<<1];int dp[maxn][2],num[maxn][2];void add(int x,int y,int z){    to[++e] = y;ne[e] = be[x];be[x] = e;w[e] = z;    to[++e] = x;ne[e] = be[y];be[y] = e;w[e] = z;}void bfs(int x,int fa){    dp[x][0] = dp[x][1] = 0;    for(int i = be[x]; i;i = ne[i])    {        int v = to[i];        if(v != fa)        {            bfs(v,x);            if(dp[v][0] + w[i] >= dp[x][0])            {                dp[x][1] = dp[x][0];num[x][1] = num[x][0];                dp[x][0] = dp[v][0] + w[i];num[x][0] = v;            }            else if(dp[v][0] + w[i] > dp[x][1])dp[x][1] = dp[v][0] + w[i],num[x][1] = v;        }    }}void dfs(int x,int fa){    for(int i = be[x]; i;i = ne[i])    {        int v = to[i];        if(v != fa)        {            if(num[x][0] == v)            {                if(dp[x][1] + w[i] > dp[v][1]) dp[v][1] = dp[x][1] + w[i], num[v][1] = x;            }            else { if(dp[x][0] + w[i] > dp[v][1]) dp[v][1] = dp[x][0] + w[i],num[v][1] = x;}            if(dp[v][1] > dp[v][0])swap(dp[v][1],dp[v][0]),swap(num[v][0],num[v][1]);            dfs(v,x);        }    }}int Max[maxn<<2],Min[maxn<<2];void build(int h,int l,int r){    if(l == r){Max[h] = Min[h] = dp[l][0];return ;}    int mid = (l + r) >> 1;    build(h<<1,l,mid);build(h<<1|1,mid+1,r);    Max[h] = max(Max[h<<1],Max[h<<1|1]);    Min[h] = min(Min[h<<1],Min[h<<1|1]);}int queryB(int h,int l,int r,int q,int w){    if(l >= q && w >= r)return Max[h];    int mid = (l + r) >> 1;    int Ans = -inf;    if(q <= mid)chkmax(Ans,queryB(h<<1,l,mid,q,w));    if(w > mid)chkmax(Ans,queryB(h<<1|1,mid+1,r,q,w));    return Ans;}int queryS(int h,int l,int r,int q,int w){    if(l >= q && w >= r)return Min[h];    int mid = (l + r) >> 1;    int Ans = inf;    if(q <= mid)chkmin(Ans,queryS(h<<1,l,mid,q,w));    if(w > mid)chkmin(Ans,queryS(h<<1|1,mid+1,r,q,w));    return Ans;}int main(){    while(scanf("%d%d",&n,&m)!=EOF)    {        mem(be,0);e = 0;        REP(i,2,n)        {            int x = read(),y = read();            add(i,x,y);        }        bfs(1,0);dfs(1,0);        build(1,1,n);        int l = 1,r = 1;        int ans = 0;        while(1)        {            if(l>r||l>n||r>n) break;            int Mx = queryB(1,1,n,l,r);            int Mn = queryS(1,1,n,l,r);            if(Mx - Mn <= m)chkmax(ans,r-l+1),++r;            else ++l;        }        printf("%d\n",ans);    }    return 0;}

P3195

传送门
这题的O(n2) 的做法很简单
dp[i]=min(dp[j]+sum[i]sum[j]+ij1) 扫一下就好了。
观察这个式子,发现后面的式子跟i,j都有关,不能用线段树或单调队列,于是我们想到了斜率优化。斜率优化就是证明一个决策单调性,然后把式子化成斜率式,每次用单调队列维护下凸包就好了。
对于这道题的话,我们设f[i]=sum[i]+i,C=L+1 原式就化成了dp[i]=min(dp[j]+f[i]f[j]+C)
我们假设j<kkj
即:dp[j]+(f[i]f[j]C)2>dp[k]+(f[i]f[k]C)2
设i的后继状态为t,f[t]=f[i]+v 
即证:dp[j]+(f[t]f[j]C)2>dp[k]+(f[t]f[k]C)2
等价于
dp[j]+(f[i]+vf[j]C)2>dp[k]+(f[i]+vf[k]C)2
展开得
dp[j]+v2+2v(f[i]f[j]C)+(f[i]f[j]C)2>dp[k]+v2+2v(f[i]f[k]C)+(f[i]f[k]C)2
H=dp[j]+(f[i]f[j]C)2dp[k]+(f[i]f[k]C)2
原式等于H>2v(f[j]f[k]C)H>0
所以f[k]>f[j]
于是我们就证明了决策单调性.
于是我们把原式化为斜率形式
dp[j]+(f[i]f[j]C)2>dp[k]+(f[i]f[k]C)2

dp[i]+f[i]22f[i]f[j]+f[j]22C(f[i]f[j])+C2>dp[k]+f[i]22f[i]f[k]+f[k]22C(f[i]f[k])+C2

dp[j]+f[j]2+2f[j]Cdp[k]f[k]22f[k]C>2(f[j]f[k])f[i]

dp[j]dp[k]+(f[j]+C)2(f[k]+C)2/2(f[i]f[k])<f[i]
然后就好了

LL cl(LL x) { return x * x;}double slope(int x,int y){    return (dp[x] - dp[y] + cl(f[x] + L) - cl(f[y] + L))/(2.0 * (f[x]-f[y]));}int main(){    n = read(),L = read();L++;    REP(i,1,n)w[i] = read(),sum[i] = sum[i-1] + w[i];    REP(i,1,n)f[i] = sum[i] + i;    dp[1] = 0;    int l = 1,r = 1;    REP(i,1,n)    {        while(l < r && slope(q[l],q[l+1]) <= f[i])l++;        dp[i] = dp[q[l]] + cl(f[i]-f[q[l]]-L);        while(l < r && slope(q[r-1],q[r]) > slope(q[r],i))r--;        q[++r] = i;    }     cout<<dp[n]<<endl;}

HDU1011

传送门
树形背包,每个节点可以当成一个决策,跟第一道题一样,这里用树形背包的放法写一下。

void dfs(int x){    vis[x] = 1;    int dg = (c[x] + 19) / 20;    REP(i,dg,m)dp[x][i] = p[x];    for(int i = be[x]; i; i = ne[i])    {        int v = to[i];        if(!vis[v])        {            dfs(v);            DREP(j,m,dg)                for(int k = 1;j + k <= m; ++k)                    dp[x][j+k] = max(dp[x][j+k],dp[x][j]+dp[v][k]);        }    }}

POJ3107

传送门
求树的重心,就是把这个点去掉后,最大的连通块最小。
dp[i]代表去掉i这个点后最大连通块大小,扫一遍就好了。

void dfs(int x,int fa){    size[x] = 1;    for(int i = be[x]; i; i = ne[i])    {        int v = to[i];        if(v != fa)        {            dfs(v,x);size[x] += size[v];            dp[x] = max(dp[x],size[v]);        }    }    dp[x] = max(dp[x],n-size[x]);    if(dp[x] < dp[root])root = x;}

codeforces 219D

传送门
给你一棵树,树边有方向,正向边权值为0,反向为1,问以那些点为根,使之遍历(从这个点往下走)所有树边的权值和最小。
一开始我们设dp[i]代表以i为根的子树的树边权值之和,之后我们再dfs一遍,用父亲来更新儿子,算出答案。具体是这样子的,对于节点x,它的一个儿子为v,当前dp的含义是从这个点出发遍历所有树边的最小代价,所以但我们递归到x的时候,x已经算完了,我们可用x的答案来更新它的儿子,我们不难发现,以v为根的时候,答案就是,以v为子树的答案+x的答案-以v为子树的答案-wedgex,v+wedge(v,x).两遍dfs就可以的。

void bfs(int x,int fa){    for(int i = be[x]; i; i = ne[i])    {        int v = to[i];        if(v != fa)        {            bfs(v,x);            dp[x] += dp[v] + w[i];        }    }}void dfs(int x,int fa){    for(int i = be[x]; i; i = ne[i])    {        int v = to[i];        if(v != fa)        {            dp[v] += (dp[x] - dp[v] - w[i]) + (w[i]^1);            dfs(v,x);        }    }}

POJ1947

传送门
问你最少割去几条边使得有大小为k的联通块.
实际上还是挺简单的,做到现在发现树型dp就是在递归的时候调用子树信息,实际上其他dp也是这样,利用后继节点的信息,这道题我们设dp[i][j]表示以i为子树根节点,强制选i,使其构成大小为j的块最少割的边的条数,我们不难发现使其只构成大小为1的块的条件就是把他和他所有的儿子断开,然后我们枚举一个子树大小j,我们枚举他的儿子构成大小为k的块,这样当前这个点只需要构成j-k的块,然后我们是正序枚举的,j-k显然是算过的,形象的理解就是,用儿子k的块+这个点这个j-k的块构成j的块,但是你会发现幼体跳边被重复算了,因为是强制选这个点,所以这个点到他儿子的边会多算一次,所以-1就好了.

void bfs(int x,int fa){    size[x] = 1;    for(int i = be[x]; i;i = ne[i])    {        int v = to[i];        if(v != fa)        {            bfs(v,x);son[x]++;            size[x] += size[v];        }    }    dp[x][1] = son[x];}void dfs(int x,int fa){    for(int i = be[x]; i; i= ne[i])    {        int v = to[i];        if(v != fa)        {            dfs(v,x);            DREP(j,m,1)                REP(k,1,size[v])                if(j-k>=1)dp[x][j] = min(dp[x][j],dp[x][j-k]+dp[v][k]-1);        }    }}
int ans = dp[1][m];REP(i,2,n)ans = min(ans,dp[i][m]+1);//这里要注意,因为是强制选,所以跟他父亲的那个点要断开.

HDU1561

传送门
是一道树型背包的好题,算是入门的吧,这里解释下为什么背包合并的时候为什么要逆序枚举,其实也好理解,我们不是一个一个子节点枚举的吗?这样我们的dp方程dp[i][j]=max(dp[i][j],dp[i][jk]+dp[v][k])的j-k可以保证是利用了上个子树的信息,相当于背包的滚动.

void dfs(int x){    size[x] = 1;    for(int i = be[x]; i; i = ne[i])    {        int v = to[i];        dfs(v);size[x] += size[v];        DREP(j,m,1)            REP(k,1,size[v])                if(j - k >= 1)dp[x][j] = max(dp[x][j],dp[x][j-k]+dp[v][k]);    }}

HDU4003

传送门
有了上面那题的思想,这道题就好理解了,好像网上说的东西我不是很懂,那个分组背包有点没懂,但是感觉我的理解也没错.树中每条边有花费,有k个机器人,问遍历所有的点的最少花费(可以回头,每次只能从s出发)
我们先证明个东西,就是对于一个机器人a,他是不可能从别的子树过来又回去的,因为我们设在这个子树有一个机器人b,他可以走完自己的路径然后走a的路径,这样会少走a会a所在的子树的那条路径.
所以,对于一个子树内机器人,他就一定是走完这棵子树,不会走到别的地方去,所以我们设dp[i][j]代表i这棵子树,放了j个机器人的最小代价,初始状态很显然,就是只放一个,这样子树内的每一条边就走了2次,然后转移dp[i][j]=min(dp[i][j],dp[i][jl]+dp[v][l]+lw[i])l就是枚举的i的子树放了l个机器人,这样我们从i出发,所以i->v就是走了l次,每次用之前的子树的信息就好了.

#include<iostream>#include<cstdio>#include<cstring>#include<cstdlib>#include<cmath>#include<algorithm>using namespace std;#define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)#define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)#define mem(a, b) memset((a), b, sizeof(a))typedef long long LL;int read(){    register int fg = 1,sum = 0;char c = getchar();    while(!isdigit(c)) { if(c == '-')fg = -1; c = getchar(); }    while(isdigit(c)) { sum = sum * 10 + c - '0'; c = getchar();    }    return fg * sum;}const int maxn = 20000+10;int n,s,k;int be[maxn],ne[maxn],to[maxn],e,w[maxn];int dp[maxn][11];void add(int x,int y,int z){    to[++e] = y;ne[e] = be[x];be[x] = e;w[e] = z;}void dfs(int x,int fa){    for(int i = be[x]; i; i = ne[i])    {        int v = to[i];        if(v != fa)        {            dfs(v,x);            DREP(j,k,0)            {                dp[x][j] += dp[v][0] + 2 * w[i];                REP(l,1,j)dp[x][j] = min(dp[x][j],dp[x][j-l]+dp[v][l]+l*w[i]);            }        }    }}int main(){#ifndef ONLINE_JUDGE    freopen("1.in","r",stdin);    freopen("1.out","w",stdout);#endif    while(scanf("%d%d%d",&n,&s,&k)!=EOF)        {        mem(be,0);e = 0;mem(dp,0);        REP(i,1,n-1)        {            int x = read(),y = read(),z = read();            add(x,y,z);add(y,x,z);        }        dfs(s,-1);        cout<<dp[s][k]<<endl;    }}

USACO2.3奶牛家谱

传送门
这道题拖了我好久,一直没做,今晚上推出来一个n^4的dp,以为做不了,但是卡卡常数就过了。。。
我们设dp[i][j][k]代表i层,选了j个,一共选了k个,然后通过组合数乱搞就好了,转移方程是这样子的
dp[i][j][k]+=dp[i1][l][kj]c[l][j/2]其中l为上一层选的个数。

#include<iostream>#include<cstdio>#include<cstdlib>#include<cmath>#include<cstring>#include<algorithm>using namespace std;#define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)#define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)#define mem(a, b) memset((a), b, sizeof(a))int read(){    int sum = 0, fg = 1; char c = getchar();    while(c < '0' || c > '9') { if (c == '-') fg = -1; c = getchar(); }    while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }    return sum * fg;}const int mod = 9901;int n,m;int dp[110][210][210];int c[220][220];int main(){    n = read(),m = read();    REP(i,1,210)c[i][i] = 1,c[i][1] = i,c[i][0] = 1;    REP(i,2,210)        REP(j,1,i)c[i][j] = (c[i-1][j] + c[i-1][j-1])%mod;    dp[1][1][1] = 1;dp[2][2][3] = 1;    REP(i,3,m)        for(int j = 2;j <= n/2+1; j += 2)        {            if(i < 31)if(1<<i < j)continue;            for(int k = j+1;k <= n; k ++)                for(int l = j/2; l <= n/2+1; l++)                if(l%2==0)(dp[i][j][k] += dp[i-1][l][k-j]*c[l][j/2])%=mod;          }    int ans = 0;    REP(i,2,n)(ans += dp[m][i][n])%=mod,++i;    cout<<ans<<endl;    return 0;}

NOIP2015子串

传送门
这道题我也是拖了好久没做,今晚上随便推了推,发现有70pt,开始设定的状态是dp[i][j][k]表示,A串第i个,B串第j个,分成k组的方案数,转移很好看,大概是这样:

REP(i,1,n)if(s[i] == p[1])dp[i][1][1] = 1;    REP(k,1,f)        REP(i,1,n)        {            REP(j,1,m)            {                if(s[i] != p[j])continue;                REP(l,1,i-1)                {                    if(s[l] != p[j-1])continue;                    if(l==i-1)(dp[i][j][k] += dp[l][j-1][k])%=mod;                    (dp[i][j][k] += dp[l][j-1][k-1])%=mod;                }            }        }

后来发现这东西不好优化,于是推了另一种状态
定义状态f[i][j][k][0/1]f[i][j][k][0/1]表示字符串A的前i个字符和字符串B的前j个字符用了k个子串,第四维为1表示A字符串的第i个字符必须被选出,为0表示A字符串的第i个字符不能被选出。
这样子转移就很好写了

/*************************************************************************    > File Name: zj.cpp    > Author: Drinkwater-cnyali    > Created Time: 2017/9/9 20:45:29************************************************************************/#include<iostream>#include<cstdio>#include<cstdlib>#include<cmath>#include<cstring>#include<algorithm>using namespace std;#define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)#define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)#define mem(a, b) memset((a), b, sizeof(a))int read(){    int sum = 0, fg = 1; char c = getchar();    while(c < '0' || c > '9') { if (c == '-') fg = -1; c = getchar(); }    while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }    return sum * fg;}const int mod = 1e9 + 7;const int maxn = 210;int dp[2][maxn][maxn][2],n,m,f,sum;char s[1010],p[1010];int main(){    n = read(),m = read(),f = read();    cin>>(s+1)>>(p+1);    REP(i,1,n)    {        dp[i&1][1][1][0] = sum;        if(s[i] == p[1])dp[i&1][1][1][1] = 1,sum++;        REP(j,2,m)        {            REP(k,1,f)            {                dp[i&1][j][k][0] = (dp[(i+1)&1][j][k][0]+dp[(i+1)&1][j][k][1])%mod;                if(s[i] == p[j])dp[i%2][j][k][1]=((dp[(i+1)%2][j-1][k-1][1]+dp[(i+1)%2][j-1][k][1])%mod+dp[(i+1)%2][j-1][k-1][0])%mod;            }        }        REP(j,1,m)            REP(k,1,f)                dp[(i+1)%2][j][k][1]=dp[(i+1)%2][j][k][0]=0;    }    cout<<(dp[n&1][m][f][0]+dp[n&1][m][f][1])%mod<<endl;    return 0;}

HDU3586

传送门
这题就是说,给一个限制m,切断的路径权值和不超过m,单个边权值也不超过k,求最小的k使得所有叶子和根不相连。其实这题蛮简单的,二分一个k,我们设dp[i]代表以i为根的子树,最小的代价,我们每次比较i到儿子的边,然后跟儿子的dp值比即可。

void dfs(int x,int lim,int fa){    dp[x] = 0;int flag = 0;    for(int i = be[x]; i; i = ne[i])    {        int v = to[i];        if(v != fa)        {            flag = 1;            dfs(v,lim,x);            int ls = w[i] <= lim ? w[i] : inf;            dp[x] += min(dp[v],ls);        }    }    if(!flag)dp[x] = inf;}

HDU4276

传送门
巧妙的转化一下思路,把最短路的边权赋成0,然后跑一遍树形背包就好了

/*************************************************************************     > Author: Drinkwater     > Created Time: 2017/9/10 20:24:38 ************************************************************************/#include<iostream>#include<cstdio>#include<cstdlib>#include<cmath>#include<cstring>#include<algorithm>#include<queue>#prag\ma GCC optimize("O3")using namespace std;typedef long long LL;typedef unsigned long long uLL;#define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)#define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)#define mem(a, b) memset((a), b, sizeof(a))template<typename T> inline bool chkmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; }template<typename T> inline bool chkmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }int read(){    register int sum = 0,fg = 0;char c = getchar();    while(c < '0' || c > '9') { fg |= c == '-'; c = getchar(); }    while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }    return fg ? -sum : sum;}const int inf = 1e9;const int maxn = 110;int n,m;int be[maxn<<1],ne[maxn<<1],to[maxn<<1],w[maxn<<1],e;void add(int x,int y,int z){    to[e] = y;ne[e] = be[x];be[x] = e;w[e] = z;e++;}int vis[maxn],pre[maxn],id[maxn],dis[maxn],dp[maxn][maxn*5];bool bfs(){    queue<int>q;mem(vis,0);    fill(dis+1,dis+1+n,inf);    vis[1] = 1;dis[1] = 0;q.push(1);pre[1] = 1;    while(!q.empty())    {        int x = q.front();q.pop();        if(x == n)break;        for(int i = be[x]; i!=-1; i = ne[i])        {            int v = to[i];            if(!vis[v])            {                if(chkmin(dis[v],dis[x]+w[i]))                    q.push(v),vis[v] = 1,pre[v] = x,id[v] = i;            }        }    }    for(int x = n;x != 1; x = pre[x])        w[id[x]] = w[id[x]^1] = 0;    if(dis[n] > m)return 0;    return 1;}int val[maxn];void dfs(int x,int fa){    REP(i,0,m)dp[x][i] = val[x];    for(int i = be[x]; i!=-1; i = ne[i])    {        int v = to[i];        if(v != fa)        {            dfs(v,x);            DREP(j,m,0)                REP(k,0,j)                    if(j-k-2*w[i]>=0)                        dp[x][j] = max(dp[x][j],dp[v][k]+dp[x][j-k-2*w[i]]);        }    }}int main(){    while(scanf("%d%d",&n,&m)!=EOF)    {        mem(be,-1);e = 0;        REP(i,1,n-1)        {            int x = read(),y = read(),z = read();            add(x,y,z);add(y,x,z);        }        REP(i,1,n)val[i] = read();        int tmp = bfs();        if(!tmp)puts("Human beings die in pursuit of wealth, and birds die in pursuit of food!");        else        {            mem(dp,0);dfs(1,-1);m -= dis[n];            printf("%d\n",dp[1][m]);        }    }    return 0;}

HAOI 逆序对数列

传送门
首先n^3的dp很好想,于是我们可以用前缀和来优化dp就好了。

/*************************************************************************    > File Name: bzoj2431.cpp    > Author: Drinkwater-cnyali    > Created Time: 2017/9/12 23:49:05************************************************************************/#include<iostream>#include<cstdio>#include<cstdlib>#include<cmath>#include<cstring>#include<algorithm>using namespace std;#define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)#define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)#define mem(a, b) memset((a), b, sizeof(a))int read(){    int sum = 0, fg = 1; char c = getchar();    while(c < '0' || c > '9') { if (c == '-') fg = -1; c = getchar(); }    while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }    return sum * fg;}const int maxn = 1010;const int mod = 10000;int dp[maxn][maxn];int n,k,sum[maxn];int main(){    n = read(),k = read();    dp[1][0] = 1;sum[1] = 1;    REP(i,1,k+1)sum[i] = 1;    REP(i,2,n)    {        REP(j,0,min(k,(i-1)*i/2))            (dp[i][j] += ((sum[j+1] - sum[max(0,j-i+1)])+mod)%mod)%=mod;        sum[0] = 0;        REP(j,0,k)(sum[j+1] = sum[j] + dp[i][j])%=mod;    }    cout<<dp[n][k]<<endl;    return 0;}

BZOJ2466

传送门
很好理解的,dp[i][0/1][0/1]代表以i为子树,按不按,亮不亮的子树全亮最小方案数。
显然的是,dp[i][1][0]这个状态没用,因为按了还不亮去更新其他的只会让答案更大。
于是我们可以推出一下dp,s为i的儿子
dp[i][0][0]=min(dp[i][0][0]+dp[s][0][1],dp[i][0][1]+dp[s][1][1])
dp[i][0][1]=min(dp[i][0][1]+dp[s][0][0],dp[i][0][0]+dp[s][1][1])
dp[i][1][1]+=dp[s][0][1]
记住是子树全亮,所以我们后记状态都是[..][..][1]

void dfs(int x,int fa){    int t1 = 0,t2 = inf,t3 = 0;    //t1 : dp[x][0][0]    //t2 : dp[x][0][1]    //t3 : dp[x][1][1]    for(int i = be[x]; i; i = ne[i])    {        int v = to[i];        if(v != fa)        {            dfs(v,x);            int s1 = t1,s2 = t2;            t1 = min(dp[v][0][1] + s1,dp[v][1][1] + s2);            t2 = min(dp[v][1][1] + s1,dp[v][0][1] + s2);            t3 += dp[v][0][0];        }    }    dp[x][0][0] = t1;dp[x][0][1] = t2;dp[x][1][1] = t3 + 1;}

BZOJ1231

传送门
一道很简单的状压dp,设状态dp[st][i]表示当前状态,i为结尾,然后枚举上一位和这一位就好了。

/*************************************************************************    > File Name: bzoj1231.cpp    > Author: Drinkwater-cnyali    > Created Time: 2017/9/14 23:52:24************************************************************************/#include<iostream>#include<cstdio>#include<cstdlib>#include<cmath>#include<cstring>#include<algorithm>using namespace std;typedef long long LL;#define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)#define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)#define mem(a, b) memset((a), b, sizeof(a))int read(){    int sum = 0, fg = 1; char c = getchar();    while(c < '0' || c > '9') { if (c == '-') fg = -1; c = getchar(); }    while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }    return sum * fg;}const int maxn = 16;int n,k;int s[maxn+10];LL dp[70000][17];int main(){    n = read(),k = read();    REP(i,1,n)s[i] = read(),dp[1<<(i-1)][i] = 1;    REP(st,0,(1<<n)-1)    {        REP(i,1,n)        {            if(st & (1<<(i-1)))continue;            REP(j,1,n)            {                if(st & (1<<(j-1)) && abs(s[j]-s[i]) > k)                    dp[st ^ (1<<(i-1))][i] += dp[st][j];            }        }    }    LL ans = 0;    REP(i,1,n)ans += dp[(1<<n)-1][i];    cout<<ans<<endl;    return 0;}