好题总结汇总

发布于:2024-05-10 ⋅ 阅读:(26) ⋅ 点赞:(0)

好题总结汇总

总结一些做完很有收获的题。


一、经典问题 + DP的结合

1、题意:

给定 n n n 种颜色的球的数量 a 1 , a 2 , . . . , a n a_1, a_2, ..., a_n a1,a2,...,an,选出一些不同种类的球(也就是在n种球中选球的任意情况),将球两两组合并且这两个球不能一致,或者将1个球看成一组,找到选出来的这些球的最小组数,球所有情况的最小组合数之和。

2、总结:

(1) 先对 a a a 数组进行排序。

(2) 假如我们已经选到了 n n n 个球的数量 a 1 , a 2 , . . . , a n , a 1 ≤ a 2 ≤ . . . ≤ a n a_1, a_2, ..., a_n, a_1 \le a_2 \le ... \le a_n a1,a2,...,an,a1a2...an 按照k个不同小球组合在一起或者是1个个组合的最小组合数是多少?
结论是:
r e s u l t = m a x ( a [ n ] , c e i l ( ∑ a [ i ] / k ) ) result = max(a[n], ceil(\sum{a[i]} / k)) result=max(a[n],ceil(a[i]/k))

(3) 当我们有个这个结论,我们就可以直接进行背包 d p dp dp 了,定义 d p dp dp 数组为: d p [ i ] [ j ] dp[i][j] dp[i][j],表示前i个物品中必选 a [ i ] a[i] a[i], a [ i ] a[i] a[i] 为最大值,且体积为 j j j 的方案数。
转移方程为: d p [ 0 ] [ 0 ] = 1 , d p [ i ] [ j ] = ∑ d p [ 1.... i − 1 ] [ j ] dp[0][0] = 1, dp[i][j] = \sum{dp[1....i-1][j]} dp[0][0]=1,dp[i][j]=dp[1....i1][j],复杂度 O ( n 2 ) O(n^2) O(n2)

(4) 求取答案, A n s = ∑ d p [ i ] [ j ] ∗ m a x ( a [ i ] , c e i l ( j / k ) ) Ans = \sum{dp[i][j] * max(a[i], ceil(j / k))} Ans=dp[i][j]max(a[i],ceil(j/k))

3、代码:
#include <bits/stdc++.h> 
#define int long long 
using namespace std; 
const int N = 5010 + 10; 
const int mod = 998244353; 
int n, m; 
int a[N];
int dp[N][N]; 
void solve() {
    cin >> n; 
    for(int i = 1; i <= n; ++ i ) cin >> a[i]; 
    sort(a + 1, a + 1 + n); 
    
    dp[0][0] = 1; 
    
    int su[N] {0};
    su[0]=1;
    dp[0][0]=1;
    for(int i = 1; i <= n; ++ i ) {
        for(int j = a[i]; j <= 5000; ++ j ) { 
            dp[i][j] += su[j-a[i]];
            // su[j] = (dp[i][j] + su[j]) % mod; 
        }
        for(int j = 0; j <= 5000; ++ j ) 
            su[j] = (su[j] + dp[i][j]) % mod;
    }
    // cout<<dp[1][1]<<endl;
    int ans = 0; 
    for(int i = 1; i <= n; ++ i ) 
        for(int j = 0; j <= 5000; ++ j ) {
            ans = (ans + dp[i][j] * max((int)ceil(j * 1.0 / 2), a[i]) % mod) % mod;
            // cout<<dp[i][j]<<' '<<max((int)ceil(j * 1.0 / 2), a[i])<<endl;
        }
    cout<<ans<<endl;
}
signed main() {
    int ts = 1;  
    // cin >> ts; 
    while(ts -- ) solve(); 
    
    return 0; 
}

二、数学公式推导

1、题意:

在这里插入图片描述

2、题解:

非常妙的一道题,直接开始推导:
假设我们划分 a a a 个’L’形, b b b 个2*2连通块。
我们可以得到一个方程组:
T T T 是一个未知的非负整数,满足 a ∗ 3 + b ∗ 4 = T a * 3 + b * 4 = T a3+b4=T
b = ( T − 3 ∗ a ) / 4 b=(T{-}3*a)/4 b=(T3a)/4
f ( a , b ) = f ( a , ( T − 3 ∗ a ) / 4 ) = a ∗ x + b ∗ y = a ∗ x + ( T − 3 ∗ a ) / 4 ∗ y = ( x − 3 ∗ y / 4 ) ∗ a + T ∗ y / 4 f(a, b) = f(a,(T{-}3*a)/4) = a * x + b * y = a * x + (T{-}3*a)/4*y = (x-3*y/4)*a+T*y/4 f(a,b)=f(a,(T3a)/4)=ax+by=ax+(T3a)/4y=(x3y/4)a+Ty/4
我们发现就是一个有关a的一次函数,我们分类讨论根据单调性求即可。

3、代码:
#include<bits/stdc++.h>
#define int long long 
using namespace std; 
const int N = 1e5 + 10; 

int n,x,y;
signed main() {
    cin>>n>>x>>y;
    if(n==2){
        cout<<max(x,y)<<endl;
        return 0; 
    }
    if(x * 4 == 3 * y) {
        cout<<y*n*n/4<<endl;
        return 0; 
    } 
    
    if(x * 4 <= 3 * y) {
        cout<<n*n/4*y<<endl;
    }
    else {
        int ans=n*n/3*x;
        if(n*n%3) {
            ans-=(x-max(x,y));
        }
        cout<<ans<<endl;
    }
    
    return 0; 
}

三、


网站公告

今日签到

点亮在社区的每一天
去签到