2025/1/27课堂记录
目录
- 旅行计划
- Banknotes
这次优化了一下代码
c:3 1 5 0 5
d:1 2 2 1 4
s1:2 -1 3 -1 1 2 -1 3 -1 1
s2:2 1 4 3 4 6 5 8 7 8
s2[i]=s1[i]+s2[i-1];
s2[1]=c1-d1
s2[2]=c2-d2+c1-d1
s2[3]=c3-d3+c2-d2+c1-d1
s2[3]-s2[1]=c3-d3+c2-d2+c1-d1-c1+d1=c3-d3+c2-d2,即从2走到4(4不加油)后还剩多少油
然后就看注释吧,精华都在注释里
#include <iostream>
using namespace std;
long long o[1000200],d[1000200],s[1000200],q[1000200],vis[1000200];
int main()
{
int n;
cin>>n;
for(long long i=1;i<=n;i++)cin>>o[i]>>d[i];
for(long long i=1;i<=n;i++)s[i+n]=s[i]=o[i]-d[i];
for(long long i=1;i<=2*n;i++)s[i]+=s[i-1];
long long hh=0,tt=0;
q[0]=2*n+1,tt=1;//补“0 ”
for(long long i=2*n;i>=0;i--)
{
while(hh<tt&&q[hh]>i+n)hh++;//i是起点,i+n是q[hh]<=i+n是一圈之内;
if(i<n)//i作为出发点
if(s[q[hh]]-s[i]>=0)
vis[i+1]=1;
//因为在循环执行到i>=n这个期间,维护单调递增,s[q[hh]]永远最小,即走到这个点剩余油量最小,那么s[q[hh]]-s[i]永远最小
//即从i+1走到q[hh],剩余油量最小。如果这个都>=0,那在i+1走到i+n+1这一圈以内就不会出现负数了,即i+1可以作为起点
while (hh<tt&&s[q[tt-1]]>=s[i]) tt--;//维护s[q[hh]]永远最小
q[tt++]=i;
}
d[0]=d[n];
for(long long i=1;i<=n;i++)s[i+n]=s[i]=o[i]-d[i-1];
for(long long i=1;i<=2*n;i++)s[i]+=s[i-1];
hh=0,tt=0;
q[0]=0,tt=1;//补0
for(long long i=1;i<=2*n;i++)
{
while(hh<tt&&q[hh]<i-n)hh++;
if(i>n)
if(s[i]-s[q[hh]]>=0)
vis[i-n]=1;
while(hh<tt&&s[q[tt-1]]<=s[i])tt--;//单调递减
q[tt++]=i;
}
for(long long i=1;i<=n;i++)
{
if(vis[i])cout<<"TAK\n";
else cout<<"NIE\n";
}
return 0;
}
这是一道多重背包题,即每个物品有有限个(01:一个,完全:无限个)
当然,你可以直接套用多重背包模板,如:
只使用模板
#include<bits/stdc++.h>
using namespace std;
int b[105],c[105],f[105][105];//f[i][j]:只使用前i种货币,凑够面值为j的钱,总共要用几枚硬币
int main()
{
int m,n;
cin>>n;
for(int i=1;i<=n;i++)cin>>b[i];
for(int i=1;i<=n;i++)cin>>c[i];
cin>>m;
memset(f,0x3f,sizeof(f));
f[0][0]=0;
for(int i=1;i<=n;i++)//枚举每种硬币的到来
for(int j=0;j<=m;j++)//枚举每种面值
for(int k=0;k<=c[i];k++)//枚举这种硬币所用的枚数
if(j-k*b[i]>=0)//不越界
f[i][j]=min(f[i][j],f[i-1][j-k*b[i]]+k);//把之前所用的i-1种硬币
cout<<f[n][m];/
return 0;
}
然后,你会发现他的时间复杂度确实太高了一点,3for,数据大了就见祖宗
有什么办法降低时间复杂度呢?
你会想到:01背包和完全背包只用2for,能不能把多重背包变成他俩呢?
把有限个变成无限个,显然太离谱
所以可以变成01背包
于是,便有了下面这种方法:把好几枚硬币摞在一块,体重,单价,数量都翻倍
这个是这种方法的模板,拆完再跑
#include<bits/stdc++.h>
using namespace std;
int w[59],c[59],v[59],f[59][59];
int main()
{
int n,we,co,va,s=0,V;
cin>>V>>n;
for(int i=1;i<=n;i++)
{
cin>>we>>va>>co;
// 体重 单价 数量
// 把一种物体的数量二进制分解成若干种"小物体"
for(int j=1;j<=co;j<<=1) // j=j*2; j=j<<1;
{
w[++s]=j*we; // s统计小物体的个数
v[s]=j*va;
co-=j;
}
if(co>0)
{
w[++s]=co*we;
v[s]=co*va;
}
}
// 做01背包
for(int i=1;i<=s;i++) //新的种类
for(int j=1;j<=V;j++) // 总体积
if(j>=w[i])
f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]);
else
f[i][j]=f[i-1][j];
cout<<f[s][V];
return 0;
}
这个是这道题的解法,但是是边拆边跑
//https://www.cnblogs.com/GXZlegend/p/7434553.html
//Bank Notes 多次背包实现
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int b[210],c[210],f[20010],m,n;
void dp(int w,int c)
{
for(int i=m;i>=w;i--)
f[i]=min(f[i],f[i-w]+c);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&b[i]);
for(int i=1;i<=n;i++)
scanf("%d",&c[i]);
scanf("%d",&m);
memset(f,0x3f,sizeof(f)),f[0]=0;
for(int i=1;i<=n;i++)
{
//将c[i]拆成2^0,2^1,2^2.......2^k,(c[i]-s)
for(int j=1;j<=c[i];j<<=1)
dp(b[i]*j,j),c[i]-=j;
if(c[i])dp(b[i]*c[i],c[i]);
}
printf("%d\n",f[m]);
return 0;
}
还有一种方法——既然是dp,那么肯定有专属的优化方法,而多重背包也可以用单调队列优化!!
具体的话,还是看一看这篇博客吧,其实我也没大弄明白
我这个是错误代码,没改过来,这坑以后补吧
#include<iostream>
#include<cstring>
using namespace std;
int b[1000],c[1000],w[20000],v[20000],f[20000][20000];
int main()
{
int n,k,m=0;
cin>>n;
for(int i=1;i<=n;i++)cin>>b[i];
for(int i=1;i<=n;i++)cin>>c[i];
cin>>k;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=c[i];j+=j)
{
w[++m]=j;//枚数
v[m]=j*b[i];//面值
c[i]-=j;
}
if(c[i]>0)
{
w[++m]=c[i];
v[m]=c[i]*b[i];
}
}
for(int i=1;i<=m;i++)cout<<w[i]<<" "<<v[i]<<"\n";
for(int i=1;i<=m;i++)f[1][i]=0x3f3f3f3f;
for(int i=1;i<=m;i++)//种类
for(int j=1;j<=k;j++)//面值
if(j>=k*v[i])
f[i][j]=min(f[i-1][j],f[i-1][j-v[i]]+w[i]);
else
f[i][j]=f[i-1][j];
for(int i=1;i<=m;i++)
{
for(int j=1;j<=k;j++)
cout<<f[i][j];
cout<<"\n";
}
cout<<f[m][k];
return 0;
}
我这坑是越来越多了
本文来自博客园,作者:永韶,转载请注明原文链接:https://www.cnblogs.com/yongshao/p/18692840