給一個序列\(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的博客。