hq的模板1

title: 模板整理
date: 2019-03-21 19:00:36
tags: 模板
在这里插入图片描述

1.高斯消元
// a[N][N]是增广矩阵
int gauss()
{
    int c,r;
    for(c=0,r=0;c<n;c++)
    {
        int t=r;
        for (int i=r;i<n;i++)   // 找到绝对值最大的行
            if (fabs(a[i][c])>fabs(a[t][c]))
                t=i;

        if (fabs(a[t][c])<eps) 
            continue;

        for (int i=c;i<=n;i++) 
            swap(a[t][i],a[r][i]);      // 将绝对值最大的行换到最顶端
        for (int i= n;i>=c;i--) 
            a[r][i]/=a[r][c];      // 将当前上的首位变成1
        for (int i=r+1;i<n;i++)       // 用当前行将下面所有的列消成0
            if (fabs(a[i][c])>eps)
                for (int j=n;j>=c;j--)
                    a[i][j]-=a[r][j]*a[i][c];

        r++;
    }

    if(r<n)
    {
        for (int i=r;i<n;i++)
            if (fabs(a[i][n])>eps)
                return 2; // 无解
        return 1; // 有无穷多组解
    }

    for (int i=n-1;i>=0;i--)
        for (int j =i + 1;j<n;j ++ )
            a[i][n]-=a[i][j]*a[j][n];

    return 0; // 有唯一解
}
2.求组合数

C n m = n ! m ! ( n − m ) ! C_{n}^{m}=\frac{n!}{m!\left( n-m \right) !} Cnm=m!(nm)!n!

(1)递推
// c[a][b] 表示从a个苹果中选b个的方案数
for (int i=0;i<N;i++ )
    for (int j=0;j<=i;j++)
        if (!j) 
            c[i][j]=1;
        else 
            c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;

粗暴计算法,只适合于较小的数,复杂度为O(n):

ll C(int n,int m)
{
	if(m<n-m)
	m=n-m;
	
	ll ans=1;
	for(ll i=m+1;i<=n;i++)
	ans*=i;
	for(ll i=1;i<=n-m;i++)
	ans/=i;
	
	return ans;
}
(2)预处理逆元
//首先预处理出所有阶乘取模的余数fact[N],以及所有阶乘取模的逆元infact[N]
//如果取模的数是质数,可以用费马小定理求逆元
int qmi(int a,int k,int p)    // 快速幂模板
{
    int res=1;
    while(k)
    {
        if (k&1) 
            res=(LL)res*a%p;
        a=(LL)a*a%p;
        k>>=1;
    }
    return res;
}

// 预处理阶乘的余数和阶乘逆元的余数
fact[0]=infact[0]=1;
for (int i=1;i<N;i++ )
{
    fact[i]=(LL)fact[i-1]*i%mod;
    infact[i]=(LL)infact[i-1]*qmi(i,mod-2,mod)%mod;
}
(3)分解质因数法求组合数
//当我们需要求出组合数的真实值,而非对某个数的余数时,分解质因数的方式比较好用:
//    1. 筛法求出范围内的所有质数
//    2. 通过 C(a, b) = a! / b! / (a - b)! 这个公式求出每个质因子的次数。 
//		 n! 中p的次数是 n / p + n / p^2 + n / p^3 + ...
//    3. 用高精度乘法将所有质因子相乘

int primes[N], cnt;     // 存储所有质数
int sum[N];     // 存储每个质数的次数
bool st[N];     // 存储每个数是否已被筛掉


void get_primes(int n)      // 线性筛法求素数
{
    for (int i=2;i<=n;i++ )
    {
        if (!st[i]) 
            primes[cnt++]=i;
        for (int j=0;primes[j]<=n/i;j++)
        {
            st[primes[j]*i]=true;
            if (i%primes[j]==0) 
                break;
        }
    }
}


int get(int n,int p)       // 求n!中的次数
{
    int res=0;
    while(n)
    {
        res+= n/p;
        n/=p;
    }
    return res;
}


vector<int> mul(vector<int> a, int b)       // 高精度乘低精度模板
{
    vector<int> c;
    int t = 0;
    for(int i=0;i<a.size();i++ )
    {
        t+=a[i]*b;
        c.push_back(t%10);
        t/=10;
    }

    while(t)
    {
        c.push_back(t % 10);
        t /= 10;
    }

    return c;
}

int main()
{
    get_primes(a);  // 预处理范围内的所有质数

for (int i = 0;i<cnt;i++)     // 求每个质因数的次数
{
    int p=primes[i];
    sum[i]=get(a,p)-get(b,p)-get(a-b,p);
}

vector<int> res;
res.push_back(1);

for(int i=0;i<cnt;i++)     // 用高精度乘法将所有质因子相乘
    for (int j=0;j<sum[i];j ++ )
        res=mul(res,primes[i]);
    
    return 0;
}
3.Lucas定理
//若p是质数,则对于任意整数 1<=m<=n,有:
//    C(n, m) = C(n%p,m%p)*C(n/p,m/p)(mod p)

int qmi(int a, int k)       // 快速幂模板
{
    int res=1;
    while (k)
    {
        if (k&1) res=(LL)res*a%p;
        a=(LL)a*a%p;
        k>>=1;
    }
    return res;
}


int C(int a, int b)     // 通过定理求组合数C(a, b)
{
    int res=1;
    for (int i=1,j=a;i<=b;i++,j-- )
    {
        res=(LL)res*j%p;
        res=(LL)res*qmi(i,p-2)%p;
    }
    return res;
}


int lucas(LL a, LL b)
{
    if (a<p&&b<p) 
        return C(a,b);
    return (LL)C(a%p,b%p)*lucas(a/p,b/p)%p;
}
4.卡特兰数

给定n个0和n个1,它们按照某种顺序排成长度为2n的序列,满足任意前缀中0的个数都不少于1的个数的序列的数量为: Cat(n) = C(2n, n) / (n + 1)

int quick_pow(int a,int k,int p){
    int res=1;
    while(k){
        if(k&1) res=(ll)res*a%p;
        k>>=1;
        a=(ll)a*a%p;
    }
    return res;
}

int main(){
    cin>>n;
    int a=2*n,b=n;
    int res=1;

    for(int i=1,j=a;i<=b;i++,j--){
        res=(ll)res*j%mod;
        res=(ll)res*quick_pow(i,mod-2,mod)%mod;
    }
    res=(ll)res*quick_pow(n+1,mod-2,mod)%mod;

    cout<<res<<endl;

}
5.二分

整型二分 特别要注意边界问题

bool check(int x) {/* ... */} // 检查x是否满足某种性质

// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(intl,intr)
{
    while(l<r)
    {
        int mid=l+r>>1;
        if(check(mid)) 
            r=mid;    // check()判断mid是否满足性质
        else l=mid+1;
    }
    return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
    while(l<r)
    {
        int mid=l+r+1>>1;
        if (check(mid)) 
            l=mid;
        else 
            r=mid-1;
    }
    return l;
}

浮点数二分

bool check(doublex) {/* ... */} // 检查x是否满足某种性质

double bsearch_3(double l, double r)
{
    const double eps=1e-6;   // eps 表示精度,取决于题目对精度的要求
    while(r-l>eps)
    {
        double mid=(l+r)/ 2;
        if(check(mid)) 
            r=mid;
        else l=mid;
    }
    return l;
}
6.高精度

(1)加法

// C = A + B, A >= 0, B >= 0
vector<int> add(vector<int> &A, vector<int> &B)
{
    if (A.size()<B.size()) 
        return add(B, A);

    vector<int> C;
    int t=0;
    for (int i=0;i<A.size();i++ )
    {
        t+=A[i];
        if(i<B.size()) 
            t+=B[i];
        C.push_back(t%10);
        t/=10;
    }

    if (t) 
        C.push_back(t);
    return C;
}

(2)减法

// C = A - B, 满足A >= B, A >= 0, B >= 0
vector<int> sub(vector<int> &A, vector<int> &B)
{
    vector<int> C;
    for (int i=0,t=0;i<A.size();i++)
    {
        t=A[i]-t;
        if(i<B.size()) 
            t-=B[i];
        C.push_back((t+10)%10);
        if(t<0) 
            t=1;
        else
            t=0;
    }

    while(C.size()>1&&C.back()==0)
        C.pop_back();
    return C;
}

(3)高精度*低精度

// C = A * b, A >= 0, b > 0
vector<int> mul(vector<int> &A, int b)
{
    vector<int> C;
    int t=0;
    for (int i=0;i<A.size()||t;i++)
    {
        if (i<A.size()) 
            t+=A[i]*b;
        C.push_back(t%10);
        t/=10;
    }

    return C;
}

(4)高精度/低精度

// A / b = C ... r, A >= 0, b > 0
vector<int> div(vector<int> &A, int b, int &r)
{
    vector<int> C;
    r = 0;
    for(int i=A.size()-1;i>=0;i--)
    {
        r=r*10+A[i];
        C.push_back(r/b);
        r%=b;
    }
    reverse(C.begin(),C.end());
    while (C.size()>1&&C.back()==0) 
        C.pop_back();
    return C;
}
7.离散化
vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());   // 去掉重复元素

// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于x的位置
{
    int l=0,r=alls.size()-1;
    while(l<r)
    {
        int mid=l+r>>1;
        if(alls[mid]>=x) 
            r=mid;
        else l=mid+1;
    }
    return r+1; // 映射到1, 2, ...n
}
8.哈希
//(1) 拉链法
    int h[N], e[N], ne[N], idx;

    // 向哈希表中插入一个数
    void insert(int x)
    {
        int k=(x%N+N)%N;
        e[idx]=x;
        ne[idx]=h[k];
        h[k]=idx++ ;
    }

    // 在哈希表中查询某个数是否存在
    bool find(int x)
    {
        int k=(x%N+N)%N;
        for (int i=h[k];i!=-1;i=ne[i])
            if (e[i]==x)
                return true;

        return false;
    }

//(2) 开放寻址法
    int h[N];

    // 如果x在哈希表中,返回x的下标;如果x不在哈希表中,返回x应该插入的位置
    int find(int x)
    {
        int t=(x%N+N)%N;
        while(h[t]!=null&&h[t]!=x)
        {
            t++;
            if(t==N) 
                t=0;
        }
        return t;
    }

字符串哈希

//核心思想:将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低
//小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果

typedef unsigned long long ULL;
ULL h[N], p[N]; // h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64

// 初始化
p[0]=1;
for (int i=1;i<=n;i++)
{
    h[i]=h[i-1]*P+str[i];
    p[i]=p[i-1]*P;
}

// 计算子串 str[l ~ r] 的哈希值
ULL get(int l, int r)
{
    return h[r]-h[l-1]*p[r-l+1];
}
9.KMP
求Next数组:
// s[]是模式串,p[]是模板串, n是s的长度,m是p的长度
for (int i=2,j=0;i<=m;i++)
{
    while(j&&p[i]!=p[j+1]) 
        j=ne[j];
    if (p[i]==p[j+1]) 
        j++;
    ne[i]=j;
}

// 匹配
for (int i=1,j=0;i<=n;i++)
{
    while(j&&s[i]!=p[j+1]) 
        j=ne[j];
    if(s[i]==p[j+1]) 
        j++;
    if(j==m)
    {
        j=ne[j];
        // 匹配成功后的逻辑
    }
}
10.trie树
int son[N][26], cnt[N], idx;
// 0号点既是根节点,又是空节点
// son[][]存储树中每个节点的子节点
// cnt[]存储以每个节点结尾的单词数量

// 插入一个字符串
void insert(char *str)
{
    int p=0;
    for(int i=0;str[i];i++)
    {
        int u=str[i]-'a';
        if (!son[p][u]) 
            son[p][u]= ++idx;
        p=son[p][u];
    }
    cnt[p]++ ;
}

// 查询字符串出现的次数
int query(char *str)
{
    int p=0;
    for(int i=0;str[i];i++)
    {
        int u=str[i]-'a';
        if(!son[p][u]) 
            return 0;
        p=son[p][u];
    }
    return cnt[p];
}
11.拓扑排序
bool topsort()
{
    int hh=0,tt=-1;

    // d[i] 存储点i的入度
    for(inti=1;i<=n;i++)
        if(!d[i])
            q[++tt]=i;

    while(hh<=tt)
    {
        int t=q[hh++];

        for (int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            if (--d[j]==0)
                q[++tt]=j;
        }
    }

    // 如果所有点都入队了,说明存在拓扑序列;否则不存在拓扑序列。
    return tt==n-1;
}
12.堆优化dij O(mlogn)
typedef pair<int, int> PII;

int n;      // 点的数量
int h[N],w[N],e[N],ne[N],idx;       // 邻接表存储所有边
int dist[N];        // 存储所有点到1号点的距离
bool st[N];     // 存储每个点的最短距离是否已确定

// 求1号点到n号点的最短距离,如果不存在,则返回-1
int dijkstra()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0,1});      // first存储距离,second存储节点编号

    while (heap.size())
    {
        auto t=heap.top();
        heap.pop();

        int ver=t.second,distance=t.first;

        if (st[ver]) 
            continue;
        st[ver] = true;

        for (int i=h[ver];i!=-1;i=ne[i])
        {
            int j=e[i];
            if (dist[j]>distance+w[i])
            {
                dist[j]=distance+w[i];
                heap.push({dist[j],j});
            }
        }
    }

    if (dist[n]==0x3f3f3f3f) 
        return -1;
    return dist[n];
}
13.spfa 算法(队列优化的Bellman-Ford算法)

时间复杂度 平均情况下 O(m),最坏情况下 O(nm), n表示点数,m表示边数

int n;      // 总点数
int h[N], w[N], e[N], ne[N], idx;       // 邻接表存储所有边
int dist[N];        // 存储每个点到1号点的最短距离
bool st[N];     // 存储每个点是否在队列中

// 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1
int spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    queue<int> q;
    q.push(1);
    st[1] = true;

    while (q.size())
    {
        auto t = q.front();
        q.pop();

        st[t] = false;

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                if (!st[j])     // 如果队列中已存在j,则不需要将j重复插入
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}
14.Kruskal算法

时间复杂度是 O(mlogm), n表示点数,m表示边数

int n, m;       // n是点数,m是边数
int p[N];       // 并查集的父节点数组

struct Edge     // 存储边
{
    int a, b, w;

    bool operator< (const Edge &W)const
    {
        return w < W.w;
    }
}edges[M];

int find(int x)     // 并查集核心操作
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int kruskal()
{
    sort(edges, edges + m);

    for (int i = 1; i <= n; i ++ ) p[i] = i;    // 初始化并查集

    int res = 0, cnt = 0;
    for (int i = 0; i < m; i ++ )
    {
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;

        a = find(a), b = find(b);
        if (a != b)     // 如果两个连通块不连通,则将这两个连通块合并
        {
            p[a] = b;
            res += w;
            cnt ++ ;
        }
    }

    if (cnt < n - 1) return INF;
    return res;
}
15.二分图染色

O(n+m)

int n;      // n表示点数
int h[N],e[M],ne[M],idx;     // 邻接表存储图
int color[N];       // 表示每个点的颜色,-1表示为染色,0表示白色,1表示黑色

// 参数:u表示当前节点,c表示当前点的颜色
bool dfs(int u, int c)
{
    color[u]=c;
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(color[j]==-1)
        {
            if(!dfs(j,!c)) 
                return false;
        }
        else if(color[j]==c) 
            return false;
    }

    return true;
}

bool check()
{
    memset(color,-1,sizeof color);
    bool flag=true;
    for (int i=1;i<=n;i++)
        if (color[i]==-1)
            if(!dfs(i,0))
            {
                flag=false;
                break;
            }
    return flag;
}
16.匈牙利算法

O(nm)

int n1, n2;     // n1表示第一个集合中的点数,n2表示第二个集合中的点数
int h[N], e[M], ne[M], idx;     // 邻接表存储所有边,匈牙利算法中只会用到从第二个集合指向第一个集合的边,所以这里只用存一个方向的边
int match[N];       // 存储第二个集合中的每个点当前匹配的第一个集合中的点是哪个
bool st[N];     // 表示第二个集合中的每个点是否已经被遍历过

bool find(int x)
{
    for (int i=h[x];i!=-1;i=ne[i])
    {
        int j=e[i];
        if (!st[j])
        {
            st[j]=true;
            if (match[j]==0||find(match[j]))
            {
                match[j]=x;
                return true;
            }
        }
    }

    return false;
}

// 求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点
int res=0;
for (int i= 1;i<=n1;i++)
{
    memset(st,false,sizeof st);
    if(find(i)) 
        res ++ ;
}
一、01背包

对于每个物品只有选和不选两种状态

枚举重量 从大到小

题目链接

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn=1e4+5;
int v[maxn],w[maxn];//二维动态规划
int dp[maxn][maxn];//表示前i个物品,当前使用重量为j的最大价值
int main()
{
    int n,c;
    cin>>n>>c;
    for(int i=1;i<=n;i++)
    cin>>w[i]>>v[i];
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=c;j++)
        {
            dp[i][j]=dp[i-1][j];
            if(w[i]<=j)//要判断能不能装下
            dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
        }
    }
   // cout<<dp[n][c]<<endl; 可以重量不达到c,也可以不装满所有n个物品,注意题目中的“前”
   int ans=0;
   for(int i=0;i<=c;i++)
   ans=max(ans,dp[n][i]);//找出最大价值
   cout<<ans<<endl;
    return 0;
}

我们可以看出,二维可以优化为一维。

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn=1e3+5;
int v[maxn],w[maxn];//二维动态规划
int dp[maxn];//当前使用重量为j的最大价值
//注意:初始化很关键
//这里所有的位置都被初始化为0,所以dp[i]的含义变成了重量小于等于w[i]时的最大价值
//k<m时
//dp[k]=max_v;
//dp[0]=0  -->  f[w[0]]=v[0]  -->...
//dp[m-k]=0  -->  f[m-k+w[0]]=v[0]  -->...  相比较而言 不过是多了一个偏移量m-k
//所以不用去枚举所有质量啦,结果就是dp[c]
//而如果 最开始初始化成 dp[0]=0,dp[其他]=-inf
//则所有状态都将由dp[0][0]转移而来,最后就要for循环去找最大的dp[i]

//若题意变成了 重量恰好是c时的最大价值
//则初始化dp[0]=0,dp[i]=-inf,  最终结果就是dp[c]
int main()
{
    int n,c;
    cin>>n>>c;
    for(int i=1;i<=n;i++)
    cin>>w[i]>>v[i];

    for(int i=1;i<=n;i++)
        for(int j=c;j>=w[i];j--)//换一下 从大到小枚举,w[i]>0 则j-w[i]一定是没有被算过的,保证j-w[i]是i-1的 而不是i的
        {
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
        }

   // cout<<dp[n][c]<<endl; 可以重量不达到c,也可以不装满所有n个物品,注意题目中的“前”
   //int ans=0;
   //for(int i=0;i<=c;i++)
   //ans=max(ans,dp[i]);//找出最大价值
   cout<<dp[c]<<endl;
    return 0;
}
二、完全背包

这些物品都可以被选择无穷多次

枚举重量 从小到大

题目链接

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn=1e3+5;
int v[maxn],w[maxn];//二维动态规划
int dp[maxn];//当前使用重量为i的最大价值
//仍然可以从二维优化到一维,所以我们直接用一维做就好了
//与01背包的区别在于,每件物品可以用无限次  这个可以用dfs?
//把所有值都初始化成了0
int main()
{
    int n,c;
    cin>>n>>c;
    for(int i=1;i<=n;i++)
    cin>>w[i]>>v[i];
    
    for(int i=1;i<=n;i++)
    {
        for(int j=w[i];j<=c;j++)//换回来,就可以表示可以取无限多次,就变成了完全背包
        {
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);//从小到大的话,dp[j-w[i]]其实就已经被算过了, 其中可能已经包括了若干个第i个物品了
            //(所有比j小的都被算过了,注意这里的dp[j]=...dp[j-w[i]])
            //所以一定可以枚举到最优解
        }
        /*通过数学归纳法证明
        1.假设考虑前i-1个物品之后,所有的dp[j]都是正确的
        2.现在要证明,考虑前i个物品后,所有的dp[j]也都是正确的
        
        对于某个j而言,如果最优解中包含k个v[i]
        从小到大枚举时,一定可以枚举到dp[j-k*w[i]]  我们会用dp[j-k*w[i]-w[i]]+v[i]来更新它 
        //                         -->  dp[j-(k-1)*w[i]-w[i]]+v[i]=dp[j+"w[i]"-k*w[i]]+v[i]    包含一个i物品
        //                         -->  dp[j-(k-2)*w[i]-w[i]]+v[i]                             包含两个i物品
        //                              ......
        //                         -->  dp[j-w[i]]+v[i] ==dp[j-(k-(k-1))*w[i]]         计算过只包含k-1个i物品这种状态
        //                         -->  dp[j]                                          就包含了k个i物品  -->找到最优解
        */
        
   /* 
    for(int j=c;j>=w[i],j--)
        for(int k=0;k*w[i]<=j;k++)
            dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i]);//可以选无穷多个  dp[j-k*w[i]] 这个里面是一定没有选第i个物品的 是i-1的
            //自己列一下01背包的动态规划二维表也许更好理解?
    */
    } 
   // cout<<dp[n][c]<<endl; 可以重量不达到c,也可以不装满所有n个物品,注意题目中的“前”
   //int ans=0;
   //for(int i=0;i<=c;i++)
   //ans=max(ans,dp[i]);//找出最大价值
   cout<<dp[c]<<endl;
    return 0;
}
三、多重背包问题

每种物品有个数限制,是01背包的扩展

题目链接

0<=n,c<=100 0<=w,v,s<=100

通过输入可以判断复杂度 可用用三次方

暴力解法:

/*  01背包的扩展
做状态转移的时候多加一重循环即可

dp[j]=max(dp[j],dp[j-w[i]]+v[i] 选一个,dp[j-2*w[i]]+2*v[i]  选两个 ...);

关于初始化,还是和之前一样
1.f[i]=0
    ans=dp[c]
    
2.f[0]=0,f[i]=-inf
    ans=max{f[0....c]}
    
*/
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn=1e3+5;
int v[maxn],w[maxn],s[maxn];//二维动态规划
int dp[maxn];//当前使用重量为i的最大价值
int main()
{
    int n,c;
    cin>>n>>c;
    for(int i=1;i<=n;i++)
    cin>>w[i]>>v[i]>>s[i];
    
    for(int i=1;i<=n;i++)
    {
        for(int j=c;j>=w[i];j--)
        {
            for(int k=1;k<=s[i]&&k*w[i]<=j;k++)//注意这里k从1开始噢,如果是0就没啥意思了诶,直接dp[j]=dp[j] 注意它后面与的条件,进行一个优化
            dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i]);//虽然从0开始也能ac 唔...
        }
    }
    cout<<dp[c]<<endl;
    return 0;
}

数据范围扩大 0<=n<=1000,0<=c<=2000 w,v,s<=2000 题目链接

复杂度O(nc)

多重背包的二进制优化:

/*
    要把一个多重背包变成01背包,怎么做?  把s个物品拆开嘛,那不就是单个单个的了?-->01背包
    最多会有1000*2000=2*10^6个物品,复杂度很高噢2*10^6 * 2000 =4*10^9  也会超时啦
    所以我们要用二进制拆法
    eg:7   最少选多少个数,能够把<7的所有数表示出来,每个数可以选也可以不选
           1 1 1 1 1 1 1   (7个1)
           至少要三个数  因为:每个数两种选法  ,log2 (8)=3  所以下界一定是3
           1 2 4  (2^0  2^1  2^2)
    
    给定任意一个数s,问最少需要多少个数能够把<s的所有数表示出来 每个数有选和不选两种状态
    log2 (s) 上取整
    
    but s=10怎么办呢  不能1 2 4 8 会超过10的  (11 12 13 14 15都是不能选的)
     1 2 4 3(s-1-2-4)    -->每个数分成的是log2 (s)份
     
     --> c=1000*log(2000)个物品   复杂度 =1000*11 *2000 =2*10^7 刚刚好
     
*/
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <vector>
using namespace std;
const int maxn=2e3+5;
//int v[maxn],w[maxn],s[maxn];//二维动态规划
int dp[maxn];//当前使用重量为i的最大价值
struct node{
    int w,v;
};
int main()
{
    vector<node> goods;
    int n,c;
    cin>>n>>c;
    for(int i=1;i<=n;i++)
    {   
        int w,v,s;
        cin>>w>>v>>s;
        for(int k=1;k<=s;k*=2)
        {
            s-=k;//开始拆分咯
            goods.push_back({w*k,v*k});//k份k份看成一个整体 注意这样一整个整体放进去的写法
        }
        if(s>0)
        goods.push_back({w*s,v*s});
    }
    
    for(auto g:goods)//auto自动类型  遍历goods
        {
            for(int j=c;j>=g.w;j--)
            {
                dp[j]=max(dp[j],dp[j-g.w]+g.v);
            }
        }
        
    cout<<dp[c]<<endl;
    return 0;
}

数据范围扩大 0<=n<=1000,0<=c<=20000 w,v,s<=20000 题目链接

硬算O(nc)=4 * 10^11 必炸

用上面二进制优化的复杂度: 1000 * log2 (20000) * 20000=1000 * 15 * 20000=3 * 10^8 会炸

啊哈哈哈哈 楼天城的《男人八题》什么时候去写一下? 噢还有网络流14题

用单调队列优化:

for(int i=1;i<=n;i++)
    {
        for(int j=c;j>=w[i];j--)
        {
            for(int k=1;k<=s[i]&&k*w[i]<=j;k++)//观察它这个决策部分,看能不能用什么数据结构去优化掉
            dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i]);
        }
    }

/*
把枚举的质量归个类 以%w不同余进行归类,一共会有w类,两两之间相互独立
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值