• DP


    DP乱选


    BZOJ1801: [Ahoi2009]chess 中国象棋

    给一个(N*M(le 100))的棋盘, 放若干个炮, 可以是(0)个,使得没有任何一个炮可以攻击另一个炮。请问有多少种放置方法.

    Sol:

    即每行每列最多放两个

    假如记录到现在为止每一列有(0/1/2)个炮为当前行的状态, 那么可以枚举(0/1/2)个当前行放的炮, 得出上一行的状态, 这样转移

    分类讨论一下每种放的数量下有几种转移可能(无非是组合数选一个或两个)

    然后发现(0/1/2)的个数相同的那些状态, 转移的方案是一样的(数量相同位置不同), 于是可以简化状态了

    (dp[i][j])表示当前(2,1)的列数分别是(i,j), 由于每次一行最多选两个, 又保证了上一状态列上的个数不是负数, 所以得到都是合法方案

    f[0][0][0] = 1;
        for (LL i = 1; i <= n; ++ i)
        {
            for (LL x = 0; x <= m; ++ x)
            {
                for (LL y = 0; y <= m - x; ++ y)
                {
                    // put 0
                    (f[i][x][y] += f[i - 1][x][y]) %= MOD;
                    // put 1
                    if (y >= 1) (f[i][x][y] += f[i - 1][x][y - 1]     * c[m - x - y + 1][1] % MOD) %= MOD;
                    if (x >= 1) (f[i][x][y] += f[i - 1][x - 1][y + 1] * c[y + 1][1]         % MOD) %= MOD;
                    // put 2
                    if (y >= 2) (f[i][x][y] += f[i - 1][x][y - 2]         * c[m - x - y + 2][2] % MOD) %= MOD;
                    if (x >= 2) (f[i][x][y] += f[i - 1][x - 2][y + 2]     * c[y + 2][2]         % MOD) %= MOD;
                    if (x >= 1) (f[i][x][y] += f[i - 1][x - 1][y - 1 + 1] * c[y][1] % MOD * c[m - x + 1 - y][1] % MOD) %= MOD;
                    if (i == n) (ans += f[i][x][y]) %= MOD;
                }
            }
        }
    

    LuoguP1860 新魔法药水

    (题面有点烦)

    (N(le 60))种药水, 给定买药水的售价和卖药水的回收价

    (M(le 240))种使用魔法的药水配方(一种药水由其他若干种不同药水组成), 你可以通过购买原料药水, 并花费若干次魔法合成一种药水, 来赚取差价

    先规定初始有(V)元, 所有原料药水必须在初始购买, 每种魔法可以重复使用, 产生的药水可以作为下一次魔法的原料(这样就少购买), 但最多只能用(K)

    问最大赚取的差价是多少?

    Sol:

    一开始考虑到可能有环, 那就不能直接在图上跑了

    但是如果设置这样一个状态(f[i][j][k])表示前(i)种魔法, 花费(j)元, 花费(k)魔法的最大收益, 那么状态之间的转移是不会有环的

    所以直接考虑DP这个(f[i][j][k])即可

    hint: 不是直接转移而是间接转移

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cmath>
      
    using namespace std;
    typedef long long LL;
    const LL INF = 2e9;
    const int MAXN = 62, MAXV = 1002, MAXK = 32, MAXM = 242;
      
    int n, m, V, K;
     
    LL ans;
    LL cost[MAXN], val[MAXN];
    LL mag[MAXM][MAXN], to[MAXM], num[MAXM];
    LL minc[MAXM][MAXK], ming[MAXM][MAXN][MAXK];
    LL f[MAXV][MAXK];
    void init()
    {
        memset(minc, 0x3f3f3f, sizeof minc);
        memset(ming, 0x3f3f3f, sizeof ming);
        for (int i = 1; i <= n; ++ i) minc[i][0] = cost[i];
        for (int i = 0; i <= m; ++ i)
            for (int j = 0; j <= K; ++ j)
                ming[i][0][j] = 0;
        for (int i = 1; i <= K; ++ i)
        {
            for (int j = 1; j <= m; ++ j)
            {
                for (int k = 1; k <= num[j]; ++ k) // 第j种魔法, 前k个, 消耗i次的最小花费
                    for (int p = 0; p <= i - 1; ++ p)
                        ming[j][k][i - 1] = min(ming[j][k][i - 1], ming[j][k - 1][i - 1 - p] + minc[mag[j][k]][p]);
                minc[to[j]][i] = min(minc[to[j]][i], ming[j][num[j]][i - 1]);
            }
        }
    }
     
    int main()
    {
        scanf("%d%d%d%d", &n, &m, &V, &K);
        for (int i = 1; i <= n; ++ i) scanf("%lld%lld", &cost[i], &val[i]);
        for (int i = 1; i <= m; ++ i)
        {
            scanf("%lld%lld", &to[i], &num[i]);
            for (int j = 1; j <= num[i]; ++ j) scanf("%lld", &mag[i][j]);
        }
        init();
        for (int i = 1; i <= n; ++ i)
        {
            for (int j = 0; j <= V; ++ j)
            {
                for (int k = 0; k <= K; ++ k)
                {
                    for (int p = 0; p <= k; ++ p)
                        if (j >= minc[i][p] && k >= p)
                            f[j][k] = max(f[j][k], f[j - minc[i][p]][k - p] + val[i] - minc[i][p]);
                    ans = max(ans, f[j][k]);
                }
            }
        }
        printf("%lld
    ", ans);
        return 0;
    }
    

    BZOJ1833: [ZJOI2010]count 数字计数

    Description

    ​ 给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次。

    Input

    ​ 输入文件中仅包含一行两个整数a、b,含义如上所述。

    Output

    ​ 输出文件中包含一行10个整数,分别表示0-9在[a,b]中出现了多少次。

    Sample Input

    ​ 1 99

    Sample Output

    ​ 9 20 20 20 20 20 20 20 20 20

    HINT

    ​ 30%的数据中,a<=b<=10^6;
    ​ 100%的数据中,a<=b<=10^12。

    Source

    Day1

    全网最菜做法

    (dp[i][j][k][0])表示到第(i)位为止(从后往前), 数码(j)出现了(k)次, 有没有上限限制, 的数的个数

    非零数码很好转移, (0)不能有前导的怎么办?, 其实只要把前导(0)算作(0)出现(0)次即可(本质上也就是这样), 其他转移跟普通的一样, 涉及到(dp[i][0][0][0])(-1)即可, 因为前导全是(0)的算在里面

    //又丑又长的代码
    LL dgt[20];
    int getdgt(LL x)
    {
    	int ret = 0;
    	while (x)
    	{
    		dgt[++ ret] = x % 10;
    		x /= 10;
    	}
    	return ret;
    }
    LL dp[14][10][14][2];
    LL cnt[20];
    void solve(LL x, bool flag) // 到第i位, j出现了k次, 是否有约束的数的个数
    {
        if (x == 0LL) return ;
    	memset(dp, 0, sizeof dp);
    	int len = getdgt(x);
    	for (int i = 0; i <= 9; ++ i)
    		dp[len + 1][i][0][1] = 1;
        for (int i = len; i >= 1; -- i) // 12
    	{
    		for (LL j = 0; j <= 9; ++ j) // 10
    		{
                if (j == 0) dp[i][j][0][0] += 1;
                dp[i][j][0][0] += dp[i + 1][j][0][0] * 9;
                dp[i][j][0][0] += dp[i + 1][j][0][1] * 1LL * (dgt[i] - (j < dgt[i]));
                dp[i][j][0][1] += dp[i + 1][j][0][1] * (j != dgt[i]);
    			for (int k = 1; k <= len - i + 1; ++ k) // 12
    			{
                    if (k == len - i + 1 && j == 0) continue;
    				dp[i][j][k][0] += dp[i + 1][j][k][0] * 1LL * 9 + dp[i + 1][j][k - 1][0];
                    if (j == 0 && k == 1) dp[i][j][k][0] -= 1LL;
    				dp[i][j][k][0] += dp[i + 1][j][k][1] * 1LL * (dgt[i] - (j < dgt[i])) + dp[i + 1][j][k - 1][1] * 1LL * (j < dgt[i]);
    				dp[i][j][k][1] += dp[i + 1][j][k][1] * 1LL * (j != dgt[i]) + dp[i + 1][j][k - 1][1] * 1LL * (j == dgt[i]);
    			}
    		}
    	}
    	for (int i = 0; i <= 9; ++ i) 
    	{
    		LL sum = 0;
    		for (LL j = 0; j <= len; ++ j) sum += (dp[1][i][j][0] + dp[1][i][j][1]) * j;
            if (flag) cnt[i] += sum;
            else cnt[i] -= sum;
    	}
    }
    
    int main()
    {
    	LL A = in(), B = in();
    	solve(B, 1); solve(A - 1, 0);
        for (int i = 0; i <= 9; ++ i) printf("%lld ", cnt[i]);
    	return 0;
    }
    

    其实把手算的过程模拟一遍也行, 小学数数也行

    BZOJ 2111: [ZJOI2010]Perm 排列计数

    Description

    称一个(1,2,...,N)的排列(p_1,p_2...,p_n)是Magic的,当且仅当(2le ile N)时,(p_i>p_{i/2}). 计算(1,2,...N)的排列中有多少是Magic的,答案可能很大,只能输出模(P)以后的值

    Input

    输入文件的第一行包含两个整数 n和p,含义如上所述。

    Output

    输出文件中仅包含一个整数,表示计算(1,2,⋯, N)的排列中, Magic排列的个数模 (P)的值。

    Sample Input

    20 23

    Sample Output

    16

    HINT

    (100\%)的数据中,(1 ≤ N ≤ 10^6, P ≤ 10^9)(P)是一个质数。 数据有所加强

    Sol:

    理应看到 "(p_i>p_{i/2})"就要想到树的, 然而我在傻逼达标找规律

    排列中各大小关系可以表示成一颗以 (1)为根, 节点(x)的左右儿子分别为(2x,2x+1)的这样一颗完全二叉树, 父亲小于儿子

    然后发现一个树的答案和具体包含那些数值无关, 只和大小有关(相当于离散一下)

    那么一个根, 对于两个子树内部的问题是完全独立的, 可以当做一个大小即为子树大小的子问题

    所以组合数一下分配给左右子树的数的集合即可即(F[i]=comb(i-1, p)*F[p]*F[i-1-p]), 由于数的形态固定, 这个(p)是可以求的

    int n;
    LL p;
    int nex[MAXN];
    LL fac[MAXN], ifac[MAXN];
    int getnex(int x)
    {
    	int ret = 1, k = 1, now;
    	while ((2 << k) - 1 <= x) ++ k;
    	now = (1 << k) - 1;
    	if (x - now < (1 << (k - 1))) return (1 << (k - 1)) - 1;
    	else return (2 << (k - 1)) - 1;
    }
    LL exgcd(LL a, LL b, LL & x, LL & y)
    {
    	if (!b) 
    	{
    		x = 1, y = 0;
    		return a;
    	}
    	LL d = exgcd(b, a % b, x, y);
    	x -= (a / b) * y;
    	swap(x, y);
    	return d;
    }
    LL inv(LL a)
    {
    	LL x, y;
    	exgcd(a, p, x, y);
    	return (x % p + p) % p;
    }
    LL comb(LL a, LL b) // a ge b
    {
    	return fac[a] * ifac[a - b] % p * ifac[b] % p;
    }
    void init()
    {
    	for (int i = 1; i <= n; ++ i) nex[i] = getnex(i);
    	fac[0] = ifac[0] = 1;
    	for (int i = 1; i <= n; ++ i)
    	{
    		fac[i] = fac[i - 1] * 1LL * i % p;
    		ifac[i] = inv(fac[i]);
    	}
    }
    
    LL f[MAXN];
    LL solve(LL x)
    {
    	if (f[x]) return f[x];
    	return f[x] = comb(x - 1, nex[x]) * solve(nex[x]) % p * solve(x - nex[x] - 1) % p;
    }
    
    int main()
    {
    	n = in(); p = in();
    	init();
    	f[1] = 1; f[2] = 1;
    	printf("%lld
    ", solve(n));
    	return 0;
    }
    

    2298: [HAOI2011]problem a

    Description

    一次考试共有(n)个人参加,第(i)个人说:“有(a_i)个人分数比我高,(b_i)个人分数比我低。”问最少有几个人没有说真话(可能有相同的分数)

    Input

    第一行一个整数(n),接下来(n)行每行两个整数,第(i+1)行的两个整数分别代表(a_i,b_i)

    Output

    一个整数,表示最少有几个人说谎

    Sample Input

    3

    2 0

    0 2

    2 2

    Sample Output

    1

    HINT

    100%的数据满足: 1≤n≤100000 0≤ai、bi≤n

    Sol:

    首先将(a_i, b_i)转化成((b_i,n-a_i)), 这样就代表(<)自己的个数, (le)自己的个数

    那么这样一个二元组唯一对应一种分数, 并且两个二元组之间要么完全相同, 要么一个的前一个数和另一个的后一个数相同

    然后想啊想, 用前缀和? 差分? 排名? 线段!

    把上述的二元组放在数轴上, 要求的就是最多能修改的线段数

    首先如果同一个线段重复次数超过它本身的长度, 那就减去多余的部分, 因为既然存在的次数超限了, 不管怎么修改其他种类的线段都不行, 只能把自己多余的部分修改, 必须修改

    那么这样所有线段的重复次数就合法了, 要求最多不修改的数量, 就是要求最多保留线段, 使他们不想交或重叠(可以收尾相接), 这样就可做了

    struct Seg
    {
    	int l, r, rpt;
    } seg[MAXN], tmp[MAXN];
    bool cmp(Seg a, Seg b)
    {
    	return (a.r == b.r) ? (a.l < b.l) : (a.r < b.r);
    }
    
    void init()
    {
    	sort(seg + 1, seg + n + 1, cmp);
    	int realn = 0;
    	for (int i = 1; i <= n; ++ i)
    	{
    		if (i == 1 || (seg[i].l != seg[i - 1].l || seg[i].r != seg[i - 1].r))
    			tmp[++ realn] = seg[i];
    		else 
    			++ tmp[realn].rpt;
    	}
    	n = realn;
    	for (int i = 1; i <= n; ++ i) 
    	{
    		seg[i] = tmp[i];
    		if (seg[i].rpt > seg[i].r - seg[i].l)
    			seg[i].rpt = seg[i].r - seg[i].l;
    	}
    }
    
    int f[MAXN];
    
    int main()
    {
    	n = in(); m = n;
    	for (int i = 1; i <= n; ++ i)
    	{
    		int a = in(), b = in();
    		seg[i] = (Seg) { b, n - a, 1 };
    	}
    	init();
    	int las = 0;
    	for (int i = 1; i <= n; ++ i)
    	{
    		for (int j = las + 1; j <= seg[i].r; ++ j)
    			f[j] = max(f[j], f[j - 1]);
    		f[seg[i].r] = max(f[seg[i].r], f[seg[i].l] + seg[i].rpt);
    		las = seg[i].r;
    	}
    	printf("%d
    ", m - f[seg[n].r]);
    	return 0;
    }
    

    NOIP2017D2T2 宝藏

    题意

    (n le 12) 有边权的无向图, 定义一颗生成树权值为 边权*深度 的和
    求最小的生成树

    Sol;

    状压, 但是我不会准确证明复杂度, 只是差不多能卡过去

    f[u][dep][sta] 表示这个点 u 距离选的根深度为 dep, 已选的点集为 sta (生成树上 u 的子树), 以这个点为子树根的最小代价
    所以只要枚举儿子 v , 和儿子的点集 res , res 是 sta 的子集,
    设 pre = sta - res, 那么容易得到

    [f[u][dep][sta] = min{ f[u][dep][pre] + f[v][dep + 1][res] + dis[u][v] * dep } ]

    那么从大到小枚举 dep, 从小到大枚举 sta, 就可以保证子状态都已经计算到

    如果采用高效的枚举子集, 枚举子集内的元素的方法, 那么复杂度应该是 深度 * 点 * 枚举子集的子集 * 点, 也就是 (O(n ^ 3 * 3 ^ n))
    但是其中有些状态是无意义的, 比如深度很大, 而点集已经选满, 或者枚举的点不在集合里
    我用了记搜来避免上述两个无意义的枚举

    然后还有玄学剪枝, 即若某个 f 值已经 > ans 了, 那么把他设为极大值, 下次枚举到后继状态是他自然会 continue

    PS: 除了交了一发假算法, 没有用 lowbit 之类的之前也能 90~95 , 应该是差不多了吧

    int n, m, K;
    
    int wgt[13][13];
    int siz[(1 << 12) + 10], lowb[(1 << 12) + 10], getd[(1 << 12) + 10];
    
    int lowbit(int & x) { return x & (-x); }
    
    int f[13][13][(1 << 12) + 10];
    const int INF = 1e9;
    int ans = INF;
    void DFS(int u, int dep, int sta)
    {
    //    printf("%d %d %d
    ", u, dep, sta);
        if (f[u][dep][sta] < INF) return ;
        int tmp = sta ^ (1 << (u - 1));
        for (int s = (tmp - 1) & tmp; ; s = (s - 1) & tmp)
        {
    	int pre = s + (1 << (u - 1));
    	DFS(u, dep, pre);
    	if (f[u][dep][pre] >= f[u][dep][sta]) 
    	{
    	    if (!s) break;
    	    continue;
    	}
    	int res = sta - pre;
    	for (int S = res; S; S ^= lowb[S])
    	{
    	    int v = getd[lowb[S]] + 1;
    	    if (wgt[u][v] == -1) continue;
    	    DFS(v, dep + 1, res);
    	    if (f[v][dep + 1][res] >= INF-1) continue;
    	    f[u][dep][sta] = min(f[u][dep][sta], f[u][dep][pre] + f[v][dep + 1][res] + wgt[u][v] * dep);
    	}
    	if (!s) break;
        }
        if (f[u][dep][sta] == INF) f[u][dep][sta] --;
        if (sta == (1 << n) - 1) ans = min(ans, f[u][dep][sta]);
        if (f[u][dep][sta] >= ans) f[u][dep][sta] = INF - 1;
    }
    /*
      0741
    */
    int main()
    {
        n = in(); m = in();
        memset(wgt, -1, sizeof wgt);
        for (int i = 1; i <= m; ++ i)
        {
    	int u = in(), v = in(), w = in();
    	if (wgt[u][v] == -1 || w < wgt[u][v]) wgt[u][v] = wgt[v][u] = w;
        }
        for (int i = 0; i < n; ++ i) getd[1 << i] = i;
        for (int i = 1; i < (1 << n); ++ i) 
        {
    	lowb[i] = lowbit(i);
    	siz[i] = siz[lowb[i]] + 1;
        } 
        for (int i = 0; i <= n; ++ i)
    	for (int k = 1; k <= n; ++ k)
    	    for (int j = 0; j < (1 << n); ++ j)
    		f[i][k][j] = INF;
        for (int i = 1; i <= n; ++ i) 
    	for (int j = 1; j <= n; ++ j) 
    	    f[i][j][1 << (i - 1)] = 0;
        for (int i = 1; i <= n; ++ i) DFS(i, 1, (1 << n) - 1);
        if (ans == INF) ans = 0;
        printf("%d
    ", ans);
        return 0;
    }
    
  • 相关阅读:
    Nacos 1.4.0 集群搭建
    docker mysql5.7
    java设计模式之简单工厂模式
    关于兑现
    Linux用户相关
    centos7开机自启动
    Shell脚本记录日志到文件
    .NetCore常用单元测试框架
    Exchange邮件开发
    Spark——Yarn模式下的日志存储及配置
  • 原文地址:https://www.cnblogs.com/Kuonji/p/11857530.html
Copyright © 2020-2023  润新知