#pragma GCC optimize (2)//淦怎么会有憨批不给开-o2
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define LL long long
/*——————————————————————————————————————————————————————————*/
#include //万能头文件
比赛时注意是否支持万能头!
#include
using namespace std;
#define LL long long
inline LL read()
{
LL x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9')
{
if(ch=='-')
w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return w*x;
}
//https://www.luogu.org/problem/P3367
//题目大意:给定n个数 查询m次 1合并 2查询 判断是否在同一集合内
#include
using namespace std;
#define LL long long
inline LL read()
{
LL x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9')
{
if(ch=='-')
w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return w*x;
}
int n,m,z,x,y,fa[10005];
int find(int xx)//寻找xx的根(路径压缩版)
{
int r=xx;
while(fa[r]!=r)//找到xx的根,用r记录
{
r=fa[r];
}
int now=xx,pr;//now代表当前节点 pr代表当前节点的上一级
while(now!=r)//将节点x以及x到根节点中间所有的点进行路径压缩
{
pr=fa[now];//记录当前节点的上一级
fa[now]=r;//路径压缩
now=pr;//将当前节点上跳为上一级,直到跳至根节点
}
return r;
}
void un(int xx,int yy)//合并两个节点
{
int tx=find(xx);
int ty=find(yy);
fa[tx]=ty;
}
bool ifin(int xx,int yy)//判断两个点是否在一个集合中
{
return find(xx)==find(yy);
}
int main()
{
n=read();
m=read();
for(register int i=1;i<=n;i++)
{
fa[i]=i;
}
for(register int i=1;i<=m;i++)
{
z=read();
x=read();
y=read();
if(z==1)
{
un(x,y);
}
else
{
if(ifin(x,y))
{
cout<<"Y"<<endl;
}
else
{
cout<<"N"<<endl;
}
}
}
return 0;
}
注意素数定义:质数(素数)是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数(即0,1均非素数)
//https://www.luogu.org/problem/P3383
//题目大意 给定1-n个数 m次查询 输出该数是否是素数
#include
using namespace std;
#define LL long long
#define MOD 10000000
LL read()
{
LL x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9')
{
if(ch=='-')
w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return w*x;
}
//质数(素数)是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数
int n,m,cnt,t,prime[10000005];//prime记录素数
bool v[10000005];//记录是否是合数
int main()
{
n=read();
m=read();
v[0]=1;//0不是素数
v[1]=1;//1不是素数
for(register int i=2;i<=n;i++)
{
if(!v[i])//如果i是素数 用prime记录,prime里面的素数严格递增
{
prime[++cnt]=i;
}
for(register int j=1;j<=cnt;j++)//遍历所有素数
{
if(prime[j]*i>n)
{
break ;
}
v[prime[j]*i]=1;
if(i%prime[j]==0)//如果该数(i)大于等于这个数的最小质因子,就跳出
//详见 https://www.cnblogs.com/jason2003/p/9761296.html 解释
{
break ;
}
}
}
for(register int i=1;i<=m;i++)
{
t=read();
if(v[t])
{
cout<<"No"<<endl;//被筛掉了——>不是素数
}
else
{
cout<<"Yes"<<endl;//是素数
}
}
return 0;
}
/*
算法思路:
对于每一个数(无论质数合数)x,筛掉所有小于x最小质因子的质数乘以x的数。比如对于77,它分解质因数是7*11,
那么筛掉所有小于7的质数*77,筛掉2*77、3*77、5*77。
好吧,是不是听起来太简单了。。。。没事,重点在证明。
算法证明:
首先我们要明确证明思路。如果要证明它是对的,只要保证两点:没用重复筛、没有漏筛
1、没有重复筛。
我们假设一个合数分解成p1*p2*p3,并且保证p1p1,所以这样是筛不到的。唯一会筛的是第一种:p1和 p2*p3。
2、没有漏筛。
还是假设把这个合数分解质因数a*b*c,保证a
//https://www.luogu.org/problem/P1226
#include
using namespace std;
#define LL long long
#define MOD 10000000
inline LL read()
{
LL x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9')
{
if(ch=='-')
w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return w*x;
}
LL b,p,k,s;
LL quickpow(LL x,LL y,LL m)//二分思想
{
x=x%m;//防止第一个x*x就爆掉longlong
LL ans=1;//记录答案
while(y)// 注意如果y=0的话不会进入循环
{
if(y&1)//判断y是否是单数
{
ans=ans*x;
ans=ans%m;
}
x=x*x;
x=x%m;
y=y>>1;
}
ans=ans%m;//防止出现特殊数据:1^0%1=0的情况
return ans;
}
int main()
{
b=read();
p=read();
k=read();
s=quickpow(b,p,k);
printf("%lld^%lld mod %lld=%lld\n",b,p,k,s);
return 0;
}
只有左矩阵的列数等于右矩阵的行数才可乘
(其实矩阵快速幂的难点在构造矩阵=.=)
矩阵乘法:行乘列(ans[i][j]=a[i][k]*b[k][j]的和)
//例题:求斐波那契数列
#include
using namespace std;
#define LL long long
#define MOD 1000000007
#define MAXN 3
LL read()
{
LL x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9')
{
if(ch=='-')
w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return w*x;
}
//只有左矩阵的列数等于右矩阵的行数才可乘
LL n;
struct Matrix
{
LL mat[MAXN][MAXN];
};
inline void init(Matrix &x)//注意此处的&
{
x.mat[1][1]=1;
x.mat[1][2]=1;
x.mat[2][1]=1;
x.mat[2][2]=0;
}
Matrix t;
Matrix cheng(Matrix a,Matrix b,LL nt,LL mod)//nt代表几阶矩阵
{
Matrix an;
for(register int i=1;i<=nt;i++)//初始化
{
for(register int j=1;j<=nt;j++)
{
an.mat[i][j]=0;
}
}
for(register int i=1;i<=nt;i++)
{
for(register int j=1;j<=nt;j++)
{
for(register int k=1;k<=nt;k++)
{
an.mat[i][j]+=a.mat[i][k]*b.mat[k][j];
an.mat[i][j]%=mod;
}
}
}
return an;
}
Matrix quickpow(Matrix a,LL b,LL nt,LL mod)//nt 代表几阶矩阵 b代表幂数
{
Matrix ans,res;
for(register int i=1;i<=nt;i++)
{
for(register int j=1;j<=nt;j++)
{
if(i==j)
{
ans.mat[i][j]=1;
}
else
{
ans.mat[i][j]=0;
}
res.mat[i][j]=0;
}
}
res=a;
while(b)
{
if(b&1)
{
ans=cheng(ans,res,2,MOD);
}
res=cheng(res,res,2,MOD);
b>>=1;
}
return ans;
}
int main()
{
n=read();
init(t);
cout<<quickpow(t,n-1,2,MOD).mat[1][1]<<endl;
//system("pause");
return 0;
}
/*
|x1 x2| × | y1 | = |x1*y1+x2*y2|
|x3 x4| | y2 | |x3*y1+x4*y2|
*/
prim算法适合稠密图(边多点少),kruskal算法适合稀疏图(边少点多)。
//https://www.luogu.org/problem/P3366
#include
using namespace std;
#define LL int
int read()
{
int x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9')
{
if(ch=='-')
w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return w*x;
}
int n,m,cnt,tt;
struct node{
int x,y,z;
};
node edge[200005];
void add(int a,int b,int w)
{
edge[++cnt].x=a;
edge[cnt].y=b;
edge[cnt].z=w;
}
bool cmp(node a,node b)
{
return a.z<b.z;
}
int fa[5005];
int found(int x)
{
int pre;
int r=x;
while(fa[r]!=r)
{
r=fa[r];
}
while(x!=r)
{
pre=fa[x];
fa[x]=r;
x=pre;
}
return x;
}
bool ifin(int a,int b)
{
return found(a)==found(b);
}
void un(int a,int b)
{
int aa=found(a);
int bb=found(b);
fa[aa]=bb;
}
int ans,aa,bb,cc;
int main()
{
n=read();
m=read();
for(register int i=1;i<=n;i++)//并查集初始化
{
fa[i]=i;
}
for(register int i=1;i<=m;i++)
{
aa=read();
bb=read();
cc=read();
add(aa,bb,cc);
}
sort(edge+1,edge+cnt+1,cmp);//按照边从小到大排序
for(register int i=1;i<=cnt;i++)
{
if(tt==n-1)//tt记录已经在树中的边的个数 如果等于n-1说明建树完毕
break ;
if(!ifin(edge[i].x,edge[i].y))//如果该边连接的两点不在同一集合中 则合并
{
ans+=edge[i].z;
un(edge[i].x,edge[i].y);
tt++;
}
}
if(tt<n-1)//判断是否成功建树
{
cout<<"orz"<<endl;
}
else
{
cout<<ans<<endl;
}
return 0;
}
//https://www.luogu.org/problem/P3366
#include
using namespace std;
#define LL long long
#define MAXN 0x3f3f3f3f
#define INF 200007
LL read()
{
LL x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9')
{
if(ch=='-')
w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return w*x;
}
int xi,yi,zi,cnt,n,m,dis[5001],mp[5001][5001];
bool v[5001];
struct nn
{
int x,d;
bool operator <(const nn& b)const
{
return d>b.d;
}
};
priority_queue < nn > q;//优先队列优化
inline int prim(int s)
{
int ans=0;
v[s]=1;
nn now;
now.d=0;
now.x=s;
nn nw;
for(register int ct=1;ct<=n-1;ct++)//找n-1个点
{
for(register int i=1;i<=n;i++)
{
nw=now;//憨批操作=.=
if(mp[now.x][i]&&!v[i])//如果点i没有加入到树中 则将该边入队
{
now.d=mp[now.x][i];
now.x=i;
q.push(now);
}
now=nw;
}
while(!q.empty()&&v[q.top().x])//如果该点已经入树了 就跳过
{
q.pop();
}
if(q.empty())
{
break ;
}
now=q.top();
q.pop();
ans+=now.d;//记录树的大小
v[now.x]=1;//标记该点已经在树中
}
return ans;
}
int main()
{
n=read();
m=read();
for(register int i=1;i<=5000;i++)//初始化
{
for(register int j=1;j<=5000;j++)
{
if(i==j)
mp[i][j]=0;
mp[i][j]=MAXN;
}
}
for(register int i=1;i<=m;i++)
{
xi=read();
yi=read();
zi=read();
mp[xi][yi]=min(mp[xi][yi],zi);//防止重边
mp[yi][xi]=min(mp[yi][xi],zi);//防止重边
}
cout<<prim(1)<<endl;//输出答案
//system("pause");
return 0;
}
注意该无法处理含有负数的情况
#include
using namespace std;
#define LL long long
LL read()
{
LL x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9')
{
if(ch=='-')
w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return w*x;
}
LL a,b,X,Y;
LL gcd(LL x,LL y)//辗转相除法
{
LL z;
while(y!=0)
{
z=x;
x=y;
y=z%y;
}
return x;
}
LL exgcd(LL a,LL b,LL &x,LL &y)//用来解a*x+b*y=gcd(a,b)的情况
{
if(b==0)
{
x=1;
y=0;
return a;
}
LL r=exgcd(b,a%b,x,y);
LL t=x;
x=y;
y=t-a/b*y;
return r;
}
LL lcm(LL a,LL b)//最小公倍数数=a*b/最大公因数
{
LL ans=a/gcd(a,b);//先计算除法防止爆掉 long long
return ans*b;
}
int main()
{
while(cin>>a>>b)
{
cout<<"LCM:"<<lcm(a,b)<<endl;
cout<<"GCD:"<<gcd(a,b)<<endl;
LL tt=exgcd(a,b,X,Y);
cout<<"exGCD:"<<a<<"*"<<X<<'+'<<b<<'*'<<Y<<'='<<tt<<endl;
}
return 0;
}
//https://www.luogu.org/problem/P4779
#include
#define LL long long
#define INF 0x3f3f3f3f
using namespace std;
LL read()
{
LL x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9')
{
if(ch=='-')
{
w=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return w*x;
}
int n,m,s;
int ui,vi,wi;
int cnt,head[100005],d[100005];
struct node{
int to,next,w;
};
node edge[200005];
void add(int x,int y,int w)
{
edge[++cnt].to=y;
edge[cnt].next=head[x];
edge[cnt].w=w;
head[x]=cnt;
}
struct nn{
int x,dis;
bool operator <(const nn& b)const//重载运算符'<'
{
return dis>b.dis;//顺序按照dis从小到大排(此处与sort的cmp恰好相反)
}
};//记录入队的节点和距离
priority_queue < nn > q;
inline nn init(int xx,int dd)
{
nn t;
t.x=xx;
t.dis=dd;
return t;
}
nn now;
void dij(int s)
{
for(register int i=1;i<=n;i++)//初始化
{
d[i]=INF;
}
d[s]=0;//d[x]代表点s到 x的距离
now.x=s;
now.dis=0;
q.push(now);
while(!q.empty())
{
now=q.top();
q.pop();
if(now.dis>d[now.x])//优化 如果该边权大于到点now.x的距离 说明该边比已有点更差就没有必要去遍历该点了
continue ;
for(register int i=head[now.x];i!=0;i=edge[i].next)//遍历当前now点所连接的所有边
{
if(d[edge[i].to]>d[now.x]+edge[i].w)//如果该边比已有点更优 则对该边所连的点进行松弛,并将该点入列
{
d[edge[i].to]=d[now.x]+edge[i].w;
q.push(init(edge[i].to,d[edge[i].to]));
}
}
}
}
int main()
{
n=read();
m=read();
s=read();
for(register int i=1;i<=m;i++)
{
ui=read();
vi=read();
wi=read();
add(ui,vi,wi);
}
dij(1);
for(register int i=1;i<=n;i++)
{
cout<<d[i]<<' ';
}
return 0;
}
难点在多种操作(例如区间加与区间乘与区间覆盖等操作同时存在时down函数的处理顺序上以及对不同lazy下标优先级与操作顺序上是否进行清零等操作)
看起来好乱
#include
using namespace std;
#define LL long long
#define MAXN 0x3f3f3f3f
#define INF 500007
LL read()
{
LL x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9')
{
if(ch=='-')
w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return w*x;
}
struct node
{
LL l,r,lazy,w;
};
node tree[INF*4];
LL n,q,f,xx,yy,ans,d;
void update(LL k)//更新k点的值
{
tree[k].w=tree[k<<1].w+tree[k<<1|1].w;
}
void build(int x,int y,int k)//建树
{
tree[k].l=x;
tree[k].r=y;
if(x==y)
{
tree[k].w=read();
return ;
}
LL mid=(x+y)>>1;
build(x,mid,k<<1);
build(mid+1,y,k<<1|1);
update(k);
}
void down(LL k)//下推lazy标记(此时该节点已经更新完成,该操作为更新两个子节点的值)
{
tree[k<<1].lazy+=tree[k].lazy;
tree[k<<1|1].lazy+=tree[k].lazy;
tree[k<<1].w+=tree[k].lazy*(tree[k<<1].r-tree[k<<1].l+1);
tree[k<<1|1].w+=tree[k].lazy*(tree[k<<1|1].r-tree[k<<1|1].l+1);
tree[k].lazy=0;
}
void add(LL x,LL y,LL num,LL k)//区间增加 xy为查询区间 该节点区间直接用tree[k].l tree[k].r表示
{
if(tree[k].l>=x&&tree[k].r<=y)
{
tree[k].w+=num*(tree[k].r-tree[k].l+1);
tree[k].lazy+=num;
return ;
}
down(k);//如果要继续二分,则要更新它的子节点,即进行下推lazy的操作
LL mid=(tree[k].l+tree[k].r)>>1;
if(x<=mid)
{
add(x,y,num,k<<1);
}
if(y>mid)
{
add(x,y,num,k<<1|1);
}
update(k);//记得最后更新当前节点
}
void oneadd(LL nx,LL num,LL k)//单点增加
{
if(tree[k].l==tree[k].r&&tree[k].l==nx)
{
tree[k].w+=num;
return ;
}
LL mid=(tree[k].l+tree[k].r)>>1;
if(nx<=mid)
oneadd(nx,num,k<<1);
else
oneadd(nx,num,k<<1|1);
update(k);
}
LL getsum(LL x,LL y,LL k)//求和
{
LL res=0;
if(tree[k].l>=x&&tree[k].r<=y)
{
res=tree[k].w;
return res;
}
down(k);
LL mid=(tree[k].l+tree[k].r)>>1;
if(x<=mid)
res+=getsum(x,y,k<<1);
if(y>mid)
res+=getsum(x,y,k<<1|1);
return res;
}
int main()
{
n=read();
build(1,n,1);
q=read();
while(q--)
{
f=read();
xx=read();
yy=read();
if(f==1)
{
d=read();
add(xx,yy,d,1);
}
else
{
ans=getsum(xx,yy,1);
printf("%lld\n",ans);
}
}
//system("pause");
return 0;
}
#include
using namespace std;
#define LL long long
LL read()
{
LL x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9')
{
if(ch=='-')
w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return w*x;
}
LL u,v,n,m,e,ans,mp[2000][2000],used[2000],b[2000];
bool found(int x)
{
for(register int i=1;i<=m;i++)
{
if(mp[x][i]&&!used[i])//x到i有边并且i没有被尝试改变归属且失败
{
used[i]=1;
if(!b[i]||found(b[i]))//如果i没有归属或者成功地改变了i原本归属的归属
{
b[i]=x;//令i的归属为x
return 1;
}
}
}
return 0;
}
int main()
{
n=read();//集合1的个数
m=read();//集合2的个数
e=read();//边的条数
for(register int i=1;i<=e;i++)
{
u=read();
v=read();
if(u>n)//原题数据有锅 无视就好
continue ;
if(v>m)
continue ;
mp[u][v]=1;//从u到v有边
}
for(register int i=1;i<=n;i++)
{
memset(used,0,sizeof(used));
if(found(i))
ans++;//记录匹配数量
}
cout<<ans<<endl;
//system("pause");
return 0;
}
// 判断一个图是否为二分图:将一个点标记为1 将与该点相连的点标记为2
// 将与标为2的点相连的点标记为1 如果有冲突 则不是二分图
//初始化dp为inf=0x3f3f3f
memset(dp,inf,sizeof(dp));
for (int i=0;i<len;i++)
{
//获取位置
int pos=lower_bound(dp,dp+len,s[i])-dp;
//更新位置
dp[pos]=s[i];
ans=max(ans,pos);
}
//答案为ans+1
cout << ans+1 << endl;
//len1和len2为两字符串长度
for (int i=1;i<=len1;i++)
for (int j=1;j<=len2;j++)
//相等时直接更新
if (s1[i-1]==s2[j-1])
dp[i][j]=dp[i-1][j-1]+1;
//不相等时继承之前的最大值
else
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
//答案为dp[len1][len2]
cout << dp[len1][len2] << endl;
//初始化DP数组
for (int i=1;i<=n;i++)
dp[i][i]=INF(0 or 其他)
//枚举区间长度
for (int len=1;len<n;len++)
//枚举起点
for (int i=1;i+len<=n;i++)
{
//区间终点
int j=i+len;
//枚举分割点
for (int k=i;k<j;k++)
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]);
}
//区间1-n的结果
cout << dp[1][n] << endl;
/*
|大数模拟加法|
|用string模拟|
|16/11/05ztx, thanks to caojiji|
*/
string add1(string s1, string s2)
{
if (s1 == "" && s2 == "") return "0";
if (s1 == "") return s2;
if (s2 == "") return s1;
string maxx = s1, minn = s2;
if (s1.length() < s2.length()){
maxx = s2;
minn = s1;
}
int a = maxx.length() - 1, b = minn.length() - 1;
for (int i = b; i >= 0; --i){
maxx[a--] += minn[i] - '0'; // a一直在减 , 额外还要减个'0'
}
for (int i = maxx.length()-1; i > 0;--i){
if (maxx[i] > '9'){
maxx[i] -= 10;//注意这个是减10
maxx[i - 1]++;
}
}
if (maxx[0] > '9'){
maxx[0] -= 10;
maxx = '1' + maxx;
}
return maxx;
}
/*
|大数模拟阶乘|
|用数组模拟|
|16/12/02ztx|
*/
#include
#include
using namespace std;
typedef long long LL;
const int maxn = 100010;
int num[maxn], len;
/*
在mult函数中,形参部分:len每次调用函数都会发生改变,n表示每次要乘以的数,最终返回的是结果的长度
tip: 阶乘都是先求之前的(n-1)!来求n!
初始化Init函数很重要,不要落下
*/
void Init() {
len = 1;
num[0] = 1;
}
int mult(int num[], int len, int n) {
LL tmp = 0;
for(LL i = 0; i < len; ++i) {
tmp = tmp + num[i] * n; //从最低位开始,等号左边的tmp表示当前位,右边的tmp表示进位(之前进的位)
num[i] = tmp % 10; // 保存在对应的数组位置,即去掉进位后的一位数
tmp = tmp / 10; // 取整用于再次循环,与n和下一个位置的乘积相加
}
while(tmp) { // 之后的进位处理
num[len++] = tmp % 10;
tmp = tmp / 10;
}
return len;
}
int main() {
Init();
int n;
n = 1977; // 求的阶乘数
for(int i = 2; i <= n; ++i) {
len = mult(num, len, i);
}
for(int i = len - 1; i >= 0; --i)
printf("%d",num[i]); // 从最高位依次输出,数据比较多采用printf输出
printf("\n");
return 0;
}
1.1 题目
有N件物品和一个容量为V的背包。第i件物品的费用是w[i],价值是v[i],求将哪些物品装入背包可使价值总和最大。
1.2 特点
每种物品仅有一件,可以选择放与不放。
1.3 基本的状态转移方程
f[i][j] = max(f[i − 1][j], f[i − 1][j − w[i]] + v[i])
1.4 基本模板
for(int i = 0; i < h; i++)
{
for(int j = c; j >= v[i]; j–)
{
dp[j] = max(dp[j], dp[j - v[i]] + v[i]);
}
}
1.5沾题
1.P2925 [USACO08DEC]干草出售Hay For Sale
简单01背包有一点优化:背包装满后就没必要再继续循环了。
#include
using namespace std;
const int maxn = 50010;
int c, h, v[maxn], dp[maxn];
int main()
{
scanf("%d%d", &c, &h);
for(int i = 0; i < h; i++) scanf("%d", &v[i]);
for(int i = 0; i < h; i++)
{
for(int j = c; j >= v[i]; j--)
{
dp[j] = max(dp[j], dp[j - v[i]] + v[i]);
if(dp[j] == j) continue;
}
if(dp[c] == c) // 优化装满后退出
{
break;
}
}
printf("%d\n", dp[c]);
return 0;
}
1.6 常数优化
for(int i = 0; i < h; i++)
{
int sum = 0;
for(int k = i; k < h; k++) sum += v[i];
int maxx = max(c - sum, v[i]);
for(int j = c; j >= maxx; j–)
{
dp[j] = max(dp[j], dp[j - v[i]] + v[i]);
}
if(dp[c] == c)
{
break;
}
}
1.7初始化细节
(1)恰好装满背包,初始化dp[0] = 0,其余F[1 … V] 均设为-INF
(2)未要求将背包装满,只是价格尽量大 F[0 … V] 均设为0
2.完全背包
2.1 题目
有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
2.2 基本思路
这个问题非常类似于01背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0件、取1件、取2件……等很多种。如果仍然按照解01背包时的思路,令f[i][j]表示前i种物品恰放入一个容量为V的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程,像这样:
f[i][j] = max(f[i − 1][j − k ∗ w[i]] + k ∗ v[i]) ∣ 0 <= k ∗ w[i] <= V
二维状态转移方程
f[i][j] = max(f[i − 1][j], f[i][j − w[i]] + v[i])
2.3 模板
for(int i = 0; i < n; i++)
for(int j = w[i]; j <= W; j++)
{
dp[j] = min(dp[j], dp[j - w[i]] + v[i]);
}
2.4 沾个题
Piggy-Bank
#include
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 10010;
int t, W, w[maxn], v[maxn], dp[maxn], n;
int main()
{
scanf("%d", &t);
while(t–)
{
int w1, w2;
scanf("%d%d", &w1, &w2);
fill(dp, dp + maxn, INF);
W = w2 - w1;
scanf("%d", &n);
for(int i = 0; i < n; i++)
{
scanf("%d%d", &v[i], &w[i]);
}
dp[0] = 0;
for(int i = 0; i < n; i++)
for(int j = w[i]; j <= W; j++)
{
dp[j] = min(dp[j], dp[j - w[i]] + v[i]);
}
if(dp[W] == INF)
{
printf(“This is impossible.\n”);
}
else
{
printf(“The minimum amount of money in the piggy-bank is %d.\n”, dp[W]);
}
}
return 0;
}
3 多重背包
3.1题目
有N种物品和一个容量为V的背包。第i种物品最多有p[i]件可用,每件费用是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
3.2 复杂度的多重背包问题
void Zero(int w, int p)
{
for(int j = W; j >= w; j–)
{
dp[j] = max(dp[j], dp[j - w] + p);
}
}
void Complete(int w, int p)
{
for(int j = w; j <= W; j++)
{
dp[j] = max(dp[j], dp[j - w] + p);
}
}
void Multiple(int c, int w, int p)
{
if(c * w >= W)
{
Complete(w, p);
return;
}
int k = 1;
while(k < c)
{
Zero(k * w, k * p);
c = c - k;
k = 2 * k;
}
Zero(c * w, c * p);
}
3.3可行性问题O(VN)的算法
当问题是每种有若干的物品能否他Inman给定容积的背包,只需考虑装满背包的可行性O(VN)复杂度。
基本思想:设F[i, j]表示用了前i种物品填满容量为j的背包后最多还剩几个第i种物品可用。若F[i, j] = -1表示这种状态不太可行,若可行则满足F[i , j] >=0 && F[i, j] <= Mi
memset(dp, -1, sizeof(dp));
dp[0][0] = 0;
for(int i = 1; i <= n; i++)
{
for(int j = 0; j <= V; j++)
{
if(dp[i - 1][j] >= 0) dp[i][j] = M[i];
else dp[i][j]= -1;
}
for(int j = 0; j <= V - C[i]; j++)
{
if(dp[i][j] > 0)
dp[i][j + C[i]] = max(dp[i][j + C[i], dp[i][j] - 1);
}
}
4 混合多种背包
4.1问题
如果将前面三个背包混合起来,也就是说,有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包),应该怎么求解呢?
4.2 01背包与完全背包的混合
考虑到在01背包和完全背包中给出的伪代码只有一处不同,故如果只有两类物品:一类物品只能取一次,另一类物品可以取无限次,那么只需在对每个物品应用转移方程时,根据物品的类别选用顺序或逆序的循环即可,复杂度是O(VN) O(VN)O(VN)。
4.3再加上多重背包
如果再加上有的物品最多可以取有限次,那么原则上也可以给出O(VN) O(VN)O(VN)的解法:遇到多重背包类型的物品用单调队列解即可。但如果不考虑超过NOIP NOIPNOIP范围的算法的话,用多重背包中将每个这类物品分成O(log(p[i])) O(log(p[i]))O(log(p[i]))个01背包的物品的方法也已经很优了
4.4赋个多校题
AreYouBusy
题意:他由很多工作0 ,至少取一个,1 最多取一个, 2随意取,让你求在时间之内,求最大的开心值。
1.第一类,至少选一项,即必须要选,那么在开始时,对于这一组的dp的初值,应该全部赋为负无穷,这样才能保证不会出现都不选的情况。
dp[i][j]=max(dp[i][j],max(dp[i][j-w[x]]+p[x],dp[i-1][j-w[x]]+p[x]));
2.第二类,最多选一项,即要么不选,一旦选,只能是第一次选。
dp[i][j]=max(dp[i][j],dp[i-1][j-w[x]]+p[x]);
3.第三类,任意选,即不论选不选,选几个都可以。
dp[i][j]=max(dp[i][j],dp[i][j-w[x]]+p[x]);
代码
#include
using namespace std;
const int maxn = 110;
const int INF = 0x3f3f3f3f;
int n, t, x, y, v[maxn], w[maxn], dp[maxn][maxn];
int main()
{
while(scanf("%d%d", &n, &t) != EOF)
{
memset(dp, 0, sizeof(dp));
for(int i = 1; i <= n; i++)
{
scanf("%d%d", &x, &y);
for(int k = 1; k <= x; k++) scanf("%d%d", &v[k], &w[k]);
if(y == 0) // 至少取一个
{
for(int j = 0; j <= t; j++) dp[i][j] = -INF;
for(int k = 1; k <= x; k++)
{
for(int j = t; j >= v[k]; j–)
{
dp[i][j] = max(dp[i][j], dp[i][j - v[k]] + w[k]);
dp[i][j] = max(dp[i][j], dp[i - 1][j - v[k]] + w[k]);
}
}
}
else if(y == 1) // 至多取一个
{
for(int j = 0; j <= t; j++) dp[i][j] = dp[i - 1][j];
for(int k = 1; k <= x; k++)
{
for(int j = t; j >= v[k]; j–)
dp[i][j] = max(dp[i][j], dp[i - 1][j - v[k]] + w[k]);
}
}
else if(y == 2) // 至多取一个
{
for(int j = 0; j <= t; j++) dp[i][j] = dp[i - 1][j];
for(int k = 1; k <= x; k++)
{
for(int j = t; j >= v[k]; j–)
dp[i][j] = max(dp[i][j], dp[i][j - v[k]] + w[k]);
}
}
}
int ans = -1;
ans = max(ans, dp[n][t]);
printf("%d\n", ans);
}
return 0;
}
5 二维费用背包
5.1题目
二维费用的背包问题是指:对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。设这两种代价分别为代价1和代价2,第i件物品所需的两种代价分别为w[i]和g[i]。两种代价可付出的最大值(两种背包容量)分别为V和T。物品的价值为v[i]。
5.2模板
for(int i = 0; i < n; i++)
{
for(int j = v; j >= a[i]; j--)
{
for(int k = m; k >= b[i]; k--)
{
dp[j][k] = max(dp[j][k], dp[j - a[i]][k - b[i]] + c[i]);
}
}
}
5.3沾题
1.P1507 NASA的食物计划
#include
using namespace std;
const int maxn = 410;
int v, m, n, a[maxn], b[maxn], c[maxn], dp[maxn][maxn];
int main()
{
scanf("%d%d", &v, &m);
scanf("%d", &n);
for(int i = 0; i < n; i++) scanf("%d%d%d", &a[i], &b[i], &c[i]);
for(int i = 0; i < n; i++)
{
for(int j = v; j >= a[i]; j–)
{
for(int k = m; k >= b[i]; k–)
{
dp[j][k] = max(dp[j][k], dp[j - a[i]][k - b[i]] + c[i]);
}
}
}
printf("%d\n", dp[v][m]);
return 0;
}
2.FATE
题意:打游戏有忍耐值,需要在忍耐值之内刷够经验,最多杀n只怪。
#include
using namespace std;
const int maxn = 110;
int n, m, k, s, a[maxn], b[maxn], dp[maxn][maxn];
int main()
{
while(scanf("%d%d%d%d", &n, &m, &k, &s) != EOF)
{
memset(dp, 0, sizeof(dp));
for(int i = 0; i < k; i++) scanf("%d%d", &a[i], &b[i]);
for(int i = 0; i < k; i++)
{
for(int j = b[i]; j <= m; j++)
{
for(int x = s; x >= 1; x–)
{
dp[j][x] = max(dp[j][x], dp[j - b[i]][x - 1] + a[i]);
}
}
}
int ans = 0x3f3f3f3f;
for(int i = 1; i <= m; i++)
{
for(int j = 1; j <= s; j++)
{
if(dp[i][s] >= n)
{
ans = m - i;
break;
}
}
if(ans != 0x3f3f3f3f) break;
}
if(ans == 0x3f3f3f3f) printf("-1\n");
else printf("%d\n", ans);
}
return 0;
}
6 分组背包
有N件物品和一个容量为V的背包。第i件物品的费用是w[i],价值是v[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
f[k][j]=max(f[k−1][j],f[k−1][j−c[i]]+w[i]∣物品i属于组k)
模板:
for(int i = 1; i <= n; i++)
{
for(int j = m; j >= 1; j–)
{
for(int k = 1; k <= j; k++)
{
dp[j] = max(dp[j], dp[j - k] + a[i][k]);
}
}
/*
|kmp算法|
|字符串匹配|
|17/1/21ztx|
*/
void getnext(char str[maxn], int nextt[maxn]) {
int j = 0, k = -1;
nextt[0] = -1;
while (j < m) {
if (k == -1 || str[j] == str[k]) {
j++;
k++;
nextt[j] = k;
}
else
k = nextt[k];
}
}
void kmp(int a[maxn], int b[maxn]) {
int nextt[maxm];
int i = 0, j = 0;
getnext(b, nextt);
while (i < n) {
if (j == -1 || a[i] == b[j]) { // 母串不动,子串移动
j++;
i++;
}
else {
// i不需要回溯了
// i = i - j + 1;
j = nextt[j];
}
if (j == m) {
printf("%d\n", i - m + 1); // 母串的位置减去子串的长度+1
return;
}
}
printf("-1\n");
}
/*
|16/11/06ztx|
*/
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 50005;
int a[maxn];
int n;
int lowbit(const int t) {
return t & (-t);
}
void insert(int t, int d) {
while (t <= n){
a[t] += d;
t = t + lowbit(t);
}
}
ll getSum(int t) {
ll sum = 0;
while (t > 0){
sum += a[t];
t = t - lowbit(t);
}
return sum;
}
int main() {
int t, k, d;
scanf("%d", &t);
k= 1;
while (t--){
memset(a, 0, sizeof(a));
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d", &d);
insert(i, d);
}
string str;
printf("Case %d:\n", k++);
while (cin >> str) {
if (str == "End") break;
int x, y;
scanf("%d %d", &x, &y);
if (str == "Query")
printf("%lld\n", getSum(y) - getSum(x - 1));
else if (str == "Add")
insert(x, y);
else if (str == "Sub")
insert(x, -y);
}
}
return 0;
}