一个大Za
图论
最短路
对比
Floyd | Bellman-Ford | Dijkstra |
---|---|---|
每对结点之间的最短路 | 单源最短路 | 单源最短路 |
无负环的图 | 任意图 | 非负权图 |
\(O(N^3)\) | \(O ( NM )\) | \(O((N+M)log\ M)\) |
dijkstra
priority_queue<pii>q;
void dijkstra(int S){
memset(dis,inf,sizeof(dis)),memset(vis,0,sizeof(vis));
q.push(make_pair(dis[S]=0,S));
while(!q.empty()){
int u=q.top().second;q.pop();
if(vis[u]) continue;vis[u]=1;
for(int i=head[u],v;i;i=e[i].nxt)
if(dis[v=e[i].v]>dis[u]+e[i].w) q.push(make_pair(-(dis[v]=dis[u]+e[i].w),v));
}
}
struct重载:
struct node{
int dis,pos;
node():dis(0),pos(0){}//无参数初始化
node(int a,int b):dis(a),pos(b){}//带参初始化
bool operator <(const node &x)const{return x.dis<dis;}//从小到大
}
spfa判负环
queue<int>q;
int dis[N],len[N];bool vis[N];
bool spfa(int s){
for(int i=1;i<=n;++i) dis[i]=inf,vis[i]=0,len[i]=-1;
while(!q.empty()) q.pop();
dis[s]=len[s]=0,q.push(s),vis[s]=1;
while(!q.empty()){
int u=q.front();q.pop(),vis[u]=0;
if(len[u]>=n) return 1;
for(int i=head[u],v;i;i=e[i].nxt)
if(dis[v=e[i].v]>dis[u]+e[i].w)
dis[v]=dis[u]+e[i].w,len[v]=len[u]+1,(!vis[v])?(vis[v]=1,q.push(v),1):1;
}
return 0;
}
floyd
\(f[i][j]\):从\(i\)号顶点到\(j\)号顶点只经过前\(k\)号点的最短路程
\(k\)是阶段 所以必须位于最外层 而\(i和j\)为附加状态
for (k=1;k<=n;k++)
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
f[i][j] = min(f[i][j],f[i][k]+f[k][j]);
给一个正权无向图,找一个最小权值和的环
这一定是一个简单环 考虑环上编号最大的结点\(u\)
\(f[u-1][x][y]\)和\((u,x), (u,y)\)共同构成了环。
在 Floyd 的过程中枚举\(u\),计算这个和的最小值即可
有向图的最小环问题 可枚举起点\(s=1\sim n\) 执行对优化的\(Dijsktra\)求解单源最短路径\(s\)一定为第一个被从堆中取出节点 扫描\(s\)所有出边 扩展、更新完成后 令\(d[s]=+\infty\) 然后继续求解 当s第二次被从堆中取出时 \(d[s]\)就是经过点\(s\)的最小环长度
POJ1734 sightseeing trip
找最小环 并输出一个最小环方案
int n,m,mp[N][N],dis[N][N],pos[N][N];
vector<int>path;
void get_path(int x,int y){
if(!pos[x][y]) return;
get_path(x,pos[x][y]);
path.push_back(pos[x][y]);
get_path(pos[x][y],y);
}
int main(){
scanf("%d%d",&n,&m);
int ans=inf;
memset(mp,inf,sizeof(mp));
for(int i=1;i<=n;++i) mp[i][i]=0;
for(int i=1,x,y,w;i<=m;++i)
scanf("%d%d%d",&x,&y,&w),mp[x][y]=mp[y][x]=w;
memcpy(dis,mp,sizeof(mp));
for(int k=1;k<=n;++k){
for(int i=1;i<k;++i)
for(int j=i+1;j<k;++j)
if((long long)dis[i][j]+mp[i][k]+mp[k][j]<ans){
ans=dis[i][j]+mp[i][k]+mp[k][j];
path.clear(),path.push_back(i);
get_path(i,j);path.push_back(j),path.push_back(k);
}
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
if(dis[i][j]>dis[i][k]+dis[k][j]) dis[i][j]=dis[i][k]+dis[k][j],pos[i][j]=k;
}
if(ans==inf) return puts("No solution."),0;
for(int i=0;i<path.size();++i) printf("%d ",path[i]);
return 0;
}
传递闭包
已知一个有向图中任意两点之间是否有连边,要求判断任意两点是否连通
按照 Floyd 的过程,逐个加入点判断一下。
只是此时的边的边权变为 \(1/0\),而取 \(min\)变成了与运算。
再进一步用 bitset 优化,复杂度可以到 \(O\left(\dfrac{n^3}w\right)\)
for (k=1;k<=n;k++)
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
f[i][j]|=f[i][k]&f[k][j];
// std::bitset<SIZE> f[SIZE];
for (k = 1; k <= n; k++)
for (i = 1; i <= n; i++)
if (f[i][k]) f[i] = f[i] & f[k];
输出方案
开一个pre
数组,在更新距离的时候记录下来后面的点是如何转移过去的,算法结束前再递归地输出路径即可。
比如 Floyd 就要记录pre[i][j] = k;
,Bellman-Ford 和 Dijkstra 一般记录 pre[v] = u
。
树上问题
LCA
倍增
void dfs(int u,int ff){
for(int i=head[u],v;i;i=e[i].nxt)
if((v=e[i].v)!=ff) f[v][0]=u,dep[v]=dep[u]+1,dfs(v,u);
}
void doubling(){
for(int j=1;j<=20;++j)
for(int i=1;i<=n;++i)
if(dep[i]>=1<<j) f[i][j]=f[f[i][j-1]][j-1];
}
int LCA(int x,int y){
if(dep[x]>dep[y]) swap(x,y);
for(int i=20;i>=0;--i)
if(dep[f[y][i]]>=dep[x]) y=f[y][i];
if(x==y) return x;
for(int i=20;i>=0;--i)
if(f[x][i]^f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
仓鼠找sugar:如果两条路径相交 那么一定有一条路径的LCA在另一条路径上 判断一个点\(x\)是否在路径\(s->t\)上:\(dep[x]>=dep[LCA(s,t)],LCA(s,x)=x或LCA(t,x)=x\)
树的直径
两遍dfs
int s,mxl,len[N];
void dfs(int u,int ff){
if(len[u]>mxl) s=u,mxl=f[u];
for(int i=head[u],v;i;i=e[i].nxt)
if((v=e[i].v)!=ff) len[v]=len[u]+e[i].w,dfs(v,u);
}
int main(){
......
memset(len,0,sizeof(len)),mxl=-1,dfs(1,0);
memser(len,0,sizeof(len)),mxl=-1,dfs(s,0);
return 0;
}
树形dp
int mxl,f[N][2];
void dfs(int u,int ff){
f[u][0]=f[u][1]=0;
for(int i=head[u],v,dis;i;i=e[i].nxt)
if((v=e[i].v)!=ff){
dfs(v,u);
dis=f[v][0]+e[i].w;
if(dis>f[u][0]) f[u][1]=f[u][0],f[u][0]=dis;
else if(dis>f[u][1]) f[u][1]=dis;
mxl=max(mxl,f[u][0]+f[u][1]);
}
}
树的重心
以树的重心为根时,所有的子树的大小都不超过整个树大小的一半
找到一个点,其所有的子树节点数最少,那么这个点就是这棵树的重心
可通过两次dfs求出,第一遍求出每个点的子树\(sz_x\) 第二遍找出使\(max_{v\in son_u}\{n-sz_u,sz_v\}\)最小的节点
性质:树中所有点到某个点的距离和中,到中心的距离和是最小的;若有两个重心,那么他们的距离和都一样
int ans,siz=inf;
void dfs(int u,int ff){
sz[u]=1;int res=0;
for(int i=head[u],v;i;i=e[i].nxt)
if((v=e[i].v)!=ff) dfs(v,u),sz[u]+=sz[v],res=max(res,son[v]);
res=max(res,n-sz[u]);
if(res<siz) ans=u,siz=res;
}
树上差分
最小生成树
kruskal
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
void kruskal(){
for(int i=1;i<=n;++i) f[i]=i;
for(int i=1,u,v,cnt=0;i<=m;++i)
if(find(u=e[i].u)!=find(v=e[i].v)){
f[f[u]]=f[v],sum+=e[i].w;
if(++cnt==n-1) return;
}
}
prim
priority_queue<pii,vector<pii>,greater<pii> >q;
void prim(){
memset(vis,0,sizeof(vis)),memset(dis,inf,sizeof(dis));
q.push(make_pair(dis[1]=0,1));
while(!q.empty()){
int u=q.top().second;q.pop();
if(vis[u]) continue;
ans+=dis[u],vis[u]=1;
for(int i=head[u],v;i;i=e[i].nxt)
if(!vis[v=e[i].v]&&dis[v]>e[i].w) q.push(make_pair(dis[v]=e[i].w,v));
}
}
tarjan
有向图
int idx=0,Bcnt=0,St[N],dfn[N],low[N],bl[N],sz[N];bool inst[N];
void tarjan(int u){
dfn[u]=low[u]=++idx,St[++St[0]]=u,inst[u]=1;
for(int i=head[u],v;i;i=e[i].nxt)
if(!dfn[v=e[i].v]) tarjan(v),low[u]=min(low[u],low[v]);
else if(inst[v]&&dfn[v]<low[u]) low[u]=dfn[v];
if(dfn[u]==low[u]){
int v;++Bcnt;
do{
inst[v=St[St[0]--]]=0,bl[v]=Bcnt,++sz[Bcnt];
}while(u!=v);
}
}
欧拉图
欧拉图中所有顶点的度数都是偶数 若\(G\)是欧拉图,则它是若干个边不重的圈的并
匈牙利算法
int k,n,m,ans,match[N];
double link[N][N],vis[N];
bool dfs(int x){
for(int i=1;i<=n;++i){//扫描每个男生
if(link[x][i]&&!vis[i]){//如果能连接 并且i不在当前匈牙利树中
vis[i]=1;
if(!match[i]||dfs(match[i])){match[i]=x;return 1;}
//名花无主或者能腾出空位置来
}
}
return 0;
}
int main(){
while(scanf("%d",&k)!=EOF&&k){
memset(link,0,sizeof(link)),memset(match,0,sizeof(match));
rd(m),rd(n),ans=0;
for(int i=1,x,y;i<=k;++i) rd(x),rd(y),link[x][y]=1;
for(int i=1;i<=m;++i){
memset(vis,0,sizeof(vis));
if(dfs(i)) ++ans;
}
printf("%d\n",ans);
}
return 0;
}
网络流
最大流
queue<int> q;bool vis[N];
bool bfs(){
while(!q.empty()) q.pop();
memset(vis,0,sizeof(vis));
q.push(s),vis[s]=1,incf[s]=inf;
while(!q.empty()){
int u=q.front();q.pop();
for(int i=head[u],v,w;i;i=e[i].nxt)
if(w=e[i].w&&!vis[v=e[i].v]){
incf[v]=Min(incf[u],w),pre[v]=i;
q.push(v),vis[v]=1;
if(v==t) return 1;
}
}
return 0;
}
void upd(){
int x=t;
while(x!=s){
int i=pre[x];
e[i].w-=incf[t],e[i^1].w+=incf[t],x=e[i^1].v;
}
maxflow+=incf[t];
}
int main(){
rd(n),rd(m),rd(s),rd(t);
for(int i=1,u,v,w;i<=m;++i) rd(u),rd(v),rd(w),add(u,v,w),add(v,u,0);
while(bfs()) upd();
printf("%d",maxflow);
return 0;
}
最小费用最大流
int head[N],tot=1;
struct edge{int v,flo,cos,nxt;}e[M<<1];
void add(int u,int v,int flo,int cos){
e[++tot]=(edge){v,flo,cos,head[u]},head[u]=tot;
e[++tot]=(edge){u,0,-cos,head[v]},head[v]=tot;
}
int dis[N];
queue<int>q;bool vis[N];
bool spfa(){
memset(vis,0,sizeof(vis)),memset(dis,inf,sizeof(dis));
q.push(s),vis[s]=1,dis[s]=0,incf[s]=inf;
while(!q.empty()){
int u=q.front();q.pop(),vis[u]=0;
for(int i=head[u],v,flo,cos;i;i=e[i].nxt)
if((flo=e[i].flo)&&dis[v=e[i].v]>dis[u]+(cos=e[i].cos)){
dis[v]=dis[u]+cos,pre[v]=i,incf[v]=Min(incf[u],flo);
if(!vis[v]) q.push(v),vis[v]=1;
}
}
return dis[t]!=inf;
}
void upd(){
int x=t;
while(x!=s){
int i=pre[x];
e[i].flo-=incf[t],e[i^1].flo+=incf[t],x=e[i^1].v;
}
mxflo+=incf[t],mncos+=incf[t]*dis[t];
}
int main(){
rd(n),rd(m),rd(s),rd(t);
for(int i=1,u,v,w,z;i<=m;++i) rd(u),rd(v),rd(w),rd(z),add(u,v,w,z);
while(spfa()) upd();
printf("%d %d",mxflo,mncos);
return 0;
}
数论
扩欧
void exgcd(ll a,ll b,ll &x,ll &y){
if(!b){x=1,y=0;return;}
exgcd(b,a%b,x,y);
ll t=x;x=y,y=t-(a/b)*y;
}
void exgcd(int a,int b,int &d,int &x,int &y){
if(b) exgcd(b,a%b,d,y,x),y-=x*(a/b);
else d=a,x=1,y=0;
}
中国剩余定理
欧拉筛
void prime(int N){
memset(prime,0,sizeof(prime));
memset(v,0,sizeof(v));
for(int i=2;i<=N;++i){
if(!v[i]) v[i]=i,prime[++num_prime]=i;
for(int j=1;j<=num_prime&&i*prime[j]<=n;++j)
v[i*prime[j]]=prime[j];
if(!(i%prime[j])) break;
}
}
BSGS
SDOI2011 计算器
1、给定y、z、p,计算y^z mod p 的值;
2、给定y、z、p,计算满足xy ≡z(mod p)的最小非负整数x;
3、给定y、z、p,计算满足y^x ≡z(mod p)的最小非负整数x。
第一个要求直接快速幂
第二个要求因为保证P为质数 直接费马小定理求逆元然后*z
第三个就是BSGS模板
const int N=10000+5,M=20000+5,INF=1e9+7,inf=0x3f3f3f3f;
int y,z,p;
int qpow(int a,int b){
int res=1;
while(b){
if(b&1) res=(ll)a*res%p;
a=(ll)a*a%p,b>>=1;
}
return res;
}
map<int,int>hash;
int BSGS(){
hash.clear();y%=p,z%=p;
if(!y) return -1;
int t=(int)sqrt(p)+1;
for(int j=0,val;j<t;++j)
val=(ll)z*qpow(y,j)%p,hash[val]=j;
y=qpow(y,t);
if(!y) return !z?1:-1;
for(int i=0,val,j;i<=t;++i){
val=qpow(y,i);
j=hash.find(val)==hash.end()?-1:hash[val];
if(j>=0&&i*t-j>=0) return i*t-j;
}
return -1;
}
void work2(){
if(!(y%p)&&z%p) puts("Orz, I cannot find x!");
else printf("%lld\n",(ll)qpow(y,p-2)*z%p);
}
void work3(){
int ans=BSGS();
if(ans==-1) puts("Orz, I cannot find x!");
else printf("%d\n",ans);
}
int main(){
int T,K;rd(T),rd(K);
while(T--){
rd(y),rd(z),rd(p);
if(K==1) printf("%d\n",(qpow(y,z))%p);
else if(K==2) work2();
else work3();
}
return 0;
}
贪心
数据结构
并查集
银河英雄传说
如果是询问指令,输出一行,仅包含一个整数,表示在同一列上第\(i\)号战舰与第\(j\)号战舰之间布置的战舰数目。如果第\(i\)号战舰与第\(j\)号战舰当前不在同一列上,则输出\(-1\) 带权并查集
int d[N],sz[N];
int find(int x){
if(x==f[x]) return x;
int rt=find(f[x]);
d[x]+=d[f[x]];
return f[x]=rt;
}
void Merge(int x,int y){f[x=find(x)]=y=find(y),d[x]=sz[y],sz[y]+=sz[x];}
int main(){
int T,x,y;char opt[5];
rd(T);
for(int i=1;i<=30000;++i) f[i]=i,sz[i]=1;
while(T--){
scanf("%s",opt);rd(x),rd(y);
if(opt[0]=='M') Merge(x,y);
else{
if(find(x)!=find(y)) puts("-1");
else printf("%d\n",Abs(d[x]-d[y])-1);
}
}
return 0;
}
按轶合并
int size[N]; //记录子树的大小
void Union(int x, int y) {
int xx = find(x),yy = find(y);
if (xx == yy) return;
if (size[xx] > size[yy]) swap(xx, yy);
fa[xx] = yy,size[yy] += size[xx];
}
食物链
开三倍x 自身 x+2n 猎物 x+3n 天敌
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
void Union(int x,int y) {f[find(y)]=find(x);}
int main(){
rd(n),rd(k);
for(int i=1;i<=n*3;++i) f[i]=i;
for(int i=1,op,x,y;i<=k;++i){
rd(op),rd(x),rd(y);
if(x>n||y>n) {++ans;continue;}
if(op==1){
if(find(x+n)==find(y)||find(x+2*n)==find(y)) ++ans;//为猎物 为天敌
else Union(x,y),Union(x+n,y+n),Union(x+2*n,y+2*n);
}
else{
if(x==y) {++ans;continue;}
if(find(x)==find(y)||find(x)==find(y+n)) ++ans;//为同类 为猎物
else Union(x,y+2*n),Union(x+n,y),Union(x+2*n,y+n);
}
}
printf("%d",ans);
return 0;
}
树状数组
\(lowbit(x)\)表示非负整数\(x\)在二进制表示下“最低位的\(1\)及其后面所有的\(0\)”所构成的数值
逆序对
struct node{int w,id;}a[N],b[N];
bool cmp(node x,node y){return x.w<y.w;}
int query(int x){int ret=0;while(x>0)ret=(ret+t[x])%P,x-=(x&(-x));return ret;}
void upd(int x){while(x<=n)++t[x],x+=(x&(-x));}
int main(){
rd(n);
for(int i=1;i<=n;++i) rd(a[i].w),a[i].id=i;
for(int i=1;i<=n;++i) rd(b[i].w),b[i].id=i;
sort(a+1,a+n+1,cmp),sort(b+1,b+n+1,cmp);
for(int i=1;i<=n;++i) c[a[i].id]=b[i].id;
for(int i=n;i;--i)
upd(c[i]),ans=(ans+query(c[i]-1))%P;
printf("%d",ans);
return 0;
}
noip2013火柴排队 mergesort
c[a[i].id]=b[i].id
得理解 \(c[i]\)中\(i\)对应\(a\)中第\(i\)小的数的位置,\(c[i]\)对应\(b\)中第\(i\)小的数的位置 排完序后\(c[i]=i\)即\(a\)中第\(i\)小的数的位置与中第\(i\)小的数的位置相同
struct node{int w,id;}a[N],b[N];
bool cmp(node x,node y){return x.w<y.w;}
void mergesort(int l,int r){
if(l>=r) return;
int mid=l+r>>1,pl=l,pr=mid+1,k=l;
mergesort(l,mid),mergesort(mid+1,r);
while(pl<=mid&&pr<=r)
if(c[pl]<=c[pr]) rk[k++]=c[pl++];
else rk[k++]=c[pr++],ans=(ans+mid-pl+1)%P;
while(pl<=mid) rk[k++]=c[pl++];
while(pr<=r) rk[k++]=c[pr++];
for(int i=l;i<=r;++i) c[i]=rk[i];
}
int main(){
rd(n);
for(int i=1;i<=n;++i) rd(a[i].w),a[i].id=i;
for(int i=1;i<=n;++i) rd(b[i].w),b[i].id=i;
sort(a+1,a+n+1,cmp),sort(b+1,b+n+1,cmp);
for(int i=1;i<=n;++i) c[a[i].id]=b[i].id;
mergesort(1,n);
printf("%d",ans);
return 0;
}
ST表
询问区间最值
int query(int x,int y){
int s=lg[y-x+1];
return max(f[x][s],f[y-cm[s]+1][s]);
}
int main(){
for(int i=1;i<=n;++i) rd(a[i]);lg[0]=-1,cm[0]=1;
for(int i=1;i<=20;++i) cm[i]=cm[i-1]<<1;
for(int i=1;i<=n;++i) f[i][0]=a[i],lg[i]=lg[i>>1]+1;
for(int j=1;j<=20;++j)
for(int i=1;i+cm[j]-1<=n;++i) f[i][j]=Max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
线段树
[SP1716 GSS3 - Can you answer these queries III]
动态查询最大子段和+单点修改
struct SegmentTree{int sum,lmx,rmx,mxs;}tree[N];
void pushup(int o){
tree[o].sum=tree[lson].sum+tree[rson].sum;
tree[o].lmx=Max(tree[lson].lmx,tree[lson].sum+tree[rson].lmx);
tree[o].rmx=Max(tree[rson].rmx,tree[rson].sum+tree[lson].rmx);
tree[o].mxs=Max(Max(tree[lson].mxs,tree[rson].mxs),tree[lson].rmx+tree[rson].lmx);
}
void buildtree(int o,int l,int r){
if(l==r){tree[o].sum=tree[o].lmx=tree[o].rmx=tree[o].mxs=a[l];return;}
int mid=l+r>>1;
buildtree(lson,l,mid),buildtree(rson,mid+1,r);
pushup(o);
}
void modify(int o,int l,int r,int x,int k){
if(l==r){tree[o].sum=tree[o].lmx=tree[o].rmx=tree[o].mxs=k;return;}
int mid=l+r>>1;
if(x<=mid) modify(lson,l,mid,x,k);
else modify(rson,mid+1,r,x,k);
pushup(o);
}
SegmentTree query(int o,int l,int r,int x,int y){
if(x<=l&&r<=y) return tree[o];
int mid=l+r>>1;
if(y<=mid) return query(lson,l,mid,x,y);
else if(x>mid) return query(rson,mid+1,r,x,y);
else{
SegmentTree ls,rs,ans;
ls=query(lson,l,mid,x,y),rs=query(rson,mid+1,r,x,y);
ans.sum=ls.sum+rs.sum;
ans.lmx=Max(ls.lmx,ls.sum+rs.lmx);
ans.rmx=Max(rs.rmx,rs.sum+ls.rmx);
ans.mxs=Max(Max(ls.mxs,rs.mxs),ls.rmx+rs.lmx);
return ans;
}
}
int main(){
rd(n);
for(int i=1;i<=n;++i) rd(a[i]);
buildtree(1,1,n);rd(q);
for(int i=1,x,y,op;i<=q;++i){
rd(op),rd(x),rd(y);
if(op==1) printf("%d\n",query(1,1,n,x,y).mxs);
else modify(1,1,n,x,y);
}
return 0;
}
主席树
又理解了一遍== 发现以前只是在背代码 压根不太理解
主席树的主要思想就是:保存每次插入操作时的历史版本,以便查询区间第k小
看图好理解嘿嘿嘿嘿 其实脑抽理解了好久....
只更改了 \(O(log\ n)\)个结点,形成一条链,也就是说每次更改的结点数 = 树的高度。
我们把问题简化一下:每次求 \([1,r]\)区间内的k小值。
怎么做呢?只需要找到插入 r 时的根节点版本,然后用普通权值线段树(有的叫键值线段树/值域线段树)做就行了。
那么这个相信大家很简单都能理解,把问题扩展到原问题——求\([l,r]\)区间k小值。
这里我们再联系另外一个知识理解: 前缀和 。
这个小东西巧妙运用了区间减法的性质,通过预处理从而达到 回答每个询问。
那么我们阔以发现,主席树统计的信息也满足这个性质。
所以……如果需要得到\([l,r]\)的统计信息,只需要用\([1,r]\)的信息减去\([1,l-1]\)的信息就行了。
那么至此,该问题解决!
const int N=2e5+5,M=100+5,inf=0x3f3f3f3f;
int n,m,a[N],w[N],tot=0,tl,rt[N];
struct SegmentTree{int lc,rc,sum;}t[N*50];
void pup(int o){t[o].sum=t[t[o].lc].sum+t[t[o].rc].sum;}
void upd(int &o,int l,int r,int pre,int k){
o=++tot;
if(l==r){t[o].sum=t[pre].sum+1;return;}
int mid=l+r>>1;
if(k<=mid) upd(t[o].lc,l,mid,t[pre].lc,k),t[o].rc=t[pre].rc;
else upd(t[o].rc,mid+1,r,t[pre].rc,k),t[o].lc=t[pre].lc;
pup(o);
}
int query(int l,int r,int x,int y,int k){
if(l==r) return l;
int mid=l+r>>1,ss=t[t[y].lc].sum-t[t[x].lc].sum;
if(ss>=k) return query(l,mid,t[x].lc,t[y].lc,k);
else return query(mid+1,r,t[x].rc,t[y].rc,k-ss);
}
int main(){
rd(n),rd(m);
for(int i=1;i<=n;++i) rd(a[i]),w[i]=a[i];
sort(a+1,a+n+1);
tl=unique(a+1,a+n+1)-a-1;
for(int i=1;i<=n;++i){
w[i]=lower_bound(a+1,a+tl+1,w[i])-a;
upd(rt[i],1,tl,rt[i-1],w[i]);
}
for(int i=1,l,r,k;i<=m;++i)
rd(l),rd(r),rd(k),printf("%d\n",a[query(1,tl,rt[l-1],rt[r],k)]);
return 0;
}
平衡树
手写版
void pushup(int x){size[x]=size[ch[x][0]]+size[ch[x][1]]+cnt[x];}
int chk(int x){return ch[par[x]][1]==x;}
void rotate(int x){
int y=par[x],z=par[y],k=chk(x),w=ch[x][k^1];
ch[y][k]=w,par[w]=y;
ch[z][chk(y)]=x,par[x]=z;
ch[x][k^1]=y,par[y]=x;
pushup(y),pushup(x);
}
void splay(int x,int goal){
while(par[x]!=goal){
int y=par[x],z=par[y];
if(z!=goal) (chk(x)==chk(y))?rotate(y):rotate(x);
rotate(x);
}
if(!goal) root=x;
}
void find(int x){//将最大的小于等于x的数所在的节点splay到根
int cur=root;
while(ch[cur][x>val[cur]]&&val[cur]!=x) cur=ch[cur][x>val[cur]];
splay(cur,0);
}
void insert(int x){
int cur=root,f=0;
while(cur&&val[cur]!=x) f=cur,cur=ch[cur][x>val[cur]];//当u存在并且没有移动到当前的值
if(cur) ++cnt[cur];
else{
cur=++tot;
if(f) ch[f][x>val[f]]=cur;
par[cur]=f,val[cur]=x;
size[cur]=cnt[cur]=1;
ch[cur][0]=ch[cur][1]=0;
}
splay(cur,0);
}
int kth(int k){
int cur=root;
while(1){
if(ch[cur][0]&&k<=size[ch[cur][0]]) cur=ch[cur][0];
else if(k>size[ch[cur][0]]+cnt[cur]) k-=size[ch[cur][0]]+cnt[cur],cur=ch[cur][1];
else return cur;
}
}
int Nxt(int x,int fla){
find(x);
int cur=root;
if(val[cur]>x&&fla) return cur;
if(val[cur]<x&&!fla) return cur;
cur=ch[cur][fla];
while(ch[cur][fla^1]) cur=ch[cur][fla^1];
return cur;
}
void remove(int x){
int pre=Nxt(x,0),nxt=Nxt(x,1);
splay(pre,0),splay(nxt,pre);
int del=ch[nxt][0];
if(cnt[del]>1) --cnt[del],splay(del,0);
else ch[nxt][0]=0;
}
int getrank(int x){
find(x);
return size[ch[root][0]];
}
int main(){
rd(n);
insert(INF),insert(-INF);
while(n--){
rd(op),rd(x);
if(op==1) insert(x);
else if(op==2) remove(x);
else if(op==3) printf("%d\n",getrank(x));
else if(op==4) printf("%d\n",val[kth(x+1)]);
else if(op==5) printf("%d\n",val[Nxt(x,0)]);
else if(op==6) printf("%d\n",val[Nxt(x,1)]);
}
return 0;
}
vector实现
vector<int>vec;
int main(){
rd(n);
while(n--){
int op,x;
rd(op),rd(x);
if(op==1) vec.insert(upper_bound(vec.begin(),vec.end(),x),x);
else if(op==2) vec.erase(lower_bound(vec.begin(),vec.end(),x));
else if(op==3) printf("%d\n",lower_bound(vec.begin(),vec.end(),x)-vec.begin()+1);
else if(op==4) printf("%d\n",vec[x-1]);
else if(op==5) printf("%d\n",*(--lower_bound(vec.begin(),vec.end(),x)));
else if(op==6) printf("%d\n",*upper_bound(vec.begin(),vec.end(),x));
}
return 0;
}
multiset实现
distance(pos1,pos2)返回一个int值为pos1与pos2之间的距离,pos1,pos2为指向同一容器的迭代器。 时间复杂度:O(N)
advance(pos,k)一个void的函数,让pos这个迭代器前进k步 时间复杂度:O(N)
set.equal_range(x),它返回的一队迭代器,因此是pair类型,定义时需注意。
multiset<int>mul;
int main(){
rd(n);
multiset<int>::iterator it;
while(n--){
rd(op),rd(x);
if(op==1) mul.insert(x);
else if(op==2) mul.erase(mul.lower_bound(x));
else if(op==3) printf("%d\n",distance(mul.begin(),mul.lower_bound(x))+1);
else if(op==4) it=mul.begin(),advance(it,x-1),printf("%d\n",*it);
else if(op==5) printf("%d\n",*(--mul.lower_bound(x)));
else if(op==6) printf("%d\n",*(mul.upper_bound(x)));
}
return 0;
}
莫队
小Z数袜子
struct node{int l,r,id,bl;}q[N];
bool cmp(node A,node B){return (A.bl^B.bl)?A.bl<B.bl:((A.bl&1)?A.r<B.r:A.r>B.r);}
//bool cmp(node A,node B){return A.bl==B.bl?A.r<B.r:A.bl<B.bl;}
void count(int x,int add){
Ans-=(cnt[a[x]]*cnt[a[x]]),cnt[a[x]]+=add;
Ans+=(cnt[a[x]]*cnt[a[x]]);
}
ll gcd(ll a,ll b){
if(a>b) swap(a,b);
return !a?b:gcd(b%a,a);
}
int main(){
rd(n),rd(m),block=sqrt(n);
for(int i=1;i<=n;++i) rd(a[i]);
for(int i=1;i<=m;++i) rd(q[i].l),rd(q[i].r),q[i].id=i,q[i].bl=(q[i].l-1)/block+1;
sort(q+1,q+m+1,cmp);
int l=1,r=0;Ans=0ll;
for(int i=1;i<=m;++i){
while(l<q[i].l) count(l++,-1);
while(l>q[i].l) count(--l,1);
while(r<q[i].r) count(++r,1);
while(r>q[i].r) count(r--,-1);
if(q[i].l==q[i].r) {aa[q[i].id]=0,ab[q[i].id]=1;continue;}
ll x=q[i].r-q[i].l+1;
aa[q[i].id]=Ans-x,ab[q[i].id]=x*(x-1);
}
for(int i=1;i<=m;++i){
ll x=gcd(aa[i],ab[i]);
if(x>0) printf("%lld/%lld\n",aa[i]/x,ab[i]/x);
else printf("%lld/%lld\n",aa[i],ab[i]);
}
return 0;
}
树剖
void dfs1(int u,int ff){
dep[u]=dep[fa]+1,f[u]=ff,sz[u]=1;
for(int i=head[u],v,mxs=-1;i;i=e[i].nxt)
if((v=e[i].v)!=ff)
dfs1(v,u),sz[u]+=sz[v],(sz[v]>mxs)?(son[u]=v,mxs=sz[v],1):1;
}
void dfs2(int u,int topf){
id[dfn[u]=++idx]=u,top[u]=topf;
if(!son[u]) return;
dfs2(son[u],topf);
for(int i=head[u],v;i;i=e[i].nxt)
if((v=e[i].v)!=ff&&v!=son[u]) dfs2(v,v);
}
int LCA(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
x=f[top[x]];
}
return dep[x]<dep[y]?x:y;
}
int qrange(int x,int y){
int ret=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
ret=(ret+query(1,1,n,dfn[top[x]],dfn[x]))%P,x=f[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
ret=(ret+query(1,1,n,dfn[x],dfn[y]));
return ret;
}
int qson(int x){return query(1,1,n,dfn[x],dfn[x]+sz[x]-1);}
换根
int findc(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
if(f[top[x]]==y) return top[x];
x=f[top[x]];
}
return dep[x]<dep[y]?son[x]:son[y];
}
int qson(int x){
if(x==rt) return query(1,1,idx,1,idx);
int lca=LCA(x,rt);
if(x!=lca) return query(1,1,n,dfn[x],dfn[x]+sz[x]-1);
int chi=findc(x,rt);
return query(1,1,b,1,n)-query(1,1,n,dfn[chi],dfn[chi]+sz[chi]-1);
}
ddp
给定一棵\(n\)个点的树,点带点权。
有\(m\)操作,每次操作给定\(x,y\),表示修改点\(x\)的权值为\(y\) 在每次操作之后求出这棵树的最大权独立集的权值大小。
ll w[N],f[N][2];
int head[N],tot=0;
struct edge{int v,nxt;}e[N<<1];
void add(int u,int v){e[++tot]=(edge){v,head[u]},head[u]=tot;}
int idx=0,dfn[N],id[N],fa[N],son[N],top[N],bot[N],sz[N];
void dfs1(int u,int ff){
fa[u]=ff,sz[u]=1;
for(int i=head[u],v,mxs=-1;i;i=e[i].nxt){
if((v=e[i].v)==ff) continue;
dfs1(v,u),sz[u]+=sz[v];
if(mxs<sz[v]) mxs=sz[v],son[u]=v;
}
}
void dfs2(int u,int topf){
dfn[u]=++idx,id[idx]=u,top[u]=topf;
if(!son[u]) {bot[u]=u;return;}
dfs2(son[u],topf),bot[u]=bot[son[u]];
for(int i=head[u],v;i;i=e[i].nxt)
if((v=e[i].v)!=fa[u]&&v!=son[u]) dfs2(v,v);
}
void dfs(int u){
f[u][0]=0,f[u][1]=w[u];
for(int i=head[u],v;i;i=e[i].nxt)
if((v=e[i].v)!=fa[u]){
dfs(v);
f[u][0]+=Max(f[v][1],f[v][0]),f[u][1]+=f[v][0];
}
}
struct Matri{
ll a[2][2];
Matri operator*(const Matri &X)const{
Matri c;
memset(c.a,0,sizeof(c.a));
for(int i=0;i<=1;++i)
for(int j=0;j<=1;++j)
for(int k=0;k<=1;++k)
c.a[i][j]=Max(c.a[i][j],a[i][k]+X.a[k][j]);
return c;
}
}val[N],t[N<<2],ans;
void pup(int o){t[o]=t[ls]*t[rs];}
void mdf(int o,int l,int r,int x){
if(l==r){t[o]=val[l];return;}
int mid=l+r>>1;
if(x<=mid) mdf(ls,l,mid,x);
else mdf(rs,mid+1,r,x);
pup(o);
}
Matri query(int o,int l,int r,int x,int y){
if(x<=l&&r<=y) return t[o];
int mid=l+r>>1;
if(y<=mid) return query(ls,l,mid,x,y);
if(x>mid) return query(rs,mid+1,r,x,y);
return query(ls,l,mid,x,y)*query(rs,mid+1,r,x,y);
}
void build(int o,int l,int r){
if(l==r){
int u=id[l];ll g0=0,g1=w[u];
for(int i=head[u],v;i;i=e[i].nxt)
if((v=e[i].v)!=fa[u]&&v!=son[u])
g0+=Max(f[v][0],f[v][1]),g1+=f[v][0];
val[l]=t[o]=(Matri){g0,g0,g1,-inf};
return;
}
int mid=l+r>>1;
build(ls,l,mid),build(rs,mid+1,r);
pup(o);
}
void Mdf(int x,int k){
val[dfn[x]].a[1][0]+=k-w[x],w[x]=k;
while(x){
Matri a=query(1,1,n,dfn[top[x]],dfn[bot[x]]),b;
mdf(1,1,n,dfn[x]);
b=query(1,1,n,dfn[top[x]],dfn[bot[x]]);
x=fa[top[x]];if(!x) return;
int nw=dfn[x];
ll g0=a.a[0][0],g1=a.a[1][0],f0=b.a[0][0],f1=b.a[1][0];
val[nw].a[0][0]=val[nw].a[0][1]=val[nw].a[0][0]+Max(f0,f1)-Max(g0,g1),
val[nw].a[1][0]=val[nw].a[1][0]+f0-g0;
}
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
rd(n),rd(m);
for(int i=1;i<=n;++i) rd(w[i]);
for(int i=1,u,v;i<n;++i) rd(u),rd(v),add(u,v),add(v,u);
dfs1(1,0),dfs2(1,1),dfs(1),
build(1,1,idx);
for(int i=1,x,y;i<=m;++i){
rd(x),rd(y);
Mdf(x,y);
ans=query(1,1,n,dfn[1],dfn[bot[1]]);
printf("%d\n",Max(ans.a[0][0],ans.a[1][0]));
}
return 0;
}
分数规划
模板:
从\(n\)个物品中选\(k\)个使其\(\frac {\sum a[i]}{\sum b[i]}\) 最大
double check(double l){
double sum=0.0;
for(int i=1;i<=n;++i) d[i]=(double)a[i]-l*b[i];
sort(d+1,d+n+1);
for(int i=n-k+1;i<=n;++i) sum+=d[i];
return sum;
}
int main(){
rd(n),rd(k);
double l=0.0,r=0.0,mid;
for(int i=1;i<=n;++i) rd(a[i]);
for(int i=1;i<=n;++i) rd(b[i]),r=Max(r,1.0*a[i]/b[i]);
while(r-l>=eps){
mid=(l+r)/2;
if(check(mid)>0) l=mid;
else r=mid;
}
printf("%.4lf\n",l);
return 0;
}
desert king 最优比率生成树
struct node{int x,y,z;}c[N];
double qdis(int x,int y){return sqrt((double)(1.0*x*x)+(1.0*y*y));}
bool vis[N];
double sum,dis[N],d[N][N];
bool check(double mid){
for(int i=0;i<=n;++i) dis[i]=1e20,d[i][i]=0.0,vis[i]=0;
dis[1]=sum=0.0;
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;++j) d[i][j]=d[j][i]=b[i][j]-mid*a[i][j];
for(int i=1,u=0;i<=n;++i,u=0){
for(int j=1;j<=n;++j)
if(!vis[j]&&dis[j]<dis[u]) u=j;
vis[u]=1,sum+=dis[u];
for(int v=1;v<=n;++v)
if(!vis[v]) dis[v]=Min(dis[v],d[u][v]);
}
return sum>0;
}
int main(){
while(scanf("%d",&n)!=EOF&&n){
for(int i=1;i<=n;++i) rd(c[i].x),rd(c[i].y),rd(c[i].z);
double l=0.0,r=10.0,mid;
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;++j)
a[i][j]=a[j][i]=qdis(c[i].x-c[j].x,c[i].y-c[j].y),b[i][j]=b[j][i]=(double)Abs(c[i].z-c[j].z);//,r=Max(r,b[i][j]/a[i][j])
while(r-l>=eps){
mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%.3f\n",l);
}
return 0;
}
sightseeing cows 最优比率环
找一个环使其点权/边权最大
每次check找一个正环 正环不好找就将其边权改为负值去找负环
int cnt[N];bool vis[N];
queue<int>q;bool vis[N];
bool spfa(){
while(!q.empty()) q.pop();
for(int i=1;i<=n;++i) dis[i]=0.0,cnt[i]=vis[i]=1,q.push(i);//图有可能不连通
while(!q.empty()){
int u=q.front();q.pop(),vis[u]=0;
for(int i=head[u],v;i;i=e[i].nxt)
if(dis[v=e[i].v]>dis[u]+e[i].w){
dis[v]=dis[u]+e[i].w;
if(!vis[v]) q.push(v),vis[v]=1,++cnt[v];
if(cnt[v]>=n) return 1;
}
}
return 0;
}
bool check(double x){
memset(head,0,sizeof(head)),tot=0;
for(int i=1;i<=m;++i) add(fr[i],to[i],-((double)a[to[i]]-x*co[i]));
if(spfa()) return 1;//找到负环
else return 0;
}
int main(){
rd(n),rd(m);
for(int i=1;i<=n;++i) rd(a[i]);
for(int i=1;i<=m;++i) rd(fr[i]),rd(to[i]),rd(co[i]);
double l=0.0,r=3000.0,mid;
while(r-l>=eps){
mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%.2f",l);
return 0;
}
Talent Show
记得初始化值为极小
double d[N],f[N][M];
bool check(double mid){
for(int i=0;i<=n;++i){
if(i) d[i]=(double)a[i]-mid*b[i];
for(int j=0;j<=W;++j) f[i][j]=-INF;
}
f[0][0]=0.0;
for(int i=1;i<=n;++i)
for(int j=0;j<=W;++j) f[i][j]=Max(f[i][j],f[i-1][j]),
f[i][Min(W,j+b[i])]=Max(f[i][Min(W,j+b[i])],f[i-1][j]+d[i]);
return f[n][W]>0;
}
int main(){
rd(n),rd(W);
double l=0.0,r=0.0,mid;
for(int i=1;i<=n;++i) rd(b[i]),rd(a[i]),r=Max(r,(double)a[i]/b[i]);
while(r-l>=eps){
mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%d",(int)(l*1000));
return 0;
}
动态规划
树形dp
保安站岗
每个点有三种状态 自己覆盖自己 被父亲覆盖 被儿子覆盖
然后要注意被儿子覆盖时的转移 最后如果都是儿子被孙子覆盖的花费更少的话 得选一个儿子自己覆盖自己花费最少的来覆盖 最后输出根节点中自己覆盖自己和被儿子覆盖中较小的一个
int head[N],tot=0;
struct edge{int v,nxt,w;}e[N<<1];
void add(int u,int v){e[++tot].v=v,e[tot].nxt=head[u],head[u]=tot;}
void dp(int u,int ff){
f[u][0]=a[u],f[u][1]=f[u][2]=0;
bool yes=0;int minc=inf;
for(int i=head[u];i;i=e[i].nxt)
if((v=e[i].v)!=ff){
dp(v,u);
f[u][0]+=min(f[v][1],min(f[v][0],f[v][2]));//自己覆盖自己
f[u][1]+=min(f[v][0],f[v][2]);//被父亲覆盖
f[u][2]+=min(f[v][0],f[v][2]) ;//被儿子覆盖
if(f[v][0]<=f[v][2]) yes=1;
else minc=min(minc,f[v][0]-f[v][2]);
}
if(!yes) f[u][2]+=minc;
}
int main(){
rd(n);
for(rg int i=1;i<=n;++i){
rd(i),rd(a[i]),rd(ns);
for(rg int j=1;j<=ns;++j) rd(s),add(i,s),add(s,i);
}dp(1,0);
printf("%d",min(f[1][0],f[1][2]));
return 0;
}
区间dp
染色
关路灯
一条路线上安装了n盏路灯,每盏灯的功率(即同一段时间内消耗的电量有多有少)。老张就住在这条路中间某一路灯旁,他有一项工作就是每天早上天亮时一盏一盏地关掉这些路灯
他每天都是在天亮时首先关掉自己所处位置的路灯,然后可以向左也可以向右去关灯,可以中间调头
现在已知老张走的速度为1m/s,每个路灯的位置(是一个整数,即距路线起点的距离,单位:m)、功率(W),老张关灯所用的时间很短而可以忽略不计。 求耗电最少
int main(){
rd(n),rd(s);
for(int i=1;i<=n;++i) rd(pos[i]),rd(w[i]),sum+=w[i];
for(int i=1;i<=n;++i)
for(int j=i;j<=n;++j) t[i][j]=t[i][j-1]+w[j];
for(int i=1;i<=n;++i)
for(int j=i;j<=n;++j) t[i][j]=sum-t[i][j];
memset(f,inf,sizeof(f));
f[s][s][0]=f[s][s][1]=0;
for(int l=2;l<=n;++l)//枚举长度
for(int i=1,j;i<=n-l+1;++i){//枚举左端点
j=l+i-1;
f[i][j][0]=Min(f[i+1][j][0]+t[i+1][j]*(pos[i+1]-pos[i]),f[i+1][j][1]+t[i+1][j]*(pos[j]-pos[i]));
f[i][j][1]=Min(f[i][j-1][1]+t[i][j-1]*(pos[j]-pos[j-1]),f[i][j-1][0]+t[i][j-1]*(pos[j]-pos[i]));
}
printf("%d",Min(f[1][n][0],f[1][n][1]));
return 0;
}
数位dp
不要62
void pre(){
for(int i=0;i<=9;++i) f[1][i]=(!(i==4));
for(int i=2;i<=8;++i)
for(int j=0;j<=9;++j){
if(j==4) {f[i][j]=0;continue;}
for(int k=0;k<=9;++k)
if(!(k==4||j==6&&k==2)) f[i][j]+=f[i-1][k];
}
}
ll solve(ll xx){
ll p=0,num[20],sum=0;
while(xx) num[++p]=xx%10,xx/=10;
num[p+1]=0;
for(int i=p;i>0;--i){
for(int j=0;j<num[i];++j)
if(!(j==2&&num[i+1]==6)) sum+=f[i][j];
if(num[i]==4||(num[i]==2&&num[i+1]==6)) break;
}
return sum;
}
int main(){
pre();
while(scanf("%lld%lld",&a,&b)==2&&a&&b) printf("%lld\n",solve(b+1)-solve(a));
return 0;
}
状压dp
背包
01背包
题中已知条件有物品的重量\(w_i\),价值\(v_i\),背包总容量\(W\)
f[i][j]
为在只能放前\(i\)个物品的情况下,容量为\(j\)的背包所能达到最大总价值
f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]);
滚动数组优化为一维:f[i]
表示处理到当前物品时背包容量为\(i\)的最大价值 f[i]=max(f[i],f[i-w[i]]+v[i])
for(int i=1;i<=n;++i)
for(int j=W;j>=w[i];--j)
f[j]=max(f[j],f[j-w[i]]+v[i]);
完全背包
即01背包中物品的个数变为无数个
for(int i=1;i<=n;++i)
for(int j=w[i];j<=W;++j)
f[j]=max(f[j],f[j-c[i]]+w[i]);
多重背包
即01背包中每种物品可选\(k_i\)次
二进制拆分法
单调队列
混合背包
混合背包就是将前面三种的背包问题混合起来,有的只能取一次,有的能取无限次,有的只能取 次。
for (循环物品种类) {
if (是 0 - 1 背包) 套用 0 - 1 背包代码;
else if (是完全背包) 套用完全背包代码;
else if (是多重背包) 套用多重背包代码;
}
二维费用背包
0-1 背包问题,可是不同的是选一个物品会消耗两种价值(经费、时间)方程基本不用变,只需再开一维数组,同时转移两个价值就行了!(完全、多重背包同理)
这时候就要注意,再开一维存放物品编号就不合适了,因为容易 MLE。
for (int k = 1; k <= n; k++) {
for (int i = m; i >= mi; i--) //对经费进行一层枚举
for (int j = t; j >= ti; j--) //对时间进行一层枚举
dp[i][j] = max(dp[i][j], dp[i - mi][j - ti] + 1);
}
分组背包即:物品分组,每组的物品相互冲突,最多只能选一个物品放进去。
其实就是从“在所有物品中选择一件”变成了“从当前组中选择一件”,于是就对每一组进行一次 0-1 背包就可以了。
可以将\(t[k][i]\)表示第\(k\)组的第\(i\)件物品的编号是多少,再用\(cnt_k\)表示第\(k\)组物品有多少个。
for (int k = 1; k <= ts; k++) //循环每一组
for (int i = m; i >= 0; i--) //循环背包容量
for (int j = 1; j <= cnt[k]; j++) //循环该组的每一个物品
if (i >= w[t[k][j]])
dp[i] = max(dp[i],
dp[i - w[t[k][j]]] + c[t[k][j]]); //像0-1背包一样状态转移
这里要注意: 一定不能搞错循环顺序 ,这样才能保证正确性。
消失之物
求失去第\(i\)个物品装满背包有几种方法
在转移的时候是\(f[v]+=f[v-a[i]]\)这样统计的体积为\(a[i]\)的贡献值
int main(){
rd(n),rd(m);f[0][0]=1;
for(int i=1;i<=n;++i){
rd(a[i]),f[0][i]=1;
for(int v=m;v>=a[i];--v) f[v][0]=(f[v][0]+f[v-a[i]][0])%10;
}
for(int i=1;i<=n;++i){
for(int v=1;v<=m;++v)
if(v>=a[i]) f[v][i]=(f[v][0]-f[v-a[i]][i]+10)%10;
else f[v][i]=f[v][0];
for(int v=1;v<=m;++v) printf("%d",f[v][i]);puts("");
}
return 0;
}
POJ3093
问有多少种方案使得无法装入剩下的任意一个物品
不能再装即=最小的也装不进去 枚举不在背包中的最小值 然后比它小的肯定都装进去了 比它大的装不进去
int main(){
rd(T);
for(int t=1;t<=T;++t){
memset(f,0,sizeof(f));
rd(n),rd(m),mn=inf,ans=sum[0]=0,f[0]=1;
for(int i=1;i<=n;++i) rd(a[i]);
sort(a+1,a+n+1);
for(int i=1;i<=n;++i) sum[i]=sum[i-1]+a[i];
for(int i=n;i>=1;--i)
for(int v=m;v;--v){
if(v-sum[i-1]>=0&&v>m-a[i]) ans+=f[v-sum[i-1]];//加上sum[i-1]的贡献
//比它小的都放进去了 比它大的放不进去
if(v>=a[i]) f[v]+=f[v-a[i]];//统计
}
printf("%d %d\n",t,ans);
}
return 0;
}
存题
素数密度
给定区\([L,R](L≤R≤2147483647,R-L≤1000000)\),计算区间中素数的个数。
int cnt=0,ans=0,prime[50000];bool v[50010];
void primes(){
for(int i=2;i<=50000;++i){
if(!v[i]) v[i]=1,prime[++cnt]=i;
for(int j=1;j<=cnt&&i*prime[j]<=50000;++j){
v[i*prime[j]]=1;
if(!(i%prime[j])) break;
}
}
}
bool a[N];
int main(){
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
primes();
rd(l),rd(r);
for(int i=1;(ll)prime[i]*prime[i]<=r&&i<=cnt;++i)
for(ll j=max(2ll,(l-1)/prime[i]+1)*prime[i];j<=r;j+=prime[i])
a[j-l]=1;
for(ll i=l;i<=r;++i) if(!a[i-l]) ++ans;
printf("%d",ans);
return 0;
}
[HAOI2008]圆上整点
\(\begin{align*}x^2+y^2&=r^2\\y^2&=r^2-x^2\\y&=\sqrt{(r+x)(r-x)}\end{align*}\) 令:\(d=gcd(r+x,r-x)\),则:设\(A=\frac{r-x}d,B=\frac{r+x}d\) 因为\(d\)为\(r+x,r-x\)的最大公约数 所以一定存在\(gcd(A,B)=1,A,B\)互质
将\(A,B\)代回柿子 得:\(y^2=d^2*A*B\) 因为\(d^2,y^2\)为完全平方数 则\(A*B\)一定为完全平方数 又\(gcd(A,B)=1\ \therefore A\not=B\) 则\(A,B\)本身一定为完全平方数
设\(A\)的算术平方根为\(a\),\(B\)的算术平方根为\(b\) 即:\(A=a*a,B=b*b\)
\(\because A\not=B\ \therefore a\not=b\) 令\(a<b\) 所以\(a*a=\frac{r-x}d,b*b=\frac{r+x}d\to a^2+b^2=\frac{2r}d\)
通解:\(x=d\frac{v^2-u^2}2,y=duv,r=\frac{2(v^2+u^2)}2\)
枚举\(2r\)的因子\(d\),对于每个\(d\)用\(O(\sqrt{\frac rd})\)枚举\(u\),带入\(r\)计算出\(v^2\) 计算\(v^2\)是否为完全平方数及\(i,v\)是否互质
ll gcd(ll a,ll b){return !b?a:gcd(b,a%b);}
bool check(ll a,ll b){
ll x=(ll)sqrt(b);
if(x*x==b) return gcd(a,b)==1;
return 0;
}
ll calc(ll d){
ll ret=0;
for(ll a=1;(a*a<<1)<d;++a)
ret+=check(a,d-a*a);
return ret;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
rd(r),r<<=1;
for(ll d=1;d*d<=r;++d)
if(!(r%d)) ans+=calc(d)+(((d*d)==r)?0:calc(r/d));
printf("%lld",ans);
return 0;
}
[SCOI2009]粉刷匠
N条木板需要被粉刷。 每条木板被分为M个格子。 每个格子要被刷成红色或蓝色。
每次粉刷,只能选择一条木板上一段连续的格子涂上一种颜色。 每个格子最多只能被粉刷一次。
如果只能粉刷 T 次,最多能正确粉刷多少格子?一个格子如果未被粉刷或粉刷错颜色,就算错误粉刷。
所以就先想只有一条木板怎么做 即\(f[i][j]\)表示前\(i\)个格子刷\(j\)次最多能刷正确多少个格子
然后很容易就能想到n条木板就可以将其进行01背包来算最多能刷正确有多少个格子
int main(){
rd(n),rd(m),rd(t);
memset(f,0,sizeof(f));
for(int x=1;x<=n;++x){
scanf("%s",S+1);
for(int i=1;i<=m;++i) sum[i]=sum[i-1]+(S[i]=='1');
for(int i=1;i<=m;++i)//前i个格子
for(int j=1;j<=i;++j){//涂j次
nw[i][j]=0;
for(int k=0;k<i;++k)//由前k个格子转移过来
nw[i][j]=Max(nw[i][j],nw[k][j-1]+Max(sum[i]-sum[k],i-k-(sum[i]-sum[k])));
}
for(int i=1;i<=t;++i)
for(int j=1;j<=Min(i,m);++j) f[x][i]=Max(f[x][i],f[x-1][i-j]+nw[m][j]);
}
for(int i=1;i<=t;++i) ans=Max(ans,f[n][i]);
printf("%d",ans);
return 0;
}