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!(n−m)!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类,两两之间相互独立
*/