dp练习orz

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 <script id="MathJax-Element-112" type="math/tex">i</script>,那么显然我们枚举一下踩了的石头,就可以把不合法的方案分为好多种:
1.首先踩了第1块的。
2.首先踩了第2块的。
3.。。。。
i - 1.首先踩了第i - 1块的。
这几组方案各不相同。
然后我们知道,不合法的方案实际上分成这样几组之后,剩下的怎么走都不合法,并且怎么走都不是相同方案。这样转移也显然了。
也就是这个玩意就是个补集转化。
好了讲完了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值