给一个序列\(a_i\),求有多少长度为偶数的区间\([l,r]\)知足\([l,mid]\)的异或和等于\([mid+1,r]\)的异或和。html
等价于询问有多少长度为偶数的区间异或和为\(0\)。数组
只须要两个位置的异或前缀和与下标奇偶性相同便可组成一个合法区间。数据结构
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; int gi(){ int x=0,w=1;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')w=0,ch=getchar(); while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return w?x:-x; } int n;pair<int,int>a[300005];long long ans; int main(){ n=gi(); for(int i=1,s=0;i<=n;++i)s^=gi(),a[i]=make_pair(s,i&1); sort(a,a+n+1); for(int i=0,j=0;i<=n;i=j=j+1){ while(j<n&&a[j+1]==a[i])++j; ans+=1ll*(j-i+1)*(j-i)>>1; } printf("%lld\n",ans);return 0; }
给一个回文串,求将其拆分红最小的段数后按任意顺序拼接起来后获得另外一个不一样的回文串,或判断无解。函数
无解当且仅当全都是同一个字符或者是长度为奇数,除正中间外全都为同一个字符。优化
不然答案至多为\(2\),只须要判断\(1\)是否可行就能够了。ui
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; int gi(){ int x=0,w=1;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')w=0,ch=getchar(); while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return w?x:-x; } const int N=5005; int n,fg=1;char s[N],t[N]; bool check1(){ for(int i=1;i<=n;++i)if(s[i]!=t[i])return 1; return 0; } bool check2(){ for(int i=1;i<=n>>1;++i)if(t[i]!=t[n-i+1])return 0; return 1; } int main(){ scanf("%s",s+1);n=strlen(s+1); for(int i=1;i<=n>>1;++i)if(s[i]!=s[1])fg=0; if(fg)return puts("Impossible"),0; for(int i=1;i<n;++i){ for(int j=1;j<=i;++j)t[n-i+j]=s[j]; for(int j=i+1;j<=n;++j)t[j-i]=s[j]; if(check1()&&check2())return puts("1"),0; } return puts("2"),0; }
有一个分段的一次函数,初始时全为\(0\)。定义一个事件\((t,s)\)为,从\(x=t\)开始,将函数的斜率改为\(s\),直至下一个事件出现为止。如今支持三种操做,一种是给出\(t,s\),加入一个事件\((t,s)\),一种是给出\(t\),删除\(x=t\)上的事件,一种是给出\(l,r,v\),问只考虑定义域\([l,r]\),一个\(f(l)=v\)且斜率为上述事件所描述的分段函数(若\(x=l\)没有事件则认为斜率为\(0\))的第一个零点在哪里,或判断无解。spa
平衡树每一个节点维护区间最小值、区间末尾的值以及一些端点处的下标、斜率之类的便可。指针
#include<cstdio> #include<algorithm> using namespace std; int gi(){ int x=0,w=1;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')w=0,ch=getchar(); while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return w?x:-x; } #define ll long long const int N=1e5+5; int ls[N],rs[N],tim[N],spd[N],tL[N],tR[N],spdR[N],rd[N],tot,rt; ll res[N],mn[N]; void up(int x){ tL[x]=tR[x]=tim[x];spdR[x]=spd[x];mn[x]=res[x]=0; if(ls[x]){ tL[x]=tL[ls[x]]; mn[x]=min(mn[x],mn[ls[x]]); res[x]+=res[ls[x]]+1ll*spdR[ls[x]]*(tim[x]-tR[ls[x]]); mn[x]=min(mn[x],res[x]); } if(rs[x]){ tR[x]=tR[rs[x]];spdR[x]=spdR[rs[x]]; res[x]+=1ll*spd[x]*(tL[rs[x]]-tim[x]); mn[x]=min(mn[x],res[x]+mn[rs[x]]); res[x]+=res[rs[x]]; mn[x]=min(mn[x],res[x]); } } void split(int x,int k,int &a,int &b){ if(!x){a=b=0;return;} if(tim[x]<=k)a=x,split(rs[x],k,rs[a],b),up(a); else b=x,split(ls[x],k,a,ls[b]),up(b); } int merge(int x,int y){ if(!x||!y)return x|y; if(rd[x]<rd[y])return rs[x]=merge(rs[x],y),up(x),x; else return ls[y]=merge(x,ls[y]),up(y),y; } double query(int x,int r,ll v){ if(ls[x]){ if(v+mn[ls[x]]<=0)return query(ls[x],tim[x],v); v+=res[ls[x]]+1ll*spdR[ls[x]]*(tim[x]-tR[ls[x]]); if(v<=0)return tim[x]-1.0*v/spdR[ls[x]]; } if(rs[x]){ v+=1ll*spd[x]*(tL[rs[x]]-tim[x]); if(v<=0)return tL[rs[x]]-1.0*v/spd[x]; if(v+mn[rs[x]]<=0)return query(rs[x],r,v); v+=res[rs[x]]; } v+=1ll*spdR[x]*(r-tR[x]);return r-1.0*v/spdR[x]; } int main(){ int q=gi();while(q--){ int op=gi(); if(op==1){ tim[++tot]=gi();spd[tot]=gi();rd[tot]=rand()*rand(); int x,y;split(rt,tim[tot],x,y); up(tot);rt=merge(x,merge(tot,y)); }else if(op==2){ int t=gi(),x,y,z; split(rt,t,x,z);split(x,t-1,x,y); rt=merge(x,z); }else{ int l=gi(),r=gi(),v=gi(),x,y,z; if(!v){printf("%d\n",l);continue;} split(rt,r,x,z);split(x,l-1,x,y); if(!y||v+min(mn[y],res[y]+1ll*spdR[y]*(r-tR[y]))>0)puts("-1"); else printf("%.6lf\n",query(y,r,v)); rt=merge(x,merge(y,z)); } } return 0; }
将\(n\)个点连成一棵树,每条边的权值在\([1,m]\)内,求使得\(a,b\)两点在树上的距离(边权和)刚好为\(m\)的连边及肯定边权的方案数。rest
枚举\(a,b\)路径上有\(i\)个点(包括\(a,b\)),方案数为“\(n-2\)个点中选\(i-2\)个排列的方案数”\(\times\)“将\(m\)的边权分配到这\(i\)条边上去的方案数”\(\times\)"剩下\(n-i\)个点连成树的方案数"\(\times\)“剩下\(n-i\)条边任意分配权值的方案数”。code
若\(n\)个点已经被分红了\(m\)个连通块,每一个连通块的大小是\(a_i\),那么其生成树的方案数为\(n^{m-2}\prod_{i=1}^ma_i\)。
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; int gi(){ int x=0,w=1;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')w=0,ch=getchar(); while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return w?x:-x; } const int N=2e6+5; const int mod=1e9+7; int n,m,inv[N],jc[N],jcn[N],ans; int fastpow(int a,int b){ int res=1; while(b){if(b&1)res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;} return res; } int C(int n,int m){return 1ll*jc[n]*jcn[m]%mod*jcn[n-m]%mod;} int P(int n,int m){return 1ll*jc[n]*jcn[n-m]%mod;} int main(){ n=gi();m=gi(); inv[1]=jc[0]=jcn[0]=1; for(int i=2;i<N;++i)inv[i]=1ll*inv[mod%i]*(mod-mod/i)%mod; for(int i=1;i<N;++i)jc[i]=1ll*jc[i-1]*i%mod,jcn[i]=1ll*jcn[i-1]*inv[i]%mod; for(int i=2;i<=n&&i<=m+1;++i)ans=(ans+1ll*P(n-2,i-2)*C(m-1,i-2)%mod*(i==n?1:1ll*fastpow(n,n-i-1)*i%mod)%mod*fastpow(m,n-i)%mod)%mod; printf("%d\n",ans);return 0; }
区间乘,单点除(保证整除),区间求和。对一个不是质数的数取模。
线段树维护区间内与模数互质部分的和以及每一个模数包含的质因子的次幂。
#include<cstdio> #include<algorithm> using namespace std; int gi(){ int x=0,w=1;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')w=0,ch=getchar(); while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return w?x:-x; } const int N=1e5+5; const int M=2e6+5; int n,mod,pri[9],tot,pw[9][M],q; void exgcd(int a,int b,int &x,int &y){ if(!b){x=1,y=0;return;} exgcd(b,a%b,y,x),y-=a/b*x; } int getinv(int a){ int x,y;exgcd(a,mod,x,y);return (x%mod+mod)%mod; } struct data{ int sum,s[9]; data(){ sum=0; for(int i=0;i<tot;++i)s[i]=0; } data(int x){ if(!x){ for(int i=0;i<tot;++i)s[i]=M-1; sum=0; }else{ for(int i=0;i<tot;++i){ s[i]=0; while(x%pri[i]==0)++s[i],x/=pri[i]; } sum=x; } } data inv(){ data c;c.sum=getinv(sum); for(int i=0;i<tot;++i)c.s[i]=-s[i]; return c; } int val(){ int res=sum; for(int i=0;i<tot;++i)res=1ll*res*pw[i][s[i]]%mod; return res; } }t[N<<2],tag[N<<2]; data operator + (data a,data b){ data c;int s1=a.sum,s2=b.sum; for(int i=0;i<tot;++i){ c.s[i]=min(a.s[i],b.s[i]); s1=1ll*s1*pw[i][a.s[i]-c.s[i]]%mod; s2=1ll*s2*pw[i][b.s[i]-c.s[i]]%mod; } c.sum=(s1+s2)%mod;return c; } data operator * (data a,data b){ data c;c.sum=1ll*a.sum*b.sum%mod; for(int i=0;i<tot;++i)c.s[i]=a.s[i]+b.s[i]; return c; } void build(int x,int l,int r){ tag[x]=data(1); if(l==r){t[x]=data(gi());return;} int mid=l+r>>1;build(x<<1,l,mid);build(x<<1|1,mid+1,r); t[x]=t[x<<1]+t[x<<1|1]; } void cover(int x,data v){ t[x]=t[x]*v;tag[x]=tag[x]*v; } void down(int x){ cover(x<<1,tag[x]);cover(x<<1|1,tag[x]);tag[x]=data(1); } void modify(int x,int l,int r,int ql,int qr,data v){ if(l>=ql&&r<=qr){cover(x,v);return;} down(x);int mid=l+r>>1; if(ql<=mid)modify(x<<1,l,mid,ql,qr,v); if(qr>mid)modify(x<<1|1,mid+1,r,ql,qr,v); t[x]=t[x<<1]+t[x<<1|1]; } data query(int x,int l,int r,int ql,int qr){ if(l>=ql&&r<=qr)return t[x]; down(x);int mid=l+r>>1;data res=data(0); if(ql<=mid)res=res+query(x<<1,l,mid,ql,qr); if(qr>mid)res=res+query(x<<1|1,mid+1,r,ql,qr); return res; } int main(){ n=gi();mod=gi();int x=mod; for(int i=2;i*i<=x;++i) if(x%i==0){ pri[tot++]=i; while(x%i==0)x/=i; } if(x>1)pri[tot++]=x; for(int i=0;i<tot;++i) for(int j=pw[i][0]=1;j<M;++j) pw[i][j]=1ll*pw[i][j-1]*pri[i]%mod; build(1,1,n);q=gi();while(q--){ int op=gi(),x=gi(),y=gi(); if(op==1)modify(1,1,n,x,y,data(gi())); if(op==2)modify(1,1,n,x,x,data(y).inv()); if(op==3)printf("%d\n",query(1,1,n,x,y).val()); } return 0; }
一个\(n\times m\)的网格图,每一个格子上有一个数字,它们构成一个\(n\times m\)的排列。求有多少个区间\([l,r]\)知足权值在这个区间内的全部点在网格图上造成一棵树(一个连通块、不包含环)。
一棵树包含两个条件:不成环,且连通块个数为\(1\)。
首先不成环的限制能够用\(LCT+\)单调指针解决。
如今要求连通块个数为\(1\),因为构成一棵树,那么连通块个数就等于点数减边数。因为点数已知,因此只须要维护区间边数就好了。
稍加转化能够变成线段树区间加区间求\(1\)的个数(等价于求最小值及其个数)的操做。
#include<cstdio> #include<algorithm> using namespace std; int gi(){ int x=0,w=1;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')w=0,ch=getchar(); while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return w?x:-x; } #define pi pair<int,int> const int N=2e5+5; int n,m,tot,f[1005][1005],px[N],py[N],fa[N],ch[2][N],rev[N]; int L=1,R=1,dx[]={1,0,-1,0},dy[]={0,1,0,-1},tmp[10],tag[N<<2]; pi sum[N<<2];long long ans=1; bool son(int x){return x==ch[1][fa[x]];} bool isroot(int x){return x!=ch[0][fa[x]]&&x!=ch[1][fa[x]];} void rotate(int x){ int y=fa[x],z=fa[y],c=son(x); ch[c][y]=ch[c^1][x];if(ch[c][y])fa[ch[c][y]]=y; fa[x]=z;if(!isroot(y))ch[son(y)][z]=x; ch[c^1][x]=y;fa[y]=x; } void rever(int x){swap(ch[0][x],ch[1][x]);rev[x]^=1;} void alldown(int x){ if(!isroot(x))alldown(fa[x]); if(rev[x])rever(ch[0][x]),rever(ch[1][x]),rev[x]=0; } void splay(int x){ alldown(x); for(int y=fa[x];!isroot(x);rotate(x),y=fa[x]) if(!isroot(y))son(x)^son(y)?rotate(x):rotate(y); } void access(int x){for(int y=0;x;y=x,x=fa[x])splay(x),ch[1][x]=y;} void makeroot(int x){access(x);splay(x);rever(x);} int findroot(int x){access(x);splay(x);while(ch[0][x])x=ch[0][x];splay(x);return x;} void split(int x,int y){makeroot(x);access(y);splay(y);} void link(int x,int y){makeroot(x);fa[x]=y;} void cut(int x,int y){split(x,y);fa[x]=ch[0][y]=0;} bool check(){ int len=0; for(int d=0;d<4;++d){ int x=px[R+1]+dx[d],y=py[R+1]+dy[d]; if(!x||x==n+1||!y||y==m+1||f[x][y]<L||f[x][y]>R)continue; tmp[++len]=findroot(f[x][y]); } sort(tmp+1,tmp+len+1); for(int i=1;i<len;++i)if(tmp[i]==tmp[i+1])return false; return true; } pi operator+(pi a,pi b){ pi c;c.first=min(a.first,b.first); if(c.first==a.first)c.second+=a.second; if(c.first==b.first)c.second+=b.second; return c; } void build(int x,int l,int r){ if(l==r){sum[x]=make_pair(0,1);return;} int mid=l+r>>1;build(x<<1,l,mid);build(x<<1|1,mid+1,r); sum[x]=sum[x<<1]+sum[x<<1|1]; } void cover(int x,int v){sum[x].first+=v;tag[x]+=v;} void down(int x){ if(!tag[x])return; cover(x<<1,tag[x]);cover(x<<1|1,tag[x]);tag[x]=0; } void modify(int x,int l,int r,int ql,int qr,int v){ if(l>=ql&&r<=qr){cover(x,v);return;} down(x);int mid=l+r>>1; if(ql<=mid)modify(x<<1,l,mid,ql,qr,v); if(qr>mid)modify(x<<1|1,mid+1,r,ql,qr,v); sum[x]=sum[x<<1]+sum[x<<1|1]; } pi query(int x,int l,int r,int ql,int qr){ if(l>=ql&&r<=qr)return sum[x]; down(x);int mid=l+r>>1;pi res=make_pair(1<<30,0); if(ql<=mid)res=res+query(x<<1,l,mid,ql,qr); if(qr>mid)res=res+query(x<<1|1,mid+1,r,ql,qr); return res; } int main(){ n=gi();m=gi();tot=n*m; for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) f[i][j]=gi(),px[f[i][j]]=i,py[f[i][j]]=j; build(1,1,tot);modify(1,1,tot,1,1,1); while(R<tot){ while(L<R&&!check()){ for(int d=0;d<4;++d){ int x=px[L]+dx[d],y=py[L]+dy[d]; if(!x||x==n+1||!y||y==m+1||f[x][y]<L||f[x][y]>R)continue; cut(L,f[x][y]); } ++L; } ++R;modify(1,1,tot,L,R,1); for(int d=0;d<4;++d){ int x=px[R]+dx[d],y=py[R]+dy[d]; if(!x||x==n+1||!y||y==m+1||f[x][y]<L||f[x][y]>R)continue; link(R,f[x][y]);modify(1,1,tot,L,f[x][y],-1); } pi res=query(1,1,tot,L,R); ans+=res.first==1?res.second:0; } printf("%lld\n",ans);return 0; }
\(n\)个站台顺次连成一个环,每一个站台上都有若干待运出的糖果,每一个糖果被指定了要运往\(b_i\)号站台。有一辆小火车沿着站台顺时针方向走,每到一个站台时能够卸任意数量的糖果但只能装至多一个糖果。求火车从每一个站台出发将全部糖果送到指定地点的最小花费。
#include<cstdio> #include<algorithm> #include<vector> using namespace std; int gi(){ int x=0,w=1;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')w=0,ch=getchar(); while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return w?x:-x; } const int N=5005; int n,m,ans[N];vector<int>E[N]; int main(){ n=gi();m=gi(); for(int i=1,a,b;i<=m;++i)a=gi(),b=gi(),E[a].push_back(b); for(int i=1;i<=n;++i) if(E[i].size()){ int mn=1<<30; for(int x:E[i])mn=min(mn,(x+n-i)%n); ans[i]=(int)(E[i].size()-1)*n+mn; }else ans[i]=-1<<30; for(int i=1;i<=n;++i){ int res=0; for(int j=1;j<=n;++j)res=max(res,ans[j]+(j+n-i)%n); printf("%d ",res); } puts("");return 0; }
每一个站台选一个最近的糖果留给最后一次走,每次求答案时扫一遍全部车站便可。
给一个数组,求\(\max_{1\le l \le r\le n}\{(r-l+1)\sum_{i=l}^ra_i\}\)。
有一份代码直接求最大子段和再乘上区间答案后输出。你须要构造数据将这份代码卡掉。
由于未知量不少因此构造方法也有不少。
一种方法是,将序列构造为\(\{-1,x\}\),这样错误的代码会输出\(x\)而正确结果应该是\(2(x-1)\)。\(x\)部分的长度能够是任意的。
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; int gi(){ int x=0,w=1;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')w=0,ch=getchar(); while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return w?x:-x; } const int N=2000; int main(){ int k=gi()+N; printf("%d\n-1 ",N); for(int i=2;i<=N;++i)printf("%d ",min(k,1000000)),k-=min(k,1000000); return 0; }
有一个长度为\(n\)的\(01\)串,定义一个\(01\)串的划分为将这个\(01\)串拆成若干长度不超过\(4\)的小段(其中有四种长度为\(4\)的串不能选)的方案数。对于每个前缀,求这个前缀中全部本质不一样子串的划分数之和模\(10^9+7\)。
要求本质不一样?那就对于每种本质不一样的子串,在其第一次出现的位置上计算贡献就好咯。
先\(O(n^2)dp\)一下求出任意区间划分的方案数,再拉个\(SAM\)随便搞搞便可。
#include<cstdio> #include<algorithm> #include<cstring> #include<vector> using namespace std; int gi(){ int x=0,w=1;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')w=0,ch=getchar(); while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return w?x:-x; } const int N=3005; const int M=1e4+5; const int mod=1e9+7; int n,s[N],L[M],fa[M],len[M],tr[M][2],tot=1,lst=1,f[N][N],ans[N]; vector<int>E[M]; inline void add(int &x,int y){x+=y;x>=mod?x-=mod:x;} void extend(int c){ int u=++tot,v=lst;len[u]=len[v]+1;lst=u; while(v&&!tr[v][c])tr[v][c]=u,v=fa[v]; if(!v)fa[u]=1; else{ int x=tr[v][c]; if(len[x]==len[v]+1)fa[u]=x; else{ int y=++tot; tr[y][0]=tr[x][0];tr[y][1]=tr[x][1]; fa[y]=fa[x];fa[x]=fa[u]=y;len[y]=len[v]+1; while(v&&tr[v][c]==x)tr[v][c]=y,v=fa[v]; } } } void dfs(int u){ for(int v:E[u])dfs(v),L[u]=min(L[u],L[v]); for(int i=len[fa[u]]+1;i<=len[u];++i)add(ans[L[u]],f[L[u]-i+1][L[u]]); } bool check(int i){ if(s[i-3]==0&&s[i-2]==0&&s[i-1]==1&&s[i]==1)return false; if(s[i-3]==0&&s[i-2]==1&&s[i-1]==0&&s[i]==1)return false; if(s[i-3]==1&&s[i-2]==1&&s[i-1]==1&&s[i]==0)return false; if(s[i-3]==1&&s[i-2]==1&&s[i-1]==1&&s[i]==1)return false; return true; } int main(){ n=gi();memset(L,63,sizeof(L)); for(int i=1;i<=n;++i)s[i]=gi(),extend(s[i]),L[lst]=i; for(int i=1;i<=n;++i){ f[i][i-1]=1; for(int j=i;j<=n;++j){ f[i][j]=f[i][j-1]; if(j>=i+1)add(f[i][j],f[i][j-2]); if(j>=i+2)add(f[i][j],f[i][j-3]); if(j>=i+3&&check(j))add(f[i][j],f[i][j-4]); } } for(int i=2;i<=tot;++i)E[fa[i]].push_back(i); dfs(1); for(int i=1;i<=n;++i)add(ans[i],ans[i-1]); for(int i=1;i<=n;++i)printf("%d\n",ans[i]); return 0; }
求将\(\{a_i\}\)分红若干段使每一段内都有刚好\(k\)个数出现了刚好一次的方案数模\(998244353\)。
右端点从左往右扫,维护\(pre_i\)表示\(i\)前面最近的一个与\(a_i\)相等数的位置(没有则为\(0\)),每次将\([pre_i+1,i]\)这段区间\(+1\),将\([pre_{pre_i+1},pre_i]\)这段区间\(-1\)(若是\(pre_i\neq0\)的话),而后只要查前缀全部刚好等于\(k\)的位置的\(dp\)值之和就好了。
发现根本不会用传统数据结构维护。因而考虑分块。而后就作完了。
值得注意的一点块内的权值范围是\(O(\sqrt n)\)级别的。
#include<cstdio> #include<algorithm> #include<vector> using namespace std; int gi(){ int x=0,w=1;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')w=0,ch=getchar(); while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return w?x:-x; } const int N=1e5+5; const int B=350; const int mod=998244353; int n,k,bl[N],L[B],R[B],tag[B],mn[B],mx[B],pre[N],lst[N],f[N],num[N]; vector<int>ans[B]; inline void add(int &x,int y){x+=y;x>=mod?x-=mod:x;} void rebuild(int x){ mn[x]=1<<30;mx[x]=-1<<30; for(int i=L[x];i<=R[x];++i){ num[i]+=tag[x]; mn[x]=min(mn[x],num[i]);mx[x]=max(mx[x],num[i]); } tag[x]=0;ans[x].clear();ans[x].resize(mx[x]-mn[x]+1); for(int i=L[x];i<=R[x];++i)add(ans[x][num[i]-mn[x]],f[i-1]); for(int i=1;i<=mx[x]-mn[x];++i)add(ans[x][i],ans[x][i-1]); } int cal(int x){ int t=k-tag[x];if(t<mn[x])return 0; return ans[x][min(t-mn[x],mx[x]-mn[x])]; } void modify(int l,int ed,int v){ while(l<=ed){ int x=bl[l],r=min(R[x],ed); if(l==L[x]&&r==R[x])tag[x]+=v; else{ for(int i=l;i<=r;++i)num[i]+=v; rebuild(x); } l=r+1; } } int query(int l,int ed){ int res=0; while(l<=ed){ int x=bl[l],r=min(R[x],ed); if(l==L[x]&&r==R[x])add(res,cal(x)); else{ rebuild(x); for(int i=l;i<=r;++i)if(num[i]<=k)add(res,f[i-1]); } l=r+1; } return res; } int main(){ n=gi();k=gi();f[0]=1; for(int i=1;i<=n;++i){ bl[i]=(i-1)/B+1; if(!L[bl[i]])L[bl[i]]=i;R[bl[i]]=i; } rebuild(1); for(int i=1;i<=n;++i){ int x=gi();pre[i]=lst[x];lst[x]=i; modify(pre[i]+1,i,1); if(pre[i])modify(pre[pre[i]]+1,pre[i],-1); f[i]=query(1,i);if(i<n)rebuild(bl[i+1]); } printf("%d\n",f[n]);return 0; }
交互题。
有一棵树。你每次能够给交互库两个点集\(S,T\)和一个点\(x\),表示询问有多少对\((s,t),s\in S,t\in T\),知足\(x\)在\((s,t)\)的路径上。你须要还原出这棵树的形态。
\(n\le500\),询问次数不超过\(11111\)次。
令\(S=\{1\},T=\{2,3,...,n\},x=i(i\in[2,n])\),便可询问出以\(1\)为根时\(i\)号点的子树大小。
将全部点按照子树大小排序,从小到大加入一个点集\(P\)。每次将点\(x\)加入点集前,\(x\)的全部直接儿子必定都在点集里,因此能够依次二分找到每一个儿子(二分找到点集中的第一个儿子,将其删去,并重复此过程直至没有儿子),最后将其加入点集\(P\)。
这样的询问复杂度是\(O(n(\log n+2))\)的。
#include<cstdio> #include<algorithm> #include<vector> using namespace std; int gi(){ int x=0,w=1;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')w=0,ch=getchar(); while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return w?x:-x; } #define pb push_back int query(vector<int>T,int v){ if(!T.size())return 0; printf("1\n1\n%d\n",(int)T.size()); for(int x:T)printf("%d ",x);printf("\n%d\n",v); fflush(stdout);return gi(); } int sz[505];vector<int>V,S,E[505]; bool cmp(int i,int j){return sz[i]<sz[j];} int main(){ int n=gi(); for(int i=2;i<=n;++i)V.pb(i); for(int i=2;i<=n;++i)sz[i]=query(V,i); sort(V.begin(),V.end(),cmp); for(int x:V){ int k=query(S,x); while(k--){ int l=0,r=S.size()-2,res=r+1; while(l<=r){ int mid=l+r>>1; vector<int>tmp; for(int i=0;i<=mid;++i)tmp.pb(S[i]); if(query(tmp,x))res=mid,r=mid-1; else l=mid+1; } E[x].pb(S[res]);S.erase(S.begin()+res); } S.pb(x); } for(int x:S)E[1].pb(x); puts("ANSWER"); for(int i=1;i<=n;++i)for(int v:E[i])printf("%d %d\n",i,v); return 0; }
一条长度为\(m\)的彩带,每一个位置上有个颜色\(a_i\),你能够删掉彩带上的若干位置,但不能改变原有的相对顺序,剩下的部分会被从前日后每\(k\)个一块儿被切成一段,最后不足\(k\)就丢掉无论。你须要保证最终可以切出至少\(n\)段,且至少存在一段知足:给定可重集\(\{b_i\},|\{b_i\}|=s\),要求这个可重集是这一段内的颜色集合(可重集)的子集。
考虑求出一些区间知足给定集合是这个区间的颜色集合的子集。枚举右端点,左端点显然单调不降,因此只要用两个单调指针扫一遍并构造方案就好了。
#include<cstdio> #include<algorithm> using namespace std; int gi(){ int x=0,w=1;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')w=0,ch=getchar(); while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return w?x:-x; } const int N=5e5+5; int m,k,n,s,a[N],b[N],c[N],tot; int main(){ m=gi();k=gi();n=gi();s=gi(); for(int i=1;i<=m;++i)a[i]=gi(); for(int i=1;i<=s;++i)++b[gi()]; for(int i=1;i<N;++i)if(b[i])++tot; for(int r=1,l=1;r<=m;++r){ ++c[a[r]];tot-=(c[a[r]]==b[a[r]]); while(l<=m&&r-l+1>k&&c[a[l]]>b[a[l]])--c[a[l]],++l; if(!tot&&r-l+1>=k&&(l-1)/k+(m-r)/k+1>=n){ printf("%d\n",(l-1)%k+r-l+1-k); for(int i=1;i<=(l-1)%k;++i)printf("%d ",i); for(int i=l,j=0;i<=r;++i){ if(c[a[i]]>b[a[i]]&&j<r-l+1-k)printf("%d ",i),++j; --c[a[i]]; } puts("");return 0; } } puts("-1");return 0; }
有一个长度为\(n\)的字符集大小为\(0-9\)的字符串,每次能够选择相邻的两个位置同时\(+1\)或\(-1\),要求\(0\)不能被\(-1\),\(9\)不能被\(+1\),须要经过一系列操做使\(A\)串变成\(B\)串。求最小操做数并输出前\(10^5\)步操做。
在无论\(0\)和\(9\)的限制下求出的最小操做步数就是前一问答案。
第二问能够直接从前日后每次操做最靠前的且可以被操做的位置。
#include<cstdio> #include<algorithm> using namespace std; int gi(){ int x=0,w=1;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')w=0,ch=getchar(); while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return w?x:-x; } const int N=1e5+5; int n,s[N];char a[N],b[N];long long ans; void work(int x,int y){ printf("%d %d\n",x,y);a[x]+=y;a[x+1]+=y; if(!--ans)exit(0); } void dfs(int x,int y){ if(a[x+1]+y<'0'||a[x+1]+y>'9')dfs(x+1,-y); work(x,y); } int main(){ n=gi();scanf("%s%s",a+1,b+1); for(int i=1;i<n;++i)s[i]=b[i]-a[i]-s[i-1]; if(s[n-1]!=b[n]-a[n])return puts("-1"),0; for(int i=1;i<n;++i)ans+=abs(s[i]); printf("%lld\n",ans);ans=min(ans,100000ll); for(int i=1;i<n;++i)while(a[i]!=b[i])dfs(i,a[i]<b[i]?1:-1); }
有一个长度为\(n\)的字符串,你须要将其划分为若干段,每段须要知足:要么长度为\(1\),此时须要付出\(a\)的代价;要么这一段是前面全部段顺序连接起来造成的串的子串,此时须要付出\(b\)的代价。求划分的最小代价。
有一个很显然的\(dp\),能够每次\(O(n)\)枚举转移点再\(O(n)\)判断是否知足第二种要求便可作到\(O(n^3)\)。
发现知足第二种要求的转移点必定是一段连续后缀。进一步的,这段后缀的起始位置是单调的,因此只须要用单调指针维护这个位置,再用单调队列优化转移就好了。
#include<cstdio> #include<algorithm> using namespace std; int gi(){ int x=0,w=1;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')w=0,ch=getchar(); while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return w?x:-x; } #define ull unsigned long long const int N=5005; const ull base=10007; int n,a,b,q[N],hd=1,tl,f[N];char s[N];ull hsh[N],pw[N]; ull cal(int l,int r){return hsh[r]-hsh[l-1]*pw[r-l+1];} bool check(int l,int r,int x,int y){ if(y-x>r-l)return false;ull val=cal(x,y); for(int i=l;i+y-x<=r;++i)if(cal(i,i+y-x)==val)return true; return false; } int main(){ n=gi();a=gi();b=gi();scanf("%s",s+1); for(int i=1;i<=n;++i)hsh[i]=hsh[i-1]*base+s[i]; for(int i=pw[0]=1;i<=n;++i)pw[i]=pw[i-1]*base; for(int i=1,j=1;i<=n;++i){ f[i]=f[i-1]+a; while(j<i&&!check(1,j,j+1,i))++j; while(hd<=tl&&q[hd]<j)++hd; if(hd<=tl)f[i]=min(f[i],f[q[hd]]+b); while(hd<=tl&&f[i]<=f[q[tl]])--tl; q[++tl]=i; } printf("%d\n",f[n]);return 0; }
给定一棵树,每一个点有个选择的代价,须要用最小的代价选出一些点,使得对于每一个叶子,它被选取的祖先的集合非空且不互相同。求最小代价,并求每一个点是否可能出如今一种最优方案中。
显然若是有\(m\)个叶子就必定会刚好选\(m\)个点。而若是一棵子树内有\(x\)个叶子,这棵子树内必定会被选\(x-1\)或\(x\)个点。
因此直接记\(f_u,g_u\)表示子树里选了\(x/x-1\)个点的最小代价,转移为
\(g_u=\min_v\{\sum_{w\neq v}f_w+g_v\},f_u=\min(\sum_{v}f_v,g_u+c_u)\)
最小代价即为\(f_1\)。构造方案能够对每一个\(dp\)状态记录其是否为最优,倒着还原一遍\(dp\)过程便可。
#include<cstdio> #include<algorithm> #include<vector> using namespace std; int gi(){ int x=0,w=1;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')w=0,ch=getchar(); while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return w?x:-x; } #define ll long long const int N=2e5+5; const ll inf=1ll<<60; int n,mrk_f[N],mrk_g[N],s[N],m;ll c[N],f[N],g[N]; vector<int>E[N]; void dfs1(int u,int fa){ if(fa&&E[u].size()==1){f[u]=c[u];return;} ll tmp=inf; for(int v:E[u]) if(v^fa){ dfs1(v,u); f[u]+=f[v];g[u]+=f[v];tmp=min(tmp,g[v]-f[v]); } g[u]+=tmp;f[u]=min(f[u],g[u]+c[u]); } void dfs2(int u,int fa){ if(mrk_f[u]){ if(f[u]==g[u]+c[u])s[++m]=u,mrk_g[u]=1; ll sum=0; for(int v:E[u])if(v^fa)sum+=f[v]; if(sum==f[u]) for(int v:E[u])if(v^fa)mrk_f[v]=1; } if(mrk_g[u]){ ll tmp=inf;int cnt=0; for(int v:E[u])if(v^fa)tmp=min(tmp,g[v]-f[v]); for(int v:E[u])if(v^fa)cnt+=(tmp==g[v]-f[v]); for(int v:E[u])if(v^fa){ if(cnt>1||tmp<g[v]-f[v])mrk_f[v]=1; if(tmp==g[v]-f[v])mrk_g[v]=1; } } for(int v:E[u])if(v^fa)dfs2(v,u); } int main(){ n=gi(); for(int i=1;i<=n;++i)c[i]=gi(); for(int i=1;i<n;++i){ int x=gi(),y=gi(); E[x].push_back(y);E[y].push_back(x); } dfs1(1,0);mrk_f[1]=1;dfs2(1,0);sort(s+1,s+m+1); printf("%lld %d\n",f[1],m); for(int i=1;i<=m;++i)printf("%d ",s[i]); puts("");return 0; }
记\(S(n)\)为\(n\)在十进制下各位数字之和,给出\(a\),求一个\(n\)知足\(S(an)=S(n)/a\)。
从低位向高位依次肯定\(n\)。状态须要记录\(an\)中当前位向上一位进位进位的值以及当前已肯定部分的\(aS(an)-S(n)\)的值。\(bfs\)便可。上界开到\(2k\)左右就能过了。
#include<cstdio> #include<algorithm> #include<queue> using namespace std; int gi(){ int x=0,w=1;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')w=0,ch=getchar(); while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return w?x:-x; } #define pi pair<int,int> #define mk make_pair #define fi first #define se second const int N=2005; int a,n,vis[N][N<<1],s[N*N];pair<pi,int>pre[N][N<<1]; queue<pi>Q; void add(int x,int y,int z){ int nx=(x+z*a)/10,ny=y+(x+z*a)%10*a-z; if(ny<=-N||ny>=N||vis[nx][ny+N])return; Q.push(mk(nx,ny));vis[nx][ny+N]=1;pre[nx][ny+N]=mk(mk(x,y),z); } int main(){ a=gi();for(int i=1;i<10;++i)add(0,0,i); while(!Q.empty()){ int x=Q.front().fi,y=Q.front().se;Q.pop(); if(x==0&&y==0) while(233){ s[n++]=pre[x][y+N].se; int px=pre[x][y+N].fi.fi,py=pre[x][y+N].fi.se; if(px==0&&py==0){ int p=0;while(!s[p])++p; while(p<n)putchar(s[p]+'0'),++p; return 0; } x=px,y=py; } for(int z=0;z<10;++z)add(x,y,z); } puts("-1");return 0; }
小P和小W要互相写信。沿时间轴依次发生了\(n\)个事件,每一个形如小P或小W写个一封信想要寄给对方。每封信有两种可选的寄出方式,一种是花费\(d\)的代价直接传送给对方,另外一种是把信丢给小R同时从小R手中拿走对方给本身的信。时间轴上第\(n+1\)个事件是两人同时去小R那里取信,小R手中每一封信保留每一单位时间须要付出\(c\)的代价。求最小代价。
假设从第\(i\)时刻起小R手中有了一封信。
那么接下来每当小P或小W要连续给对方寄若干封信时,他先去一次小R那里必定不亏。剩下的信就从留给小R和直接传送二者中取代价小的便可。
按时间轴从后往前作,枚举\(i\)计算答案便可。
#include<cstdio> #include<algorithm> using namespace std; int gi(){ int x=0,w=1;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')w=0,ch=getchar(); while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return w?x:-x; } #define ll long long const int N=1e5+5; int n,c,d,a[N];char b[N];ll sum,ans; int main(){ n=gi();c=gi();d=gi();ans=1ll*n*d; for(int i=1;i<=n;++i)a[i]=gi(),b[i]=getchar(); a[n+1]=gi(); for(int i=n,lst;i;--i){ if(b[i]==b[i+1])sum+=min(d,(lst-a[i+1])*c); else lst=a[i+1]; ans=min(ans,1ll*(a[n+1]-a[i])*c+sum+1ll*(i-1)*d); } printf("%lld\n",ans);return 0; }
咕了,能够去看yyb的博客。