dp练习orz

来源:互联网 发布:c语言接口与实现 编辑:程序博客网 时间:2024/04/29 10:21

1.51nod1296
对于一段,限制只有前一个和它本身。
定义f[i][j]表示前i个数结尾是第j名的方案数。
转移也很显然。

#include <bits/stdc++.h>#define Rep(i,n) for(int i = 1;i <= n;++ i)using namespace std;const int Mod = 1e9 + 7;int n,K,L,sum[5002],f[2][5002],pos[5005];int main(){    scanf("%d%d%d",&n,&K,&L);    Rep(i,K){int x;scanf("%d",&x);++ x;pos[x] = 1;}    Rep(i,L){int x;scanf("%d",&x);++ x;pos[x] = 2;}    sum[1] = 1;sum[0] = 0;    for(int i = 2;i <= n;++ i)    {        int cur = i & 1;        Rep(j,i)        {            if(!pos[i])            {                if(!pos[i - 1])f[cur][j] = sum[i - 1];                else if(pos[i - 1] == 1)f[cur][j] = sum[j - 1];                else f[cur][j] = (sum[i - 1] - sum[j - 1] + Mod) % Mod;            }            else if(pos[i] == 1)            {                if(!pos[i - 1])f[cur][j] = (sum[i - 1] - sum[j - 1] + Mod) % Mod;                else if(pos[i] == 1)f[cur][j] = 0;                else f[cur][j] = (sum[i - 1] - sum[j - 1] + Mod) % Mod;            }            else             {                if(!pos[i - 1])f[cur][j] = sum[j - 1];                else if(pos[i - 1] == 1)f[cur][j] = sum[j - 1];                else f[cur][j] = 0;            }        }        Rep(j,i)sum[j] = (sum[j - 1] + f[cur][j]) % Mod;    }    printf("%d\n",sum[n]);    return 0;}

关键在于想到排名OTZ
2.51nod1043
水题一道,实际上我们对第一位强制让它选一个数就好了。

#include <bits/stdc++.h>#define Rep(i,n) for(int i = 1;i <= n;++ i)using namespace std;const int N = 9005;const int Mod = 1e9 + 7;int f[N],n,g[N];int main(){    f[0] = 1;    g[0] = 1;    scanf("%d",&n);    Rep(i,n)        for(int j = 9 * n;j;-- j)            for(int k = 1;k <= 9;++ k)                if(j >= k)                {                    f[j] = (f[j] + f[j - k]);                    if(f[j] >= Mod) f[j] %= Mod;                }    for(int i = 2;i <= n;++ i)        for(int j = 9 * n;j;-- j)            for(int k = 1;k <= 9;++ k)                if(j >= k)                {                    g[j] += g[j - k];                    if(g[j] >= Mod)g[j] %=  Mod;                }    int sum = 0;    for(int i = 1;i <= 9 * n;++ i)sum = (sum + 1ll * (f[i] - g[i] + Mod) * f[i] % Mod) % Mod;    printf("%d\n",sum);    return 0;}

真的得点一点DP技能树。
3.bzoj4321
考虑到这个东西肯定是第i个沙茶去更新原来的队伍。
肯定的一点是,我们的不合法状态数目会因为第i个人发生改变。
定义状态f[i][j][k]为前i个人j个不合法的,和前一个人挨着不挨着。
则:
k = 1:挨着 => 前一个人假如和i-2这个人挨着的话,那么在不拆散的情况下,就只有一个位置。如果不挨着的话就是两个位置。
考虑拆散的话,不合法数目不变。
k = 0:麻烦一点,考虑到实际上这个人有可能自己安静的呆着,也有可能去烧烧烧。
假设他安静地呆着的话,那么我们知道一共有i个空位置,但是有j+2个位置他不能去拆散。
但是这样在前两个人挨着的时候实际上只有j+1个位置不能去选。
如果他去拆别人:
有j+1个位置可以拆。
但是如果说前两个人挨着就只有j个位置可以拆了QAQ

#include<bits/stdc++.h>#define Rep(i,n) for(int i = 1;i <= n;i ++)using namespace std;int m,n;int f[1002][1002][2];typedef long long LL;const int Mod = 7777777;void add(int &x,LL y){if(y >= Mod)y %= Mod;x += y;if(x >= Mod)x -= Mod;}int main (){    f[1][0][0] = 1;    scanf("%d",&n);    for(int i = 2;i <= n;++ i)    {        for(int j = 0;j < n - 1;++ j)        {            int &st = f[i][j][1],&t = f[i][j][0];            st = f[i - 1][j][1];            if(j)add(st,f[i - 1][j - 1][0] * 2ll + f[i - 1][j - 1][1]);            add(t,j * (LL)f[i - 1][j + 1][1]);//拆别人             add(t,(j + 1) * (LL)f[i - 1][j + 1][0]);//拆别人             add(t,(i - j - 1) * (LL)f[i - 1][j][1]);            add(t,(i - j - 2) * (LL)f[i - 1][j][0]);        }    }    printf("%d\n",f[n][0][0]);    return 0;}

感谢何广荣大爷的题解orz
4.51nod1412:AVL树的种类
根据问题设计状态,f[i]表示有i个节点的AVL。
显然要枚举形态,再枚举个高度。
f[i][j]=f[ik1][j1]f[k][j1]+2f[ik1][j2]f[k][j1]

#include <bits/stdc++.h>#define Rep(i,n) for(int i = 1;i <= n;++ i)using namespace std;typedef long long LL;const int N = 2002;LL f[N][22];const int Mod = 1e9 + 7;int main(){    int n;    scanf(  "%d",&n);    f[0][0] = f[1][1] = 1;    Rep(i,n)    {        for(int j = 0;j < i;++ j)        {            for(int k = 2;k <= 16;++ k)            {                (f[i][k] += f[i - j - 1][k - 1] * f[j][k - 1]) %= Mod;                if(k >= 2)(f[i][k] += 2ll * f[i - j - 1][k - 2] * f[j][k - 1]) %= Mod;            }        }    }    LL ans = 0;    Rep(i,15)(ans += f[n][i]) %= Mod;    printf("%lld\n",ans);    return 0;}

5.51nod1354 选数字
肯定约数个数很少。
直接dp。

#include <bits/stdc++.h>#define Rep(i,n) for(int i = 1;i <= n;++ i)using namespace std;int n,K,cur,f[10001],a[1001];const int Mod = 1e9 + 7;vector<int>vec;map<int,int>mp;void Div(){    cur = 0;    vec.clear();    mp.clear();    int i;    for(i = 1;1ll * i * i < K;++ i)        if(K % i == 0)mp[i] = cur ++,vec.push_back(i);    if(K % i == 0)mp[i] = cur ++,vec.push_back(i);    for(;i;-- i)        if(K % i == 0)mp[K / i] = cur ++,vec.push_back(K / i);}void solve(){    Div();    int s = sqrt(K);    memset(f,0,sizeof(f));    f[0] = 1;    for(int i = 1;i <= n;++ i)        for(int j = cur - 1;~ j;-- j)            if(vec[j] % a[i] == 0)f[j] = (f[j] + f[mp[vec[j] / a[i]]]) % Mod;    printf("%d\n",f[cur - 1]);}int main(){    int T;scanf("%d",&T);    while(T --)    {        scanf("%d%d",&n,&K);        Rep(i,n)scanf("%d",&a[i]);        solve();    }    return 0;}

6.51nod1232
这个数位DP比较好玩啊。
首先我们注意到肯定转移的时候是记录余数的。
实际上记录每一位的余数肯定不如直接记录LCM优秀。
这样思路就显而易见了。
我们对Mod lcm{1,2..,10}建立剩余系,在数位dp的时候记录当前的选中的数字的lcm是多少,以及在剩余系下的值。
那么我们知道组成的lcm的数目是很小的,所以我们离散化一下就行了。这样我们就解决了这个题目。

#include <bits/stdc++.h>#define Rep(i,n) for(int i = 1;i <= n;++ i)using namespace std;typedef long long LL;int Id[2550],cnt,bit[125],cur[100];LL f[21][2550][100];const int Mod = 1e9 + 7;int idx(int x){return Id[x] ? Id[x] : (Id[x] = ++ cnt,cur[cnt] = x,cnt);}int gcd(int x,int y){return !y ? x : gcd(y,x % y);}int LCM(int x,int y){return x * y / gcd(x,y);}int Get(int x,int y){return !y ? x : LCM(x,y);}LL dfs(int len,int re,int se,bool flag){    if(!len)return re % cur[se] == 0;    LL st = f[len][re][se];    if(!flag && ~st)return st;    st = 0;    int t = flag ? bit[len] : 9;    for(int i = 0;i <= t;++ i)    {        int lcm = Get(cur[se],i);        int id = idx(lcm);        st += dfs(len - 1,(re * 10 % 2520 + i) % 2520,id,flag && i == t);    }    if(!flag)f[len][re][se] = st;    return st;}void init(){memset(f,-1,sizeof(f));cnt = 0;}LL solve(LL x){    if(x <= 9 && x >= 0)return x + 1;    int len = 0;    while(x)bit[++ len] = x % 10,x /= 10;    cur[0] = 1;    return dfs(len,0,0,1);}void readin(){    int T;    scanf("%d",&T);    while(T -- )    {        LL x,y;        scanf("%lld%lld",&x,&y);        printf("%lld\n",solve(y) - solve(x - 1));    }}void end(){}int main(){    init();    readin();    end();    return 0;}

7.51nod1486
这个题目我觉得自己还不是特别会。
首先,补集转换是显然的。
我们把n个石子的约束转化成补集,即:
随意走 - 至少踩一个石子+至少踩两个石子-。。。
之后就不会做了……
这个容斥太笨拙了。
我们考虑一个更加优美的容斥:
随意走 - 不合法的方案数。
我可以直接求并集我为啥非要容斥呢。
不合法的方案数,显然对于一个不合法的方案只有一种情况,就是踩到的第一块石头不是i,那么显然我们枚举一下踩了的石头,就可以把不合法的方案分为好多种:
1.首先踩了第1块的。
2.首先踩了第2块的。
3.。。。。
i - 1.首先踩了第i - 1块的。
这几组方案各不相同。
然后我们知道,不合法的方案实际上分成这样几组之后,剩下的怎么走都不合法,并且怎么走都不是相同方案。这样转移也显然了。
也就是这个玩意就是个补集转化。
好了讲完了。

0 0
原创粉丝点击