优化
取消同步流
ios::sync_with_stdio(0); cin.tie(0);
读入优化
朴素读入优化
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 x * w; }
fread读入优化
inline char getcha(){ static char buf[100000],*p1=buf,*p2=buf; return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++; } int read(){ int res=0;char ch=getcha();bool XX=false; for(;!isdigit(ch);ch=getcha()) (ch=='-') && (XX=true); for(;isdigit(ch);ch=getcha()) res=(res<<3)+(res<<1)+(ch^48); return XX?-res:res; }
实数读入优化
inline double dbread(){ double X=0,Y=1.0; int w=0; char ch=0; while(!isdigit(ch)) {w|=ch=='-';ch=getchar();} while(isdigit(ch)) X=X*10+(ch^48),ch=getchar(); ch=getchar();//读入小数点 while(isdigit(ch)) X+=(Y/=10)*(ch^48),ch=getchar(); return w?-X:X; }
输出优化
朴素输出优化
void write(int x) { if (x < 0) { x = -x; putchar('-'); } if (x > 9) write(x / 10); putchar(x % 10 + '0'); }
write输出优化
char pbuf[100000],*pp=pbuf; void push(const char c) { if(pp-pbuf==100000) fwrite(pbuf,1,100000,stdout),pp=pbuf; *pp++=c; } void write(int x){ static int sta[35]; int top=0; do{sta[top++]=x%10,x/=10;}while(x); while(top) push(sta[--top]+'0'); } //请大家在程序结束前加上一句fwrite(pbuf,1,pp-pbuf,stdout);pp=pbuf; //防止出现没输出完成的类似错误
O2O3优化
#pragma GCC optimize(2) #pragma GCC optimize(3,"Ofast","inline")
离散化
int a[N],b[N],x[N*2],p[N*2]; //乘几具体看题目 int n;cin>>n; for(int i=0;i<n;i++){ cin>>a[i]>>b[i]; x[cnt++]=a[i]; x[cnt++]=b[i]; } sort(x,x+cnt); cnt=unique(x,x+cnt)-x; for(int i=0;i<n;i++){ a[i]=lower_bound(x,x+cnt,a[i])-x; b[i]=lower_bound(x,x+cnt,b[i])-x; }
数论模板
欧几里得算法(辗转相除法)
gcd(a,b)=gcd(b,a%b)
int gcd(int a, int b){ return !b ? a : gcd (b, a % b); }
a,b的最小公倍数
int lcm(int a, int b){ return a / gcd(a, b) * b; }
扩展欧几里得算法
裴蜀定理:若 a,b 是整数,且 gcd(a,b)=d,那么对于任意的整数 x,y, ax+by 都一定是 d 的倍数,特别地,一定存在整数 x,y,使 ax+by=d 成立。
扩展欧几里德常用在求解模线性方程及方程组中。
https://www.luogu.com.cn/problem/P1082
#include <bits/stdc++.h> using namespace std; int exgcd(int a,int b,int &x,int &y){ if(!b){ x=1,y=0; return a; } int gcd=exgcd(b,a%b,x,y),temp=x; x=y,y=temp-a/b*y; return gcd; } int main(){ int a,b,x,y; cin>>a>>b; exgcd(a,b,x,y); cout<<(x%b+b)%b<<"\n"; return 0; }
快速幂(a^b % mod)
ll fpow(ll a,ll b,ll mod){ if(mod==1) return 0; ll ans=1%mod; while(b){ if(b&1) ans=ans*a%mod; a=a*a%mod; b>>=1; } return ans; }
矩阵快速幂
#include<bits/stdc++.h> using namespace std; typedef long long ll; const ll mod=1e9+7; struct node{ ll mat[105][105]; }; int n; node mul(node x,node y){ node tmp; for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ tmp.mat[i][j]=0; for(int k=0;k<n;k++){ tmp.mat[i][j]+=(x.mat[i][k]*y.mat[k][j])%mod; } tmp.mat[i][j]%=mod; } } return tmp; } node matpow(node x,node y,ll num){ while(num){ if(num&1){ y=mul(x,y); } x=mul(x,x); num=num>>1; } return y; } int main(){ ios::sync_with_stdio(0);cin.tie(0); node x,y;//x是系数矩阵,y是单位矩阵 ll k; cin>>n>>k; for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ cin>>x.mat[i][j]; if(i==j) y.mat[i][j]=1; } } node c=matpow(x,y,k); for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ cout<<c.mat[i][j]<<" "; } cout<<"\n"; } return 0; }
乘法逆元
快速幂逆元
https://www.acwing.com/problem/content/878/
#include <bits/stdc++.h> using namespace std; typedef long long ll; ll mod; ll fpow(ll a,ll b){ ll ans=1%mod; while(b){ if(b&1) ans=ans*a%mod; a=a*a%mod; b>>=1; } return ans; } int main(){ int n; cin>>n; while(n--){ int a; cin>>a>>mod; if(a%mod==0) puts("impossible"); else cout<<fpow(a,mod-2)<<"\n"; } return 0; }
线性递推求逆元
https://www.luogu.com.cn/problem/P3811
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N=3e6+10; ll n,mod,inv[N]; int main(){ cin>>n>>mod; inv[1]=1; for(int i=2;i<=n;i++) inv[i]=(mod-(mod/i))*inv[mod%i]%mod; for(int i=1;i<=n;i++) cout<<inv[i]<<"\n"; return 0; }
分数取模(求单个)
https://ac.nowcoder.com/acm/contest/80/B
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int mod=998244353; typedef long long ll; ll fpow(ll a,ll b){ if(mod==1) return 0; ll ans=1%mod; while(b){ if(b&1) ans=ans*a%mod; a=a*a%mod; b>>=1; } return ans; } int main() { ll n,m,fz,fm; cin>>n>>m; fz=n*n-m;fm=n*n; cout<<(fz%mod)*fpow(fm,mod-2)%mod<<"\n"; return 0; }
分数取模(存数组)(补
素数筛(补
筛法求积性函数
中国剩余定理
整除分块
数据结构
树状数组
单点修改,区间查询
#include <bits/stdc++.h> using namespace std; const int N=500010; int q[N],t[N]; int n,m; // 算出x二进制的从右往左出现第一个1以及这个1之后的那些0组成数的二进制对应的十进制的数 inline int lowbit(int x){ return x&(-x); } void add(int x,int v){ //在x位置加上v while(x<=n){ t[x]+=v; x+=lowbit(x); } } //求前缀和 int getsum(int x){ int res=0; while(x>0){ res+=t[x]; x-=lowbit(x); } return res; } int main(){ int k,a,b; cin>>n>>m; for(int i=1;i<=n;i++) { scanf("%d",&q[i]); add(i,q[i]); //树状数组 } for(int i=0;i<m;i++){ scanf("%d%d%d",&k,&a,&b); if(k==2) printf("%d\n",getsum(b)-getsum(a-1)); else if(k==1) add(a,b); } return 0; }
区间修改,单点查询
#include <bits/stdc++.h> using namespace std; const int N=500010; int q[N],d[N],tree[N],n,m; inline int lowbit(int x){ return x&(-x); } void add(int p,int x){ for(int i=p;i<=n;i+=lowbit(i)) tree[i]+=x; } int getsum(int p){ int ans=0; for(int i=p;i;i-=lowbit(i)) ans+=tree[i]; return ans; } int main(){ ios::sync_with_stdio(0);cin.tie(0); cin>>n>>m; for(int i=1;i<=n;i++) { cin>>q[i]; d[i]=q[i]-q[i-1]; add(i,d[i]); } for(int i=0;i<m;i++){ int op,x,y,k; cin>>op; if(op==1) { cin>>x>>y>>k; add(x,k),add(y+1,-k); } else{ cin>>x; cout<<getsum(x)<<"\n"; } } return 0; }
线段树
区间修改,区间查询
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e5+10; ll a[N]; ll tree[N<<2]; ll lazy[N<<2]; inline ll ls(ll x){return x<<1;} inline ll rs(ll x){return x<<1|1;} inline void pushup(ll u){ tree[u]=tree[ls(u)]+tree[rs(u)]; } inline void build(ll u,ll ul,ll ur){ lazy[u]=0; if(ul==ur){tree[u]=a[ul];return;} ll mid=(ul+ur)>>1; build(ls(u),ul,mid); build(rs(u),mid+1,ur); pushup(u); } inline void addlazy(ll u,ll ul,ll ur,ll v){ lazy[u]+=v; tree[u]+=v*(ur-ul+1); } inline void pushdown(ll u,ll ul,ll ur){ if(lazy[u]){ ll mid=(ul+ur)>>1; addlazy(ls(u),ul,mid,lazy[u]); addlazy(rs(u),mid+1,ur,lazy[u]); lazy[u]=0; } } //区间修改 inline void update(ll l,ll r,ll u,ll ul,ll ur,ll v){ if(l<=ul&&ur<=r){ addlazy(u,ul,ur,v); return; } pushdown(u,ul,ur); ll mid=(ul+ur)>>1; if(l<=mid) update(l,r,ls(u),ul,mid,v); if(r>mid) update(l,r,rs(u),mid+1,ur,v); pushup(u); } //区间查询 inline ll query(ll l,ll r,ll u,ll ul,ll ur){ if(l<=ul&&ur<=r) return tree[u]; pushdown(u,ul,ur); ll res=0; ll mid=(ul+ur)>>1; if(l<=mid) res+=query(l,r,ls(u),ul,mid); if(r>mid) res+=query(l,r,rs(u),mid+1,ur); return res; } int main(){ ll n,m;cin>>n>>m; for(ll i=1;i<=n;i++) scanf("%lld",&a[i]); build(1,1,n); while(m--){ int t;scanf("%d",&t); ll l,r,v; if(t==1){ scanf("%lld%lld%lld",&l,&r,&v); update(l,r,1,1,n,v); } else{ scanf("%lld%lld",&l,&r); printf("%lld\n",query(l,r,1,1,n)); } } return 0; }
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N=100010; ll q[N]; int p; struct node{ int l,r; //tree[i].l和tree[i].r分别表示这个点代表的线段的左右下标 ll sum; //tree[i].sum表示这个节点表示的线段和 ll addlazy,mullazy; //懒标记 }tree[N<<2]; //开四倍空间 inline void pushup(int u){ //更新函数 tree[u].sum=(tree[u<<1].sum+tree[u<<1|1].sum)%p; //父节点的和等于两个子节点之和 } inline void pushdown(int u){ tree[u<<1].sum=((tree[u].mullazy*tree[u<<1].sum)%p+(tree[u].addlazy*(tree[u<<1].r-tree[u<<1].l+1))%p)%p; tree[u<<1|1].sum=((tree[u].mullazy*tree[u<<1|1].sum)%p+(tree[u].addlazy*(tree[u<<1|1].r-tree[u<<1|1].l+1))%p)%p; tree[u<<1].mullazy=(tree[u<<1].mullazy*tree[u].mullazy)%p; tree[u<<1|1].mullazy=(tree[u<<1|1].mullazy*tree[u].mullazy)%p; tree[u<<1].addlazy=((tree[u<<1].addlazy*tree[u].mullazy)%p+tree[u].addlazy)%p; tree[u<<1|1].addlazy=((tree[u<<1|1].addlazy*tree[u].mullazy)%p+tree[u].addlazy)%p; tree[u].mullazy=1; tree[u].addlazy=0; } //一个节点为x 它的父节点为x/2(x>>1) 左儿子2x(x<<1) 右儿子2x+1(x<<1|1) inline void build(int u,int l,int r){ tree[u].l=l; tree[u].r=r; tree[u].mullazy=1; if(l==r){ //左端点等于右端点,即为叶子节点,直接赋值即可 tree[u].sum=q[l]%p; return; } int mid=(l+r)>>1; //mid则为中间点,左儿子的结点区间为[l,mid],右儿子的结点区间为[m+1,r] build(u<<1,l,mid); //递归构造左儿子结点 build(u<<1|1,mid+1,r); //递归构造右儿子结点 pushup(u); //更新父节点 } inline void add(int u,int l,int r,ll v) { //u为结点下标,[l,r]为修改区间,v为要加上的值 if(l<=tree[u].l&&r>=tree[u].r){ tree[u].sum=(tree[u].sum+((tree[u].r-tree[u].l+1)*v)%p)%p; tree[u].addlazy=(tree[u].addlazy+v)%p; return; } pushdown(u); int mid=(tree[u].l+tree[u].r)>>1; if(l<=mid) add(u<<1,l,r,v); if(r>mid) add(u<<1|1,l,r,v); pushup(u); } inline void mul(int u,int l,int r,ll v) { //u为结点下标,[l,r]为修改区间,v为要乘上的值 if(l<=tree[u].l&&r>=tree[u].r){ tree[u].sum=(tree[u].sum*v)%p; tree[u].mullazy=(tree[u].mullazy*v)%p; tree[u].addlazy=(tree[u].addlazy*v)%p; return; } pushdown(u); int mid=(tree[u].l+tree[u].r)>>1; if(l<=mid) mul(u<<1,l,r,v); if(r>mid) mul(u<<1|1,l,r,v); pushup(u); } //区间查询 inline ll query(int u,int l,int r){ //u为结点下标, [l,r]即为要查询的区间 if(tree[u].l>=l&&tree[u].r<=r) //如果当前结点的区间包含于(?)要查询的区间内,则返回结点信息且不需要往下递归 return tree[u].sum; ll sum=0; pushdown(u); int mid=(tree[u].l+tree[u].r)>>1; //mid则为中间点,左儿子的结点区间为[l,mid],右儿子的结点区间为[mid+1,r] if(l<=mid) //先找和左边无交集 sum=(sum+query(u<<1,l,r))%p; //左儿子 if(r>mid) //再找和右边无交集 sum=(sum+query(u<<1|1,l,r))%p; //加上右儿子 return sum; //返回当前结点得到的信息 } int main(){ int n,m; int t,x,y; ll k; cin>>n>>m>>p; for(int i=1;i<=n;i++) scanf("%lld",&q[i]); build(1,1,n); for(int i=0;i<m;i++){ scanf("%d",&t); if(t==1){ scanf("%d%d%lld",&x,&y,&k); mul(1,x,y,k); } else if(t==2){ scanf("%d%d%lld",&x,&y,&k); add(1,x,y,k); } else if(t==3){ scanf("%d%d",&x,&y); printf("%lld\n",query(1,x,y)); } } return 0; }
维护区间最值操作与区间历史最值
1 l r k
:对于所有的 ,将 加上 ( 可以为负数)。2 l r v
:对于所有的 ,将 变成 。3 l r
:求 。4 l r
:对于所有的 ,求 的最大值。5 l r
:对于所有的 ,求 的最大值。
在每一次操作后,我们都进行一次更新,让 。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N=5e5+10,INF=0x3f3f3f3f; struct SegmentTree{ struct Node{ int l, r; int mx, mx_, se, cnt; ll sum; int add1, add1_, add2, add2_; } tr[N<<2]; #define lc (o<<1) #define rc (o<<1|1) void pushup(int o){ tr[o].sum=tr[lc].sum+tr[rc].sum; tr[o].mx_=max(tr[lc].mx_, tr[rc].mx_); if (tr[lc].mx==tr[rc].mx){ tr[o].mx=tr[lc].mx; tr[o].se=max(tr[lc].se, tr[rc].se); tr[o].cnt=tr[lc].cnt+tr[rc].cnt; } else if (tr[lc].mx>tr[rc].mx){ tr[o].mx=tr[lc].mx; tr[o].se=max(tr[lc].se, tr[rc].mx); tr[o].cnt=tr[lc].cnt; } else{ tr[o].mx=tr[rc].mx; tr[o].se=max(tr[lc].mx, tr[rc].se); tr[o].cnt=tr[rc].cnt; } } void update(int o, int k1, int k1_, int k2, int k2_){ tr[o].sum+=1ll*k1*tr[o].cnt+1ll*k2*(tr[o].r-tr[o].l+1-tr[o].cnt); tr[o].mx_=max(tr[o].mx_, tr[o].mx+k1_); tr[o].add1_=max(tr[o].add1_, tr[o].add1+k1_); tr[o].mx+=k1, tr[o].add1+=k1; tr[o].add2_=max(tr[o].add2_, tr[o].add2+k2_); if (tr[o].se!=-INF) tr[o].se+=k2; tr[o].add2+=k2; } void pushdown(int o){ int tmp=max(tr[lc].mx, tr[rc].mx); if (tr[lc].mx==tmp) update(lc, tr[o].add1, tr[o].add1_, tr[o].add2, tr[o].add2_); else update(lc, tr[o].add2, tr[o].add2_, tr[o].add2, tr[o].add2_); if (tr[rc].mx==tmp) update(rc, tr[o].add1, tr[o].add1_, tr[o].add2, tr[o].add2_); else update(rc, tr[o].add2, tr[o].add2_, tr[o].add2, tr[o].add2_); tr[o].add1=tr[o].add1_=tr[o].add2=tr[o].add2_=0; } void build(int o, int l, int r, int* a){ tr[o].l=l, tr[o].r=r; tr[o].add1=tr[o].add1_=tr[o].add2=tr[o].add2_=0; if (l==r){ tr[o].sum=tr[o].mx_=tr[o].mx=a[l]; tr[o].se=-INF, tr[o].cnt=1; return; } int mid=l+r>>1; build(lc, l, mid, a); build(rc, mid+1, r, a); pushup(o); } void modify1(int o, int l, int r, int k){ if (tr[o].l>r||tr[o].r<l) return; if (l<=tr[o].l&&tr[o].r<=r) { update(o, k, k, k, k); return; } pushdown(o); modify1(lc, l, r, k), modify1(rc, l, r, k); pushup(o); } void modify2(int o, int l, int r, int k){ if (tr[o].l>r||tr[o].r<l||k>=tr[o].mx) return; if (l<=tr[o].l&&tr[o].r<=r&&k>tr[o].se) { update(o, k-tr[o].mx, k-tr[o].mx, 0, 0); return; } pushdown(o); modify2(lc, l, r, k), modify2(rc, l, r, k); pushup(o); } ll query3(int o, int l, int r){ if (tr[o].l>r||tr[o].r<l) return 0; if (l<=tr[o].l&&tr[o].r<=r) return tr[o].sum; pushdown(o); return query3(lc, l, r)+query3(rc, l, r); } int query4(int o, int l, int r){ if (tr[o].l>r||tr[o].r<l) return -INF; if (l<=tr[o].l&&tr[o].r<=r) return tr[o].mx; pushdown(o); return max(query4(lc, l, r), query4(rc, l, r)); } int query5(int o, int l, int r){ if (tr[o].l>r||tr[o].r<l) return -INF; if (l<=tr[o].l&&tr[o].r<=r) return tr[o].mx_; pushdown(o); return max(query5(lc, l, r), query5(rc, l, r)); } #undef lc #undef rc } sgt; int a[N]; int main(){ int n,m; cin>>n>>m; for(int i=1;i<=n;i++) cin>>a[i]; sgt.build(1, 1, n, a); while (m--){ int op, l, r, k; cin>>op; switch (op){ case 1: cin>>l>>r>>k; sgt.modify1(1, l, r, k); break; case 2: cin>>l>>r>>k; sgt.modify2(1, l, r, k); break; case 3: cin>>l>>r; cout<<sgt.query3(1, l, r)<<"\n"; break; case 4: cin>>l>>r; cout<<sgt.query4(1, l, r)<<"\n"; break; case 5: cin>>l>>r; cout<<sgt.query5(1, l, r)<<"\n"; break; } } return 0; }
李超线段树
https://www.luogu.com.cn/problem/P4097
#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<double,int> pdi; const int N=1e5+10,mod1=39989,mod2=1e9,INF=0x3f3f3f3f; int cnt,lastans; double k[N],b[N]; int tag[N<<2]; inline int ls(int x){return x<<1;} inline int rs(int x){return x<<1|1;} inline double calc(int i,int x){return b[i]+k[i]*x;} void add(int x0,int y0,int x1,int y1) { cnt++; if(x0==x1){ //斜率不存在 k[cnt]=0; b[cnt]=max(y1,y0); } else{ k[cnt]=1.0*(y1-y0)/(x1-x0); b[cnt]=y0-k[cnt]*x0; } } void update(int l,int r,int p,int pl,int pr,int x){ int mid=(pl+pr)>>1; if(l<=pl&&pr<=r){ if(pl==pr){ if(calc(tag[p],pl)<calc(x,pl)) tag[p]=x; return; } if(!tag[p]){tag[p]=x;return;} else{ double y1=calc(tag[p],mid),y2=calc(x,mid); if(k[tag[p]]<k[x]) { if(y1<=y2) {update(l,r,ls(p),pl,mid,tag[p]);tag[p]=x;} else {update(l,r,rs(p),mid+1,pr,x);} } else if(k[tag[p]]>k[x]) { if(y1<=y2) {update(l,r,rs(p),mid+1,pr,tag[p]);tag[p]=x;} else {update(l,r,ls(p),pl,mid,x);} } else if(b[tag[p]]>b[x]){tag[p]=x;} } return; } if(l<=mid) update(l,r,ls(p),pl,mid,x); if(r>mid) update(l,r,rs(p),mid+1,pr,x); } pdi query(int p,int l,int r,int x){ if (r<x||x<l) return {0,0}; if(l==r) { return {calc(tag[p],l),tag[p]}; } double res=calc(tag[p],x); int ansid=tag[p]; int mid=(l+r)>>1; if(x<=mid){ auto temp=query(ls(p),l,mid,x); if(res<temp.first){ res=temp.first; ansid=temp.second; } else if(res==temp.first) { ansid=min(ansid,temp.second); } } else { auto temp=query(rs(p),mid+1,r,x); if(res<temp.first){ res=temp.first; ansid=temp.second; } else if(res==temp.first){ ansid=min(ansid,temp.second); } } return {res,ansid}; } int main() { int n;cin>>n; while(n--){ int op;cin>>op; if(op==1){ int x0,y0,x1,y1; cin>>x0>>y0>>x1>>y1; x0=(x0+lastans-1)%mod1+1; x1=(x1+lastans-1)%mod1+1; y0=(y0+lastans-1)%mod2+1; y1=(y1+lastans-1)%mod2+1; if(x0>x1) swap(x0,x1),swap(y0,y1); add(x0,y0,x1,y1); update(x0,x1,1,1,mod1,cnt); } else { int x;cin>>x; x=(x+lastans-1)%mod1+1; lastans=query(1,1,mod1,x).second; cout<<lastans<<"\n"; } } return 0; }
扫描线
矩形面积并
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N=100010; int n,m; int y[N<<1]; struct Segment{ int x; int y1,y2; int state;//是左边还是右边 bool operator< (const Segment &t)const{ return x<t.x; } }seg[N<<1]; struct node{ int l,r; int cover;//当前区间覆盖次数 ll len;//至少被覆盖1次的区间长度 }tree[N<<4]; inline void pushup(int u){ if(tree[u].cover) tree[u].len=tree[u].r-tree[u].l; else tree[u].len=tree[u<<1].len+tree[u<<1|1].len; } void build(int u,int l,int r){ tree[u].l=y[l],tree[u].r=y[r]; if(r-l<=1) return; int mid=(l+r)>>1; build(u<<1,l,mid),build(u<<1|1,mid,r); } void modify(int u,int l,int r,int k){ int x=tree[u].l,y=tree[u].r; if(x>=l&&y<=r){ tree[u].cover+=k; pushup(u); } else{ if(l<tree[u<<1].r) modify(u<<1,l,r,k); if(r>tree[u<<1|1].l) modify(u<<1|1,l,r,k); pushup(u); } } int main(){ cin>>n; for(int i=1;i<=n;i++){ int x1,y1,x2,y2; cin>>x1>>y1>>x2>>y2; y[i]=y1,y[n+i]=y2; seg[m++]={x1,y1,y2,1}; seg[m++]={x2,y1,y2,-1}; } sort(y+1,y+m+1); sort(seg,seg+m); build(1,1,m); ll ans=0; modify(1,seg[0].y1,seg[0].y2,seg[0].state); for(int i=1;i<m;i++){ ans+=tree[1].len*(seg[i].x-seg[i-1].x); modify(1,seg[i].y1,seg[i].y2,seg[i].state); } cout<<ans<<"\n"; return 0; }
矩形周长并
#include<bits/stdc++.h> using namespace std; int ls(int x){ return x<<1; } int rs(int x){ return x<<1|1;} const int MAXN = 200005; struct ScanLine { int l, r, h, inout; //inout=1 下边, inout=-1 上边 ScanLine() {} ScanLine(int a, int b, int c, int d) :l(a), r(b), h(c), inout(d) {} }line[MAXN]; bool cmp(ScanLine &a, ScanLine &b) { if(a.h==b.h) return a.inout>b.inout; return a.h<b.h; } //y坐标排序 bool lbd[MAXN << 2], rbd[MAXN << 2];//标记这个结点的左右两个端点是否被覆盖(0表示没有,1表示有) int num[MAXN << 2]; //这个区间有多少条独立的边 int Tag[MAXN << 2]; //标记这个结点是否有效 int length[MAXN << 2]; //这个区间的有效宽度 void pushup(int p, int pl, int pr) { if (Tag[p]) { //结点的Tag为正,这个线段对计算宽度有效 lbd[p] = rbd[p] = 1; length[p] = pr - pl + 1; num[p] = 1; //每条边有两个端点 } else if (pl == pr) length[p]=num[p]=lbd[p]=rbd[p]=0;//叶子结点 else { lbd[p] = lbd[ls(p)]; // 和左儿子共左端点 rbd[p] = rbd[rs(p)]; //和右儿子共右端点 length[p] = length[ls(p)] + length[rs(p)]; num[p] = num[ls(p)] + num[rs(p)]; if (lbd[rs(p)] && rbd[ls(p)]) num[p] -= 1; //合并边 } } void update(int L, int R, int io, int p, int pl, int pr) { if(L<=pl && pr<=R){ //完全覆盖 Tag[p] += io; pushup(p, pl, pr); return; } int mid = (pl + pr) >> 1; if (L<= mid) update(L, R, io, ls(p), pl, mid); if (mid < R) update(L, R, io, rs(p), mid+1, pr); pushup(p, pl, pr); } int main() { int n; while (~scanf("%d", &n)) { int cnt = 0; int lbd = 10000, rbd = -10000; for (int i = 0; i < n; i++) { int x1, y1, x2, y2; scanf("%d%d%d%d", &x1, &y1, &x2, &y2); //输入矩形 lbd = min(lbd, x1); //横线最小x坐标 rbd = max(rbd, x2); //横线最大x坐标 line[++cnt] = ScanLine(x1, x2, y1, 1); //给入边赋值 line[++cnt] = ScanLine(x1, x2, y2, -1); //给出边赋值 } sort(line+1, line + cnt+1, cmp); //排序。数据小,不用离散化 int ans = 0, last = 0; //last:上一次总区间被覆盖长度 for (int i = 1; i <= cnt ; i++){ //扫描所有入边和出边 if (line[i].l < line[i].r) update(line[i].l, line[i].r-1, line[i].inout, 1, lbd, rbd-1); ans += num[1]*2 * (line[i + 1].h - line[i].h); //竖线 ans += abs(length[1] - last); //横线 last = length[1]; } printf("%d\n", ans); } return 0; }
可持久化线段树(主席树)
//区间内的第 k 小值 #include <bits/stdc++.h> using namespace std ; const int N = 200010; int cnt = 0; //用cnt标记可以使用的新结点 int a[N], b[N], root[N]; //a[]是原数组,b[]是排序后数组,root[i]记录第i棵线段树的根节点编号 struct node { int L, R, sum; //L左儿子, R右儿子,sum[i]是结点i的权值 } tree[N<<5]; //需要开 nlogn空间 int build(int pl, int pr) { int rt = ++ cnt; //cnt为当前节点编号 tree[rt].sum = 0; int mid=(pl+pr)>>1; if (pl < pr) { tree[rt].L = build(pl, mid); tree[rt].R = build(mid+1, pr); } return rt; //返回当前节点的编号 } int update(int pre, int pl, int pr, int x) { //建一棵只有logn个结点的新线段树 int rt = ++cnt; //新的结点,下面动态开点 tree[rt].L = tree[pre].L;//该结点的左右儿子初始化为前一棵树相同位置结点的左右儿子 tree[rt].R = tree[pre].R; tree[rt].sum = tree[pre].sum + 1; //插了1个数,在前一棵树的相同结点加1 int mid = (pl+pr)>>1; if (pl < pr) { //从根结点往下建logn个结点 if (x <= mid) //x出现在左子树,修改左子树 tree[rt].L = update(tree[pre].L, pl, mid, x); else //x出现在右子树,修改右子树 tree[rt].R = update(tree[pre].R, mid+1, pr, x); } return rt; //返回当前分配使用的新结点的编号 } int query(int u, int v, int pl, int pr, int k) { //查询区间[u,v]第k小 if (pl == pr) return pl; //到达叶子结点,找到第k小,pl是节点编号,答案是b[pl] int x = tree[tree[v].L].sum - tree[tree[u].L].sum; //线段树相减 int mid = (pl+pr)>>1; if (x >= k) //左儿子数字大于等于k时,说明第k小的数字在左子树 return query(tree[u].L, tree[v].L, pl, mid, k); else //否则在右子树找第k-x小的数字 return query(tree[u].R, tree[v].R, mid+1, pr, k-x); } int main() { int n, m;cin>>n>>m; for (int i = 1; i <= n; i ++) { cin>>a[i]; b[i] = a[i]; } sort(b+1, b+1+n); int size = unique(b+1, b+1+n)-b-1; for (int i = 1; i <= n; i ++) { int x = lower_bound(b+1, b+1+size, a[i]) - b; root[i] = update(root[i-1], 1, size, x); } while (m--) { int x, y, k;cin>>x>>y>>k; int t = query(root[x-1], root[y], 1, size, k); //第y棵线段树减第x-1棵线段树,就是区间[x,y]的线段树 cout<<b[t]<<"\n"; } return 0; }
#include <bits/stdc++.h> using namespace std ; typedef long long ll; const int N = 1e5+10; int cas,cnt; ll a[N]; int root[N]; int n,m; struct node { int L, R; ll lazy,sum; } tree[N<<5]; //需要开 nlogn空间 void pushup(int u){ tree[u].sum=tree[tree[u].L].sum+tree[tree[u].R].sum; } int build(int pl, int pr) { int rt = ++ cnt; //cnt为当前节点编号 tree[rt].L=tree[rt].R=tree[rt].lazy=tree[rt].sum=0; if(pl==pr){ tree[rt].sum=a[pl]; return rt; } int mid=(pl+pr)>>1; tree[rt].L = build(pl, mid); tree[rt].R = build(mid+1, pr); pushup(rt); return rt; //返回当前节点的编号 } int update(int pre, int pl, int pr, int l, int r, ll v) { //建一棵只有logn个结点的新线段树 int rt = ++cnt; tree[rt] = tree[pre]; tree[rt].sum+=v*(r-l+1); if(l==pl&&r==pr){ tree[rt].lazy += v; return rt; } int mid=(pl+pr)>>1; if(r<=mid) tree[rt].L = update(tree[pre].L,pl,mid,l,r,v); else if(l>mid) tree[rt].R = update(tree[pre].R,mid+1,pr,l,r,v); else{ tree[rt].L = update(tree[pre].L,pl,mid,l,mid,v); tree[rt].R = update(tree[pre].R,mid+1,pr,mid+1,r,v); } // pushup(rt); return rt; //返回当前分配使用的新结点的编号 } //区间查询 ll query(int rt,int pl,int pr,int l,int r){ if(pl>=l&&pr<=r) return tree[rt].sum; int mid=(pl+pr)>>1; ll res=tree[rt].lazy*(r-l+1); if(r<=mid) res+=query(tree[rt].L,pl,mid,l,r); else if(l>mid) res+=query(tree[rt].R,mid+1,pr,l,r); else{ res+=query(tree[rt].L,pl,mid,l,mid); res+=query(tree[rt].R,mid+1,pr,mid+1,r); } return res; //返回当前结点得到的信息 } void solve(){ if(cas++) cout<<"\n"; cnt=0; for(int i=1;i<=n;i++) cin>>a[i]; root[0]=build(1,n); int time=0; while(m--){ char op;cin>>op; if(op=='C'){ int l,r;ll d;cin>>l>>r>>d; time++; root[time]=update(root[time-1],1,n,l,r,d); } else if(op=='Q'){ int l,r;cin>>l>>r; cout<<query(root[time],1,n,l,r)<<"\n"; } else if(op=='H'){ int l,r,t;cin>>l>>r>>t; cout<<query(root[t],1,n,l,r)<<"\n"; } else{ int t;cin>>t; time=t; } } } int main() { while(cin>>n>>m) solve(); return 0; }
珂朵莉树(ODT)
推平一段区间
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e5+10; struct node{ int l,r; mutable ll v; node(int L,int R=-1,ll V=0):l(L), r(R), v(V) {} bool operator<(const node& o) const{ return l < o.l; } }; set<node> s; int n,m; ll seed,vmax; ll a[N]; ll fpow(ll a,ll b,ll mod){ if(mod==1) return 0; ll ans=1%mod; a%=mod; while(b){ if(b&1) ans=ans*a%mod; a=a*a%mod; b>>=1; } return ans; } set<node>::iterator split(int pos){ auto it=s.lower_bound(node(pos)); if(it!=s.end() && it->l==pos) return it; --it; int L=it->l,R=it->r; ll V=it->v; s.erase(it); s.insert(node(L,pos-1,V)); return s.insert(node(pos,R,V)).first; } //推平 void assign(int l, int r, ll val){ auto itr=split(r+1),itl=split(l); s.erase(itl,itr); s.insert(node(l,r,val)); } //区间加 void add(int l,int r,ll val){ auto itr=split(r+1),itl=split(l); for(;itl!=itr;++itl) itl->v+=val; } //区间第k小 ll _rank(int l,int r,int k){ vector<pair<ll,int> >vp; auto itr=split(r+1),itl=split(l); vp.clear(); for(;itl!=itr;++itl) vp.push_back({itl->v,itl->r-itl->l+1}); sort(vp.begin(),vp.end()); for(auto i:vp){ k-=i.second; if(k<=0) return i.first; } return -1; } //区间幂次和 ll sum(int l,int r,ll ex,ll mod){ auto itr=split(r+1),itl=split(l); ll res=0; for(;itl!=itr;++itl) res=(res+(ll)(itl->r - itl->l + 1)*fpow(itl->v,ex,mod))%mod; return res; } ll rnd(){ ll ret=seed; seed=(seed*7+13)%1000000007; return ret; } int main(){ cin>>n>>m>>seed>>vmax; for(int i=1;i<=n;i++){ a[i]=(rnd()%vmax)+1; s.insert(node(i,i,a[i])); } for(int i=1;i<=m;i++){ int op=(rnd()%4)+1; int l=(rnd()%n)+1; int r=(rnd()%n)+1; if(l>r) swap(l,r); ll x,y; if(op==3) x=(rnd()%(r-l+1))+1; else x=(rnd()%vmax)+1; if(op==4) y=(rnd()%vmax)+1; if(op==1) add(l,r,x); else if(op==2) assign(l,r,x); else if(op==3) cout<<_rank(l,r,x)<<"\n"; else cout<<sum(l,r,x,y)<<"\n"; } return 0; }
图论模板
最近公共祖先(LCA)
https://www.luogu.com.cn/problem/P3379
#include <bits/stdc++.h> using namespace std; const int N=500010; int n,m,root; vector<int> g[N]; int depth[N],fa[N][20],lg[N]; void init(){ //log2(i)+1 for(int i=1;i<=n;i++){ lg[i]=lg[i-1]+(1<<lg[i-1]==i); } } void dfs(int u,int father){ depth[u]=depth[father]+1; fa[u][0]=father; // 2^i祖先为2^i-1级祖先的2^i-1级祖先 for(int i=1;(1<<i)<=depth[u];i++){ fa[u][i]=fa[fa[u][i-1]][i-1]; } for(int v:g[u]){ if(v==father) continue; dfs(v,u); } } int lca(int a,int b){ if(depth[a]<depth[b]) swap(a,b); while(depth[a]>depth[b]){ a=fa[a][lg[depth[a]-depth[b]]-1]; } if(a==b) return a; for(int i=lg[depth[a]];i>=0;i--){ if(fa[a][i]!=fa[b][i]){ a=fa[a][i]; b=fa[b][i]; } } return fa[a][0]; } int main(){ cin>>n>>m>>root; init(); for(int i=0;i<n-1;i++){ int x,y; cin>>x>>y; g[x].push_back(y); g[y].push_back(x); } dfs(root,0); for(int i=0;i<m;i++){ int a,b; cin>>a>>b; cout<<lca(a,b)<<"\n"; } return 0; }
最小生成树(MST)
Borůvka (Sollin) 算法
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MaxN = 5000 + 5, MaxM = 200000 + 5; int N, M; int U[MaxM], V[MaxM], W[MaxM]; bool used[MaxM]; int par[MaxN], Best[MaxN]; void init() { scanf("%d %d", &N, &M); for (int i = 1; i <= M; ++i) scanf("%d %d %d", &U[i], &V[i], &W[i]); } void init_dsu() { for (int i = 1; i <= N; ++i) par[i] = i; } int get_par(int x) { if (x == par[x]) return x; else return par[x] = get_par(par[x]); } inline bool Better(int x, int y) { if (y == 0) return true; if (W[x] != W[y]) return W[x] < W[y]; return x < y; } void Boruvka() { init_dsu(); int merged = 0, sum = 0; bool update = true; while (update) { update = false; memset(Best, 0, sizeof Best); for (int i = 1; i <= M; ++i) { if (used[i] == true) continue; int p = get_par(U[i]), q = get_par(V[i]); if (p == q) continue; if (Better(i, Best[p]) == true) Best[p] = i; if (Better(i, Best[q]) == true) Best[q] = i; } for (int i = 1; i <= N; ++i) if (Best[i] != 0 && used[Best[i]] == false) { update = true; merged++; sum += W[Best[i]]; used[Best[i]] = true; par[get_par(U[Best[i]])] = get_par(V[Best[i]]); } } if (merged == N - 1) printf("%d\n", sum); else puts("orz"); } int main() { init(); Boruvka(); return 0; }
字符串
Trie树(字典树)
https://www.acwing.com/problem/content/837/
#include <bits/stdc++.h> using namespace std; struct Trie { int nex[100010][26], cnt; int exist[100010]; // 该结点结尾的字符串是否存在 void insert(string s, int l) { // 插入字符串 int p = 0; for (int i = 0; i < l; i++) { int c = s[i] - 'a'; if (!nex[p][c]) nex[p][c] = ++cnt; // 如果没有,就添加结点 p = nex[p][c]; } exist[p] ++; } int find(string s, int l) { // 查找字符串 int p = 0; for (int i = 0; i < l; i++) { int c = s[i] - 'a'; if (!nex[p][c]) return 0; p = nex[p][c]; } return exist[p]; } } t; int main() { ios::sync_with_stdio(0);cin.tie(0); int n;cin>>n; char c; string s; while(n--) { cin>>c>>s; if(c=='I') t.insert(s,s.length()); else cout<<t.find(s,s.length())<<"\n"; } return 0; }
01Trie
struct Trie { int nex[N*32][2],cnt=0; void insert(int x) { int p = 0; for (int i = 30; i >= 0; i--) { int c = x>>i&1; if (!nex[p][c]) nex[p][c] = ++cnt; p = nex[p][c]; } } int find(int x) { int p = 0,res = 0; for (int i = 30; i >= 0; i--) { int c = x>>i&1; if(nex[p][c^1]) p=nex[p][c^1],res|=(1<<i); else p=nex[p][c]; } return res; } } t;
字符串哈希
https://www.acwing.com/problem/content/description/843/
#include <bits/stdc++.h> using namespace std; typedef unsigned long long ull; const ull P = 131; const ull N = 1e5+10; ull powP[N],H[N]; int n,m; char str[N]; void init(){ powP[0]=1; for(int i=1;i<=n;i++){ powP[i]=powP[i-1]*P; } } void calH(ull H[],char str[]){ H[0]=str[0]; for(int i=1;i<=n;i++){ H[i]=H[i-1]*P+str[i]; } } ull get(int l,int r){ return H[r]-H[l-1]*powP[r-l+1]; } int main(){ cin>>n>>m; cin>>str+1; init(); calH(H,str); while(m--){ int l1,l2,r1,r2; cin>>l1>>r1>>l2>>r2; if(get(l1,r1)==get(l2,r2)) puts("Yes"); else puts("No"); } return 0; }
KMP算法
https://www.acwing.com/problem/content/description/833/
#include <bits/stdc++.h> using namespace std; const int N=1e6+10; int ne[N]; void getNext(string s,int len){ int j=-1; ne[0]=-1; for(int i=1;i<len;i++){ while(j!=-1&&s[i]!=s[j+1]){ j=ne[j]; } if(s[i]==s[j+1]) j++; ne[i]=j; } } int KMP(string text,string pattern){ int n=text.length(),m=pattern.length(); getNext(pattern,m); int ans=0,j=-1; for(int i=0;i<n;i++){ while(j!=-1&&text[i]!=pattern[j+1]){ j=ne[j]; } if(text[i]==pattern[j+1]) j++; if(j==m-1) ans++,j=ne[j],printf("%d ", i-m+1); } return ans; } int main(){ int n,m; string s1,s2; cin>>n>>s1>>m>>s2; getNext(s1,n); getNext(s2,m); KMP(s2,s1); return 0; }
Manacher(马拉车)算法
最长回文子串 https://www.acwing.com/problem/content/1526/
#include <bits/stdc++.h> using namespace std; int Manacher(string s){ string str="@#"; for(int i=0;i<s.length();i++) str+=s[i],str+='#'; vector<int> p(str.length(),0); /* mx表示某个回文串延伸在最右端半径的下标 id表示这个回文子串最中间位置下标 reslen表示对应在s中的最大子回文串的半径 rescenter表示最大子回文串的中间位置 */ int mx=0,id=0,reslen=0,rescenter=0; for(int i=1;i<str.length();i++){ p[i] = mx>i ? min(p[2*id-i],mx-i):1; while(str[i+p[i]]==str[i-p[i]]) p[i]++; if(mx<i+p[i]) mx=i+p[i],id=i; if(reslen<p[i]) reslen=p[i],rescenter=i; } return reslen-1; } int main(){ ios::sync_with_stdio(0);cin.tie(0); string s;getline(cin,s); cout<<Manacher(s)<<"\n"; return 0; }
后缀数组(SA)+++
https://www.luogu.com.cn/problem/P3809
https://www.acwing.com/problem/content/description/142/
后缀自动机 (SAM)+++
AC 自动机+++
动态规划
背包DP
01背包
int v,w; //v体积,w价值 int f[N]; void solve(){ int n,m;cin>>n>>m; for(int i=1;i<=n;i++){ scanf("%d%d",&v,&w); for(int j=m;j>=v;j--){ f[j]=max(f[j], f[j-v]+w); } } cout<<f[m]<<"\n"; }
完全背包
int v,w; //v体积,w价值 int f[N]; void solve(){ int n,m; cin>>n>>m; for(int i=1;i<=n;i++){ scanf("%d%d",&v,&w); for(int j=v;j<=m;j++){ f[j]=max(f[j],f[j-v]+w); } } cout<<f[m]<<"\n"; }
多重背包
int v,w,s; //v体积 w价值 s数量 int f[N]; void solve(){ int n,m;cin>>n>>m; for(int i=1;i<=n;i++){ cin>>v>>w>>s; for(int j=m;j>=v;j--){ for(int k=1;k<=s&&k*v<=j;k++){ f[j]=max(f[j],f[j-k*v]+w*k); } } } cout<<f[m]<<"\n"; }
多重背包(二进制优化)
int v,w,s; int dp[N]; vector<pii> good; void solve() { int n,V; cin>>n>>V; for(int i=1;i<=n;i++){ cin>>v>>w>>s; for(int k=1;k<=s;k<<=1){ s-=k; good.push_back({v*k,w*k}); } if(s>0) good.push_back({v*s,w*s}); } for(auto t:good) { for(int j=V; j>=t.X; j--) { dp[j]=max(dp[j],dp[j-t.X]+t.Y); } } cout<<dp[V]<<"\n"; }
多重背包(单调队列优化)
int v[N],w[N],s[N];//体积、价值和数量 int f[N],g[N];//g[]为f[i-1][],f[]为f[i][] void solve(){ int n,V;cin>>n>>V; for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>s[i]; for(int i=1;i<=n;i++) { memcpy(g,f,sizeof f); for(int j=0;j<v[i];j++){ //枚举余数 deque<int> q; for (int k=j;k<=V;k+=v[i]){ f[k]=g[k]; if(!q.empty()&&k-s[i]*v[i]>q.front()){ q.pop_front(); } if(!q.empty()){ f[k]=max(f[k],g[q.front()]+(k-q.front())/v[i]*w[i]); } while(!q.empty()&&g[q.back()]-(q.back()-j)/v[i]*w[i]<=g[k]-(k-j)/v[i]*w[i]){ q.pop_back(); } q.push_back(k); } } } cout<<f[V]<<"\n"; }
混合背包
- 表示第 种物品只能用1次;
- 表示第 种物品可以用无限次;
- 表示第 种物品可以使用 次;
int v[N],w[N],s[N],dp[N]; struct good{ int kind; int v,w; }; vector<good> g; void solve() { int n,V; cin>>n>>V; for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>s[i]; for(int i=1;i<=n;i++){ if(s[i]==-1||s[i]==0) g.push_back({s[i],v[i],w[i]}); else{ for(int k=1;k<=s[i];k<<=1){ s[i]-=k; g.push_back({-1,v[i]*k,w[i]*k}); } if(s[i]>0) g.push_back({-1,v[i]*s[i],w[i]*s[i]}); } } for(auto t:g){ if(t.kind==-1){ for(int j=V;j>=t.v;j--){ dp[j]=max(dp[j],dp[j-t.v]+t.w); } } else{ for(int j=t.v;j<=V;j++){ dp[j]=max(dp[j],dp[j-t.v]+t.w); } } } cout<<dp[V]<<"\n"; }
二维费用的背包
int dp[N][N]; void solve(){ int n,V,M;cin>>n>>V>>M; for(int i=1;i<=n;i++){ int v,m,w;cin>>v>>m>>w; for(int j=V;j>=v;j--){ for(int k=M;k>=m;k--){ dp[j][k]=max(dp[j][k],dp[j-v][k-m]+w); } } } cout<<dp[V][M]<<"\n"; }
分组背包
int dp[N],v[N],w[N]; void solve(){ int n,V;cin>>n>>V; for(int i=1;i<=n;i++){ //循环每一组 int s;cin>>s; for(int j=1;j<=s;j++) cin>>v[j]>>w[j]; for(int j=V;j>=0;j--){ //循环背包容量 for(int k=1;k<=s;k++){ //循环该组的每一个物品 if(j>=v[k]) dp[j]=max(dp[j],dp[j-v[k]]+w[k]); } } } cout<<dp[V]<<"\n"; }
有依赖的背包
int n,m,root; int v[N],w[N],dp[N][N]; vector<int> g[N]; void dfs(int u){ for(int i=v[u];i<=m;i++) dp[u][i]=w[u]; for(int x:g[u]){ dfs(x); for(int j=m;j>=v[u];j--){ for(int k=0;k<=j-v[u];k++){ dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[x][k]); } } } } void solve(){ cin>>n>>m; for(int i=1;i<=n;i++){ int fa; cin>>v[i]>>w[i]>>fa; if(~fa) g[fa].push_back(i); else root=i; } dfs(root); cout<<dp[root][m]<<"\n"; }
背包问题求最大价值的方案数
int f[N]; ll cnt[N]; void solve(){ int n,m;cin>>n>>m; for(int i=0;i<=m;i++) cnt[i]=1; for(int i=1;i<=n;i++){ int v,w;cin>>v>>w; for(int j=m;j>=v;j--){ int value=f[j-v]+w; if(value>f[j]){ f[j]=value; cnt[j]=cnt[j-v]; } else if(value==f[j]){ cnt[j]+=cnt[j-v]; cnt[j]%=mod; } } } cout<<cnt[m]<<"\n"; }
背包问题求最大价值字典序最小的具体方案
int n,m; int v[N],w[N],f[N][N]; void print(int i,int j){ if(i==n+1) return; if(j>=v[i]&&f[i][j]==f[i+1][j-v[i]]+w[i]) { cout<<i<<" "; print(i+1,j-v[i]); } else print(i+1,j); } void solve(){ cin>>n>>m; for(int i=1;i<=n;i++) cin>>v[i]>>w[i]; for(int i=n;i>=1;i--){ for(int j=0;j<=m;j++){ if(j>=v[i]) f[i][j]=max(f[i+1][j],f[i+1][j-v[i]]+w[i]); else f[i][j]=f[i+1][j]; } } print(1,m); }