各类DP题总结

DP


1.CF1729G.Cut Substrings

类型:字符串匹配类DP

题目描述:当字符串s的字串与字符串t相匹配时,需要在s中将子串删去,问最少的删除次数以及删除后有多少种不同的字串

思路:

  • 数据量只有500,考虑\(n^3\)三维dp

  • \(dp[i][j][0/1]\)表示在i这个位置用了j次删除操作满足题目要求,当前i位置结尾时删/不删的操作次数

  • 考虑转移,我们可以先预处理出\([i- m + 1,i]\)这个区间是否为目标串t;

    • 如果是目标串t,v[i] = 1,

    • 转移1:\(dp[i][j][1] = dp[i - m][j - 1][1] + dp[i-m][j-1][0]\)

    • 就代表了删去这个部分,利用的是i-m部分的j-1次

    • 转移0:\(dp[i][j][0] = \sum_{k = i - m + 1}^{i-1}dp[i][j][1]\)

    • 因为我现在不删那我肯定要在一个阶段里面删,那么肯定在区间\([i- m + 1,i - 1]\)里面找


    如果不是目标串t,v[i] = 0,那么我直接从上面状态转移即可,我只能转移不删的状态,即\(dp[i][j][0] = dp[i - 1][j][0] + dp[i - 1][j][1]\)

代码:

//懒得取模拿了个自动取模板子
const int mod = 1e9+7;
struct MyLL
{
    LL x;
    MyLL():x(0){}
    MyLL(LL u):x(u){}
    LL qmi(LL a,LL b){
        LL res = 1;
        while(b){
            if(b & 1) res = res * a % mod;
            a = a * a % mod;
            b >>= 1;
        }
        return res;
    }
    MyLL& inv(){
        x = qmi(x,mod - 2);
        return *this;
    }
    MyLL pow(LL k){
        MyLL z;
        z.x = qmi(x,k) % mod;
        return z;
    }
    MyLL& operator+=(const MyLL& y){
        x = (x + y.x) % mod;
        return *this;
    }
    MyLL& operator-=(const MyLL& y){
        x = ((x - y.x) % mod + mod) % mod;
        return *this;
    }
    MyLL& operator*=(const MyLL& y){
        x = (x * y.x) % mod;
        return *this;
    }
    MyLL& operator/=(const MyLL& y){
        x = (x * qmi(y.x,mod - 2)) % mod;
        return *this;
    }
    bool operator<(const MyLL& y) const {
        return x < y.x;
    }
    bool operator==(const MyLL& y) const {
        return x == y.x;
    }
    bool operator>(const MyLL& y) const {
        return x > y.x;
    }
    bool operator!=(const MyLL& y) const {
        return x != y.x;
    }
    friend std::ostream& operator<<(std::ostream& os,const MyLL& y){
        os << y.x;
        return os;	
    }
    friend std::istream& operator>>(std::istream& is,MyLL& y){
        is >> y.x;
        return is;
    }
};
MyLL operator+(MyLL x,MyLL y){
    x += y;
    return x;
}
MyLL operator-(MyLL x,MyLL y){
    x -= y;
    return x;
}
MyLL operator*(MyLL x,MyLL y){
    x *= y;
    return x;
}
MyLL operator/(MyLL x,MyLL y){
    x /= y;
    return x;
}
const int MOD = 1e9+7;
const int N = 605;
int v[N];
MyLL dp[N][N][2];
int n,m;
void solve(){
	memset(dp,0,sizeof dp);
	memset(v,0,sizeof v);
	string s,t;
	cin >> s >> t;
	n = s.size(),m = t.size();
	s=  " " + s,t = " " + t;
	for(int i = m;i <= n;i++){
		bool f = true;
		for(int j = i - m + 1,l=1;j<=i;j++,l++){
			if(s[j]!=t[l]){
				f=false;
				break;
			}
		}
		v[i] = f;
	}
	dp[0][0][0] = 1;
	for(int i = 1;i <= n;i++){
		for(int j = 0;j <= n;j++){
			if(v[i]){
				if(j) dp[i][j][1] = dp[i - m][j - 1][1] + dp[i-m][j-1][0];
				for(int k = i - m + 1;k < i;k++) dp[i][j][0] += dp[k][j][1];
			}else{
				dp[i][j][0] = dp[i - 1][j][0] + dp[i-1][j][1];
			}
		}
	}
	for(int j = 0;j <= n;j++){
		if(dp[n][j][1]>0 || dp[n][j][0]>0){
			cout << j << " " << dp[n][j][1] + dp[n][j][0] << endl;
			return;
		}
	}
}

2.CF1987D.World is Mine

类型:博弈结合思维贪心的DP

题目描述:A,B吃蛋糕,A先手,第一次可以吃任意蛋糕,接着后面吃的蛋糕的甜蜜度都得严格高于之前吃的甜蜜度的最大值,然后B再吃,B可以吃任何的蛋糕;

B希望A吃的尽可能少,A希望尽可能多吃

求两者都最优吃,A能吃多少个

思路:

  • 首先读题可知A每个甜蜜度只能吃一个蛋糕,a肯定从小到大吃最优

  • 显然将每个相同甜蜜度的蛋糕的数量记数组\(c[i]\),那么我们发现,如果B要防止A吃当前这个x的甜蜜度的蛋糕,那么B要在A吃到这个甜蜜度蛋糕之前,把\(c[x]\)这么多x的甜蜜度的蛋糕先吃光(不然a随便吃一个就行)

  • 这里就显然发现了一些策略,我b对于同一甜蜜度的蛋糕要么全部吃光,要么不吃光,如果吃一半肯定是不优

  • 那么就放现是选和不选的问题

  • 那么设计一下状态:

  • 首先第一维度是蛋糕甜蜜度个数\(i\),表示前\(i\)个甜蜜度的蛋糕

  • 自然的推出\(j\)为B吃了多少个甜蜜度种类的蛋糕(注意不是蛋糕总数,而是甜蜜度数量)

  • \(dp[i][j]\)表示前i个甜蜜度的蛋糕,B吃了\(j\)个种类蛋糕的????

  • 再思考一下,dp是最大化/最小化/计数,我们要干啥,我们要让B吃的种类数尽可能多对吧,那么种类数我是\(j\)已经固定了,那么在同样吃\(j\)种类的轮次不同,就是我B可能在第三轮就吃完了j个蛋糕,也可能在第五轮吃完了j个蛋糕,那肯定是第三轮就吃完更优,所以状态出来了

  • \(dp[i][j]\)表示前i个甜蜜度的蛋糕,B吃了\(j\)个种类蛋糕的最小轮数(还是不理解的可以想象成最小总蛋糕数,因为一轮大家都会吃一个蛋糕,直到A吃不了)

  • 转移就很简单:

  • \(dp[i][j] = min(dp[i-1][j-1] + c[i],dp[i- 1][j])\) 吃或不吃

  • 当然\(i - j >= dp[i-1][j-1] + c[i]\)时候才可以吃,\(i-j\)为A吃到i的时候需要多少轮

3.CF1875D.Jellyfish and Mex

类型:线性dp

题目描述:给你一个长度为n的数列,你要删n次,每次删除后,把删除后的mex加到答案里,问你答案最小是多少

思路:

  • 首先我们对每个数字记桶ci,发现要删一个数肯定删ci次连续删除效果更优
  • 开局先算整个数组的mex,我们只对 <= mex的数进行操作
  • 从大到小mex->0
  • \(dp[i]\) 为mex为i时候的最小代价,那么\(dp[j] = min(dp[j],dp[i] + (c[j]-1) * i + j)\)

代码:

const int N = 200005;
int dp[5010];
void solve(){
	map<int,int> h;
	int n;
	cin >> n;
	for(int i = 1;i<=n;i++){
		int t;
		cin >> t;
		h[t]++;
	}
	int mx = 0;
	memset(dp,0x3f,sizeof dp);
	for(int i = 0;i <= n;i++){
		if(!h.count(i)) {
			mx = i;
			break;
		}
	}
	dp[mx] = 0;
    for (int i = mx; i >= 0; i--){
        for (int j = i; j >= 0; j--){
            dp[j] = min(dp[j], dp[i] + (h[j]-1)*i + j);
        }
    }
    cout << dp[0] << endl;
}

4.CF1956D.Nene and the Mex Operator

类型:区间DP

题目描述:给你一个长度最多为18的数组,你可以操作最多不超过\(5\cdot10^5\)次,每次操作你可以选一个区间\([l,r]\)使得区间内的值改为该区间的\(mex\)

求数组最后通过变化最大能变成多大,并写出操作过程

思路:

  • 显然贪心不行,因为无法证明如果变mex才优

  • 我们思考dp,我们操作次数很多,我们可以使一些数慢慢变小,然后递增一样变大比如说 2 2 2我可以先 变成 0 1 2,然后总的求一次mex变成 3 3 3,这样明显更优,那有没有方式这样构造呢

  • 当然是有,我们观察区间\([l,r]\)最大变成的mex也就是\(r-l+1\)也就是区间长度

  • 例如 2 2 2我们先把他变成 0 0 0方便操作,然后看接下来的演示

  • 0 0 0 -> 1 0 0 -> 2 2 0 -> 0 2 0 -> 1 2 0 -> 3 3 3

  • 发现我们可以递归式的去求,假设我要最后一个数字搞出3那么我就要先搞2,如果要搞2那么前面得是1,很明显的递归,当这个例子最后第二个数字是3的时候最后第二个前数字也应该是3, 比如说数组变成了 3 3 3 0,那么怎么把最后变成4呢 很明显要对前面两个3再进行清零重新变成 1 2组合 组成 1 2 3 0才可以变成4,这点是本题关键,读者需好好理解,多推几个例子

  • 所以很明显的区间dp,枚举分割点即可,如果一个大区间可以由两个小区间更优变成,那么就要去看小区间有没有进行mex操作啦,因为大区间单独就不用mex操作,这样我们可以用个\(g[l][r]\)记录每对\([l,r]\)的分割点,如果没有分割点,那就说明肯定用了mex,直接进行递归求解即可,不然就分裂

代码:

const int N = 200005;
int dp[30][30];
int g[30][30];
int n;
int a[N];
int pre[N];
vt<PII> res;
int mex(int l,int r){
	for(int i = 0;i<=18;i++){
		bool f = true;
		for(int j = l;j <= r;j++){
			if(a[j]==i){
				f=false;
				break;
			}
		}
		if(f) return i;
	}
	return 19;
}
void add(int l,int r){
	res.pb({l,r});
	fill(a + l,a+r+1,mex(l,r));
}
void gt(int l,int r){
	if(l > r) return;
	if(l==r){
		while(a[l]!=1) add(l,r);
		return;
	}
	//逐渐 0 0 0 -> 1 0 0 -> 2 2 0 - > 0 2 0 -> 1 2 0
	//从0变全部是len然后保留len个,将前len-1个重构   
	gt(l,r-1),add(l,r),add(l,r-1),gt(l,r-1);
}
void ptr(int l,int r){
	if(g[l][r] == 0){
		if(pre[r]-pre[l - 1] < (r-l+1)*(r-l+1)){
			while(accumulate(a + l,a+r + 1,0ll)!=0) add(l,r);
			gt(l,r-1),add(l,r);
		}
		return;
	}
	ptr(l,g[l][r]),ptr(g[l][r]+1,r);
}
void solve(){
	cin >> n;
	for(int i = 1;i <= n;i++) cin >> a[i],pre[i] = pre[i - 1] + a[i];
	for(int len = 1;len <= n;len++){
		for(int l = 1;l + len - 1 <= n;l++){
			int r = l + len - 1;
			dp[l][r] = max(pre[r] - pre[l - 1],len*len);
			for(int k = l;k<r;k++){
				if(dp[l][k] + dp[k+1][r] > dp[l][r]){
					dp[l][r] = dp[l][k] + dp[k+1][r];
					g[l][r] = k;
				}
			}
		}
	}
	ptr(1,n);
	cout << dp[1][n] << " " << res.size() << endl;

	for(auto [k,v]:res){
		cout << k << " " << v <<endl;
	}
}
posted @ 2025-01-16 22:02  GsGXT  阅读(38)  评论(0)    收藏  举报