2022“杭电杯”中国大学生算法设计超级联赛(3)
A. Equipment Upgrade
设表示从等级强化至等级的期望花费,则有
根据上式,如果已知,即可计算出的值。但是由于未知,我们无法从前往后进行递推。在这里,设,则
此时和都是可以从前往后递推计算的,且计算过程是卷积的形式,可以用分治+NTT求出,最后根据解出的值即为答案。
时间复杂度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | #include<cstdio> #include<algorithm> using namespace std; typedef long long ll; const int N=262144,K=17,P=998244353,G=3; int Case,n,i,x; int pos[N+5],A[N+5],B[N+5],W[N+5],g[K+5],ng[K+5],inv[N+5],inv2; int p[N+5],invp[N+5],cost[N+5],w[N+5],pre[N+5]; int ea[N+5],eb[N+5],fa[N+5],fb[N+5]; inline int po( int a, int b){ int t=1; for (;b;b>>=1,a=1LL*a*a%P) if (b&1)t=1LL*t*a%P; return t;} inline void NTT( int *a, int n, int t){ for ( int i=1;i<n;i++) if (i<pos[i])swap(a[i],a[pos[i]]); for ( int d=0;(1<<d)<n;d++){ int m=1<<d,m2=m<<1,_w=t==1?g[d]:ng[d]; for ( int i=0;i<n;i+=m2) for ( int w=1,j=0;j<m;j++){ int &A=a[i+j+m],&B=a[i+j],t=1LL*w*A%P; A=B-t; if (A<0)A+=P; B=B+t; if (B>=P)B-=P; w=1LL*w*_w%P; } } if (t==-1) for ( int i=0,j=inv[n];i<n;i++)a[i]=1LL*a[i]*j%P; } void solve( int l, int r){ if (l==r){ if (l){ ea[l+1]=((ea[l]-(1LL-p[l])*fa[l]%P*pre[l])%P+P)*invp[l]%P; eb[l+1]=((eb[l]-cost[l]-(1LL-p[l])*fb[l]%P*pre[l])%P+P)*invp[l]%P; } return ; } int mid=(l+r)>>1; solve(l,mid); int k=1; while (k<r-l+1)k<<=1; for ( int i=0;i<k;i++)A[i]=B[i]=W[i]=0; for ( int i=l;i<=mid;i++)A[i-l]=ea[i],B[i-l]=eb[i]; for ( int i=1;i<=r-l;i++)W[i]=w[i]; int j=__builtin_ctz(k)-1; for ( int i=0;i<k;i++)pos[i]=pos[i>>1]>>1|((i&1)<<j); NTT(A,k,1); NTT(B,k,1); NTT(W,k,1); for ( int i=0;i<k;i++)A[i]=1LL*A[i]*W[i]%P,B[i]=1LL*B[i]*W[i]%P; NTT(A,k,-1); NTT(B,k,-1); for ( int i=mid+1;i<=r;i++){ fa[i]=(fa[i]+A[i-l])%P; fb[i]=(fb[i]+B[i-l])%P; } solve(mid+1,r); } int main(){ for (g[K]=po(G,(P-1)/N),ng[K]=po(g[K],P-2),i=K-1;~i;i--)g[i]=1LL*g[i+1]*g[i+1]%P,ng[i]=1LL*ng[i+1]*ng[i+1]%P; for (inv[1]=1,i=2;i<=N;i++)inv[i]=1LL*(P-inv[P%i])*(P/i)%P;inv2=inv[2]; scanf ( "%d" ,&Case); while (Case--){ scanf ( "%d" ,&n); for (i=0;i<n;i++){ scanf ( "%d%d" ,&x,&cost[i]); p[i]=1LL*x*inv[100]%P; invp[i]=100LL*inv[x]%P; } for (i=1;i<n;i++){ scanf ( "%d" ,&w[i]); pre[i]=(pre[i-1]+w[i])%P; } for (i=1;i<n;i++)pre[i]=po(pre[i],P-2); for (i=0;i<=n;i++)fa[i]=fb[i]=0; ea[0]=1,eb[0]=0; ea[1]=1,eb[1]=(P-cost[0])%P; solve(0,n-1); int ans=1LL*(P-eb[n])*po(ea[n],P-2)%P; printf ( "%d\n" ,(ans+P)%P); } } |
B. Boss Rush
二分答案,转化为判断帧内能否打败BOSS,即求出帧内能打出的最高伤害,判断是否大于等于。
从前往后依次发动若干个技能,则下一个技能可以发动的时刻等于之前发动过的技能的演出时间之和,因此只和之前发动过哪些技能有关。设表示发动了 集合的技能,在帧内最多能结算多少伤害,枚举不在中的某个技能作为下一个技能进行转移,由于技能发动时刻已知,因此可以计算出在帧内下一个技能可以结算多少伤害。
时间复杂度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | #include<cstdio> typedef long long ll; const int N=18,M=100005; int Case,n,i,j,S,t[N],d[N],l,r,ans,mid,sum[(1<<N)+1]; ll hp,f[(1<<N)+1],dmg[N][M]; inline void up(ll&a,ll b){a<b?(a=b):0;} bool check( int T){ int S,i; for (S=0;S<1<<n;S++)f[S]=-1; f[0]=0; for (S=0;S<1<<n;S++){ ll w=f[S]; if (w<0) continue ; if (w>=hp) return 1; int cur=sum[S]; if (cur>T) continue ; for (i=0;i<n;i++) if (!(S>>i&1)){ if (cur+d[i]-1<=T)up(f[S|(1<<i)],w+dmg[i][d[i]-1]); else up(f[S|(1<<i)],w+dmg[i][T-cur]); } } return 0; } int main(){ scanf ( "%d" ,&Case); while (Case--){ scanf ( "%d%lld" ,&n,&hp); ans=-1,l=r=0; for (i=0;i<n;i++){ scanf ( "%d%d" ,&t[i],&d[i]); r+=t[i]+d[i]-1; for (j=0;j<d[i];j++) scanf ( "%lld" ,&dmg[i][j]); for (j=1;j<d[i];j++)dmg[i][j]+=dmg[i][j-1]; } for (S=1;S<1<<n;S++)sum[S]=sum[S-(S&-S)]+t[__builtin_ctz(S&-S)]; while (l<=r){ mid=(l+r)>>1; if (check(mid))r=(ans=mid)-1; else l=mid+1; } printf ( "%d\n" ,ans); } } |
C. Cyber Language
签到模拟,遍历每个字符,如果一个字符是小写字母且前一个字符是空格或者它是第一个字符,那么把它转大写输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #include<cstdio> #include<cstring> const int N=1000005; int Case,n,i; char s[N]; int main(){ fgets (s,N,stdin); sscanf (s, "%d" ,&Case); while (Case--){ fgets (s,N,stdin); n= strlen (s); for (i=0;i<n;i++) if (s[i]>= 'a' &&s[i]<= 'z' ) if (!i||(i>0&&s[i-1]== ' ' )) putchar (s[i]- 'a' + 'A' ); puts ( "" ); } } |
D. Divide the Sweets
令表示集合的箱子的糖果数之和,表示把集合的箱子分给个孩子的最小平方和,则,其中是的子集。直接枚举子集转移的时间复杂度为,不能接受。
方便起见,令,则
将表示集合的位二进制数拆成前位和后位来考虑。枚举转移中的前位,再枚举的所有超集作为的前半部分,然后枚举的后位,此时,我们需要找到一个的子集作为的后半部分(即),满足最小。这是经典斜率优化的形式,对于每个,将其看作直线,若能得到子集的所有直线形成的凸壳,则在凸壳上询问时的最小值即可完成状态转移。假设已经有了凸壳,为了保证询问的均摊复杂度为,需要按照递增(或递减)的顺序去询问,由于,所有的对应的都相等,因此按照递增(或递减)的顺序去枚举即可保证询问坐标的单调性。
剩下的问题是如何得到子集的凸壳。类似于高维前缀和,对于每个状态保存其凸壳。一开始对于每个状态,它的凸壳大小为,对应。接下来依次枚举后半部分的每一位,假设当前枚举到了第位,对于一个集合,如果它的第位为,则它缺少 B xor (1<<x) 的信息,暴力将 B xor (1<<x) 和的凸壳二路归并成一个,作为的新凸壳。最坏情况下,每次合并后凸壳大小翻倍,总代价为最终凸壳大小的两倍,所有状态的凸壳大小不超过子集数,因此预处理子集凸壳的时间复杂度为。
总时间复杂度分析:一共要进行轮,每轮需要枚举个,然后枚举它的超集,总计,每个又需要接着枚举个后半部分,然后花费均摊的代价在子集的凸壳上进行询问,这部分每轮的总时间复杂度为。而枚举完后,又需要支付的代价预处理出所有子集的凸壳,这部分每轮的总时间复杂度也为。因此,最终得到总时间复杂度为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | #include<cstdio> #include<algorithm> #include<vector> using namespace std; typedef long long ll; const int N=20,M=1<<N,K=1<<(N/2),P=998244353; const ll inf=1LL<<60; int Case,n,m,fir,sec,cq,i,j,S,T,A,B,a[N],cnt[M+1],sum[M+1],ans[N+1],q[K+1]; ll pre[M+1],cur[M+1],base[M+1]; int st[K+1],en[K+1],head,tail; struct E{ int k;ll b;E(){}E( int _k,ll _b){k=_k,b=_b;}}h[120005]; inline bool cmp( int x, int y){ return sum[x]<sum[y];} inline void up(ll&a,ll b){a>b?(a=b):0;} inline void insert( const E&e){ while (head<tail&&(h[tail-1].b-h[tail].b)*(e.k-h[tail].k)<=(h[tail].b-e.b)*(h[tail].k-h[tail-1].k))tail--; h[++tail]=e; } inline void merge( int A, int B, int C, int D){ head=tail+1; while (A<=B&&C<=D){ if (h[A].k==h[C].k){ insert(h[A].b<h[C].b?h[A]:h[C]); A++,C++; continue ; } insert(h[A].k<h[C].k?h[A++]:h[C++]); } while (A<=B)insert(h[A++]); while (C<=D)insert(h[C++]); } int main(){ scanf ( "%d" ,&Case); while (Case--){ scanf ( "%d%d" ,&n,&m); for (i=0;i<n;i++) scanf ( "%d" ,&a[i]); for (S=1;S<1<<n;S++){ T=S&-S; cnt[S]=cnt[S-T]+1; i=__builtin_ctz(T); sum[S]=sum[S-T]+a[i]; base[S]=base[S-T]+1LL*a[i]*a[i]; } for (i=1;i<=n;i++)ans[i]=0; cur[0]=inf; for (S=1;S<1<<n;S++){ cur[S]=1LL*sum[S]*sum[S]; ans[1]=(ans[1]+cur[S])%P; } fir=n/2; sec=n-fir; for (i=2;i<=m;i++){ for (S=1;S<1<<n;S++){ pre[S]=cur[S]; cur[S]=cnt[S]<=i?base[S]:inf; } for (A=0;A<1<<fir;A++){ cq=0; for (T=A;;T=(T+1)|A){ q[cq++]=T<<sec; if (T==(1<<fir)-1) break ; } sort(q,q+cq,cmp); tail=0; for (B=0;B<1<<sec;B++){ st[B]=tail+1; S=A<<sec|B; if (cnt[S]>=i-1)h[++tail]=E(sum[S],pre[S]+1LL*sum[S]*sum[S]); en[B]=tail; } for (j=0;j<sec;j++) for (B=0;B<1<<sec;B++) if (B>>j&1){ int l=tail+1; merge(st[B],en[B],st[B^(1<<j)],en[B^(1<<j)]); st[B]=l; en[B]=tail; } for (B=0;B<1<<sec;B++){ int l=st[B],r=en[B]; if (l>r) continue ; for (j=0;j<cq;j++){ S=q[j]|B; if (cnt[S]<=i) continue ; ll x=-2*sum[S]; while (l<r&&h[l].k*x+h[l].b>h[l+1].k*x+h[l+1].b)l++; up(cur[S],h[l].k*x+h[l].b); } } } for (S=1;S<1<<n;S++){ if (cnt[S]>i)cur[S]+=1LL*sum[S]*sum[S]; ans[i]=(ans[i]+cur[S])%P; } } for (i=1;i<=m;i++) printf ( "%d\n" ,ans[i]); } } |
E. Spanning Tree Game
题意即对于每个 (),从数组中选取个边权,从数组中选取个边权,并最大化最小生成树的边权和。
对于一条边,有以下两种情况:
- :拆成两条边和。
- :拆成两条边和。
根据最小生成树的Kruskal算法,将拆后的条边按边权从小到大排序,边权相同时,将类型(或或)为的边排在后面,从前往后依次考虑每条边:
- 若是,则这条边可选可不选,若选了则数组中选取的边数要增加,此时若和不连通,则MST的边权和要增加。
- 若是,则这条边可选可不选,若选了则数组中选取的边数要减少,此时若和不连通,则MST的边权和要增加。
- 若是,则这条边对应的另一条边在此之前已经考虑过。若另一条边选了,则这条边选上不会对MST产生多余的错误贡献;若另一条边没选,则这条边必须要选。因此,此时如果判断出和不连通,则MST的边权和要增加。
上述过程中每条边可能会有两种选择,使得整个图中个点的连通情况不同,由此设计出状态表示考虑了排序后前条边,个点连通性的最小表示为,有条边边权来自数组时,最小生成树边权和的最大值是多少。预处理转移后可以做到转移。
状态数分析:和都是个,有个,其中。
时间复杂度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | #include<cstdio> #include<map> #include<algorithm> using namespace std; typedef unsigned long long ull; const int N=15,M=35,K=21155; int Case,n,m,cnt,ce,base,i,j,k,f[N],v[N],pre[K][M],cur[K][M]; ull pool[K]; map<ull, int >T; struct E{ int x,y,w,t;E(){}E( int _x, int _y, int _w, int _t){x=_x,y=_y,w=_w,t=_t;}}e[M*2]; inline bool cmp( const E&a, const E&b){ if (a.w!=b.w) return a.w<b.w; return (!a.t)<(!b.t); } void dfs( int x, int y){ if (x>n){ ull S=0; for ( int i=1;i<=n;i++)S=S<<4|f[i]; T[S]=++cnt; pool[cnt]=S; return ; } for ( int i=1;i<=y+1;i++){ f[x]=i; dfs(x+1,i>y?i:y); } } inline void up( int &a, int b){a<b?(a=b):0;} inline void clr(){ for ( int i=1;i<=cnt;i++) for ( int j=0;j<=m;j++)cur[i][j]=-1;} inline void nxt(){ for ( int i=1;i<=cnt;i++) for ( int j=0;j<=m;j++)pre[i][j]=cur[i][j];} int main(){ scanf ( "%d" ,&Case); while (Case--){ scanf ( "%d%d" ,&n,&m); ce=cnt=base=0; T.clear(); for (i=1;i<=m;i++){ int x,y,a,b; scanf ( "%d%d%d%d" ,&x,&y,&a,&b); if (a<b){ e[++ce]=E(x,y,a,1); e[++ce]=E(x,y,b,0); } else { base++; e[++ce]=E(x,y,b,-1); e[++ce]=E(x,y,a,0); } } sort(e+1,e+ce+1,cmp); dfs(1,0); clr(); cur[cnt][base]=0; for (i=1;i<=ce;i++){ int x,y,w,t; x=e[i].x,y=e[i].y,w=e[i].w,t=e[i].t; nxt(); clr(); for (j=1;j<=cnt;j++){ ull S=pool[j]; for (k=n;k;k--){ f[k]=S&15; S>>=4; } int o=j,A=f[x],B=f[y],tmp=A==B?0:w; if (A!=B){ int A=f[x],B=f[y]; for (k=1;k<=n;k++) if (f[k]==A)f[k]=B; int now=0; for (k=1;k<=n;k++)v[k]=0; for (k=1;k<=n;k++) if (!v[f[k]])v[f[k]]=++now; S=0; for (k=1;k<=n;k++)S=S<<4|v[f[k]]; o=T[S]; } if (t==0){ for (k=0;k<=m;k++) if (~pre[j][k])up(cur[o][k],pre[j][k]+tmp); } else { for (k=0;k<=m;k++) if (~pre[j][k])up(cur[j][k],pre[j][k]); if (t>0){ for (k=0;k<m;k++) if (~pre[j][k])up(cur[o][k+1],pre[j][k]+tmp); } else { for (k=1;k<=m;k++) if (~pre[j][k])up(cur[o][k-1],pre[j][k]+tmp); } } } } for (i=0;i<=m;i++) printf ( "%d\n" ,cur[1][i]); } } |
F. Dusk Moon
对于一个点集,它的凸包覆盖住了所有点,且最小覆盖圆覆盖住了凸包,因此仅保留凸包的顶点不会影响答案。
由于点的坐标在给定的正方形范围内随机,因此一个点集的凸包的期望顶点数为,使用线段树直接记录区间凸包点集,然后对于个点运行最小圆覆盖算法即可。
时间复杂度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | #include<cstdio> #include<cmath> #include<cstdlib> #include<algorithm> using namespace std; typedef long double ld; const int N=100010,M=262150,K=35; const ld eps=1e-10; int Case,n,m,i,op,x,y,q[N],h[N],vf[M][K],vg[M][K],F[K],G[K]; struct P{ int x,y;}a[N]; struct E{ld x,y;E(){}E(ld _x,ld _y){x=_x,y=_y;}}; inline ld dis( const E&a, const E&b){ return sqrtl((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));} inline E center( const E&x, const E&y, const E&z){ ld a1=y.x-x.x,b1=y.y-x.y, c1=(a1*a1+b1*b1)/2,a2=z.x-x.x, b2=z.y-x.y,c2=(a2*a2+b2*b2)/2, d=a1*b2-a2*b1; return E(x.x+(c1*b2-c2*b1)/d,x.y+(a1*c2-a2*c1)/d); } inline bool cmp0( int x, int y){ if (a[x].x!=a[y].x) return a[x].x<a[y].x; return a[x].y>a[y].y; } inline bool cmp1( int x, int y){ if (a[x].x!=a[y].x) return a[x].x<a[y].x; return a[x].y<a[y].y; } inline void merge0( int *A, int *B, int *C){ int cnt=0,i=0,j=0,t=0; while (A[i]&&B[j])q[cnt++]=cmp0(A[i],B[j])?A[i++]:B[j++]; while (A[i])q[cnt++]=A[i++]; while (B[j])q[cnt++]=B[j++]; for (i=0;i<cnt;i++){ j=q[i]; if (i&&a[j].x==a[q[i-1]].x) continue ; while (t>1&&1LL*(a[h[t]].y-a[h[t-1]].y)*(a[j].x-a[h[t]].x)<=1LL*(a[j].y-a[h[t]].y)*(a[h[t]].x-a[h[t-1]].x))t--; h[++t]=j; } for (i=1;i<=t;i++)C[i-1]=h[i]; C[t]=0; } inline void merge1( int *A, int *B, int *C){ int cnt=0,i=0,j=0,t=0; while (A[i]&&B[j])q[cnt++]=cmp1(A[i],B[j])?A[i++]:B[j++]; while (A[i])q[cnt++]=A[i++]; while (B[j])q[cnt++]=B[j++]; for (i=0;i<cnt;i++){ j=q[i]; if (i&&a[j].x==a[q[i-1]].x) continue ; while (t>1&&1LL*(a[h[t]].y-a[h[t-1]].y)*(a[j].x-a[h[t]].x)>=1LL*(a[j].y-a[h[t]].y)*(a[h[t]].x-a[h[t-1]].x))t--; h[++t]=j; } for (i=1;i<=t;i++)C[i-1]=h[i]; C[t]=0; } inline void up( int x){ merge0(vf[x<<1],vf[x<<1|1],vf[x]); merge1(vg[x<<1],vg[x<<1|1],vg[x]); } void build( int x, int a, int b){ if (a==b){ vf[x][0]=vg[x][0]=a; vf[x][1]=vg[x][1]=0; return ; } int mid=(a+b)>>1; build(x<<1,a,mid),build(x<<1|1,mid+1,b); up(x); } void change( int x, int a, int b, int c){ if (a==b) return ; int mid=(a+b)>>1; if (c<=mid)change(x<<1,a,mid,c); else change(x<<1|1,mid+1,b,c); up(x); } void ask( int x, int a, int b, int c, int d){ if (c<=a&&b<=d){ merge0(F,vf[x],F); merge1(G,vg[x],G); return ; } int mid=(a+b)>>1; if (c<=mid)ask(x<<1,a,mid,c,d); if (d>mid)ask(x<<1|1,mid+1,b,c,d); } inline int cal(){ int n=0,i,j,k; static E b[K*2]; for (i=0;F[i];i++)b[n++]=E(a[F[i]].x,a[F[i]].y); for (i=0;G[i];i++)b[n++]=E(a[G[i]].x,a[G[i]].y); random_shuffle(b,b+n); E O=b[0]; ld R=0; for (i=1;i<n;i++) if (dis(b[i],O)>R+eps) for (O=b[i],R=0,j=0;j<i;j++) if (dis(b[j],O)>R+eps){ O=E((b[i].x+b[j].x)/2,(b[i].y+b[j].y)/2); R=dis(O,b[i]); for (k=0;k<j;k++) if (dis(b[k],O)>R+eps)O=center(b[k],b[j],b[i]),R=dis(O,b[i]); } return ceil (R); } int main(){ scanf ( "%d" ,&Case); while (Case--){ scanf ( "%d%d" ,&n,&m); for (i=1;i<=n;i++) scanf ( "%d%d" ,&a[i].x,&a[i].y); build(1,1,n); while (m--){ scanf ( "%d" ,&op); if (op==1){ scanf ( "%d" ,&x); scanf ( "%d%d" ,&a[x].x,&a[x].y); change(1,1,n,x); } else { scanf ( "%d%d" ,&x,&y); F[0]=G[0]=0; ask(1,1,n,x,y); printf ( "%d\n" ,cal()); } } } } |
G. Shallow Moon
按行为一块,将的矩阵从上到下分为个的块。其中最后一块可能不足行,方便起见下面都忽略这种情况。
由于每块的行数和每个矩形障碍的行数相等,因此每个矩形障碍要么完全属于某一块,要么经过相邻的两块。一个矩形障碍对于某一块的影响只能是下列两种情况之一:
- 块内第列至第列的前若干行是障碍。
- 块内第列至第列的后若干行是障碍。
对于某块中固定的某一列,只需要考虑上述第一种情况中下边界最靠下的限制,以及第二种情况中上边界最靠上的限制,那么这一列要么都是障碍,要么能走的行数是一个连续的区间。假设某一块中有个矩形障碍,那么按照每个障碍的左右边界将这一块的区域从左往右离散成个行数为的区域,每个区域中最多只有一个连续的行区间可以行走。由于所有障碍的列数都为,可以通过单调队列在时间内求出每个区域的可走区间。至此,我们成功地将一个包含个矩形障碍的块的可走区域从左往右划分成了个矩形。
由于每块至少贡献一个区域,而总障碍数为,因此上述方法会得到个矩形区域,当较大时会退化,这是因为很多块一个障碍都没有,此时需要把连续的不含障碍的块合并成一个,则最终我们得到了个矩形区域。
对这个矩形区域建图,每个点代表一个区域,点权为区域的面积,若两个区域相邻则连边,答案即为每个连通块的点权和的平方之和。在这里,连边有以下两种情况:
- 同一块内左右相邻的两个区域需要连边,边数显然为。
- 相邻两块内上下相邻的两个区域需要连边,可以通过双指针实现,边数为相邻两块区域数之和,总边数仍为。
时间复杂度,使用基数排序可以做到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | #include<cstdio> #include<algorithm> using namespace std; typedef unsigned long long ull; const int N=100000+105; int Case,n,M,W,H,i,j,k,x,y; int cp,pool[N*2]; int ce,cb,cnt; struct Block{ int l,r,bl,br,st,en; Block(){} Block( int _l, int _r){l=_l;r=_r;bl=l/W;br=r/W;} }block[N*4]; struct E{ int b,x,y,t; E(){} E( int _b, int _x, int _y, int _t){b=_b;x=_x;y=_y;t=_t;} }e[N*2]; struct Area{ int yl,yr,xl,xr; Area(){} Area( int _yl, int _yr, int _xl, int _xr){yl=_yl,yr=_yr,xl=_xl,xr=_xr;} ull cal(){ return 1ULL*(yr-yl+1)*(xr-xl+1);} }area[N*8]; int f[N*8]; ull sz[N*8],ans; inline bool cmpe( const E&a, const E&b){ return a.b==b.b?a.y<b.y:a.b<b.b;} inline void ext( int x, int y){ pool[++cp]=x/W; e[++ce]=E(x/W,x,y,0); if (x%W==0) return ; x+=W-1; pool[++cp]=x/W; e[++ce]=E(x/W,x,y,1); } int F( int x){ return f[x]==x?x:f[x]=F(f[x]);} inline void merge( int x, int y){ x=F(x),y=F(y); if (x==y) return ; sz[y]+=sz[x]; f[x]=y; } inline bool check( int al, int ar, int bl, int br){ return al<=br&&bl<=ar;} inline void analyze( int o, int l, int r){ int i,j,k,m,_; static int py[N*4],q0[N*2],q1[N*2]; int L=block[o].l,R=block[o].r; py[_=1]=M; for (i=l;i<=r;i++){ py[++_]=e[i].y-1; py[++_]=e[i].y+H-1; } sort(py+1,py+_+1); for (m=0,i=1;i<=_;i++) if (py[i]>py[m])py[++m]=py[i]; int h0=1,t0=0,h1=1,t1=0; block[o].st=cnt+1; for (i=1,j=l;i<=m;i++){ while (j<=r&&e[j].y<=py[i-1]+1){ if (e[j].t==0){ while (h0<=t0&&e[q0[t0]].x>=e[j].x)t0--; q0[++t0]=j; } else { while (h1<=t1&&e[q1[t1]].x<=e[j].x)t1--; q1[++t1]=j; } j++; } int up=L,down=R; while (h0<=t0&&e[q0[h0]].y+H-1<py[i])h0++; if (h0<=t0)down=min(down,e[q0[h0]].x-1); while (h1<=t1&&e[q1[h1]].y+H-1<py[i])h1++; if (h1<=t1)up=max(up,e[q1[h1]].x+1); if (up<=down){ area[++cnt]=Area(py[i-1]+1,py[i],up,down); sz[cnt]=area[cnt].cal(); f[cnt]=cnt; } } int nowl=block[o].st,nowr=block[o].en=cnt; for (i=nowl;i<nowr;i++) if (area[i].yr+1==area[i+1].yl&&check(area[i].xl,area[i].xr,area[i+1].xl,area[i+1].xr)) merge(i,i+1); if (o==1) return ; int prel=block[o-1].st,prer=block[o-1].en; for (i=nowl,j=prel,k=prel-1;i<=nowr;i++){ if (area[i].xl!=L) continue ; while (j<=prer&&area[j].yr<area[i].yl)j++; while (k<prer&&area[k+1].yl<=area[i].yr)k++; for (_=j;_<=k;_++) if (area[_].xr+1==L)merge(i,_); } } int main(){ scanf ( "%d" ,&Case); while (Case--){ scanf ( "%d%d%d%d" ,&n,&M,&W,&H); pool[1]=0; pool[cp=2]=(M-1)/W; ce=cb=cnt=0; while (n--){ scanf ( "%d%d" ,&x,&y); x--; ext(x,y); } sort(pool+1,pool+cp+1); for (i=1;i<=cp;i++){ if (i>1&&pool[i]==pool[i-1]) continue ; int l=pool[i]*W; if (cb&&l>block[cb].r+1){ cb++; block[cb]=Block(block[cb-1].r+1,l-1); } block[++cb]=Block(l,min(l+W-1,M-1)); } sort(e+1,e+ce+1,cmpe); for (i=j=1;i<=cb;i++){ for (k=j;k<=ce&&e[k].b<=block[i].br;k++); analyze(i,j,k-1); j=k; } ans=0; for (i=1;i<=cnt;i++) if (F(i)==i)ans+=sz[i]*sz[i]; printf ( "%llu\n" ,ans); } } |
H. Laser Alarm
三个不共线的点可以确定一个平面。对于任意一个平面,将其调整至经过三个顶点,结果不会变差。因此枚举三个顶点得到平面,然后计算触碰了该平面的线段数,更新答案即可。所有点都共线的情况需要特判。
时间复杂度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | #include<cstdio> const int N=55; int Case,n,i,j,k,o,now,ans; struct P{ int x,y,z; P(){} P( int _x, int _y, int _z){x=_x,y=_y,z=_z;} P operator-( const P&p) const { return P(x-p.x,y-p.y,z-p.z);} P operator*( const P&p) const { return P(y*p.z-z*p.y,z*p.x-x*p.z,x*p.y-y*p.x);} int operator^( const P&p) const { return x*p.x+y*p.y+z*p.z;} bool operator==( const P&p) const { return x==p.x&&y==p.y&&z==p.z;} }p[N*2]; inline int ptoplane( const P&a, const P&b, const P&c, const P&p){ return ((b-a)*(c-a))^(p-a);} inline bool colinear( const P&a, const P&b, const P&p){ P t=(a-b)*(b-p); return !t.x&&!t.y&&!t.z; } int main(){ scanf ( "%d" ,&Case); while (Case--){ scanf ( "%d" ,&n); for (i=0;i<n+n;i++) scanf ( "%d%d%d" ,&p[i].x,&p[i].y,&p[i].z); ans=1; for (i=0;i<n+n;i++) for (j=0;j<i;j++){ if (p[i]==p[j]) continue ; for (k=0;k<j;k++){ if (p[i]==p[k]) continue ; if (p[j]==p[k]) continue ; if (colinear(p[i],p[j],p[k])) continue ; now=0; for (o=0;o<n;o++){ int x=ptoplane(p[i],p[j],p[k],p[o<<1]); int y=ptoplane(p[i],p[j],p[k],p[o<<1|1]); if (!x||!y||(x<0&&y>0)||(x>0&&y<0))now++; } if (now>ans)ans=now; } now=0; for (o=0;o<n;o++) if (colinear(p[i],p[j],p[o<<1])||colinear(p[i],p[j],p[o<<1|1])) now++; if (now>ans)ans=now; } printf ( "%d\n" ,ans); } } |
I. Package Delivery
考虑最小的那个区间,第一次取快递放在第天一定不会使结果变差。此时可能有很多区间覆盖了,那么为了尽量延后下一次取快递的日期,此时的最优策略应该是选择覆盖且值最小的个区间,使用堆找到并去掉这些区间后,问题就递归了。重复上述过程直至处理完所有个区间。
时间复杂度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | #include<cstdio> #include<algorithm> #include<vector> #include<queue> using namespace std; typedef pair< int , int >P; const int N=100005; int Case,n,k,i,j,t,ans,ql[N],qr[N],del[N]; P e[N]; priority_queue<P,vector<P>,greater<P> >q; inline bool cmpl( int x, int y){ return e[x].first<e[y].first;} inline bool cmpr( int x, int y){ return e[x].second<e[y].second;} int main(){ scanf ( "%d" ,&Case); while (Case--){ scanf ( "%d%d" ,&n,&k); for (i=1;i<=n;i++){ scanf ( "%d%d" ,&e[i].first,&e[i].second); ql[i]=i; qr[i]=i; del[i]=0; } sort(ql+1,ql+n+1,cmpl); sort(qr+1,qr+n+1,cmpr); for (ans=0,i=j=1;i<=n;i++){ if (del[qr[i]]) continue ; while (j<=n&&e[ql[j]].first<=e[qr[i]].second){ q.push(P(e[ql[j]].second,ql[j])); j++; } ans++; for (t=1;t<=k;t++){ if (q.empty()) break ; del[q.top().second]=1; q.pop(); } } printf ( "%d\n" ,ans); } } |
J. Range Reachability Query
离线询问,设表示点能否仅通过编号在之间的边走到第个询问的目的地,共个01状态,可以使用bitset存储。第个询问的答案即为。
考虑一条边的转移,假设它的编号为,令所有满足的询问的集合为,则有f[u] |= f[v] & S。接下来考虑如何得到集合,一个直接的想法是从到依次考虑每条边,维护覆盖当前边的询问集合,每个询问拆成两个事件:
- 在处加入集合。
- 在处离开集合。
上述方法需要保存个bitset,空间复杂度过高。节省空间的方法是每个事件保存一次bitset,这样只需保存个bitset,然后每次要得到集合时,再往对应bitset的副本中暴力模拟个事件。
总时间复杂度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | #include<cstdio> #include<algorithm> using namespace std; typedef unsigned long long ull; const int N=50005,M=100005,Q=50005,K=785,B=325; int Case,n,m,q,all,ce,i,j,k,x,y,l,r,g[N],v[M],nxt[M],que[Q][2],en[M]; ull f[N][K],h[Q*2/B+3][K],cur[K]; struct E{ int x,y;E(){}E( int _x, int _y){x=_x,y=_y;}}e[Q*2]; inline bool cmp( const E&a, const E&b){ return a.x<b.x;} inline void flip(ull*f, int x){f[x>>6]^=1ULL<<(x&63);} inline void clr(ull*f){ for ( int i=0;i<=all;i++)f[i]=0;} inline void copy(ull*f,ull*g){ for ( int i=0;i<=all;i++)f[i]=g[i];} inline void trans(ull*f,ull*g,ull*h){ for ( int i=0;i<=all;i++)f[i]|=g[i]&h[i];} int main(){ scanf ( "%d" ,&Case); while (Case--){ scanf ( "%d%d%d" ,&n,&m,&q); for (i=1;i<=m;i++){ scanf ( "%d%d" ,&x,&y); v[i]=y; nxt[i]=g[x]; g[x]=i; } all=(q-1)>>6; for (i=1;i<=n;i++)clr(f[i]); for (i=0;i<q;i++){ scanf ( "%d%d%d%d" ,&x,&y,&l,&r); que[i][0]=x; que[i][1]=y; flip(f[y],i); e[++ce]=E(l,i); e[++ce]=E(r+1,i); } sort(e+1,e+ce+1,cmp); for (i=1,j=0;i<=m;i++){ while (j<ce&&e[j+1].x<=i)j++; en[i]=j; } clr(cur); for (i=1;i<=ce;i++){ flip(cur,e[i].y); if (i%B==0)copy(h[i/B],cur); } for (i=n;i;i--) for (j=g[i];j;j=nxt[j]){ x=en[j]; copy(cur,h[x/B]); for (k=x/B*B+1;k<=x;k++)flip(cur,e[k].y); trans(f[i],f[v[j]],cur); } for (i=0;i<q;i++) puts (f[que[i][0]][i>>6]>>(i&63)&1? "YES" : "NO" ); for (i=1;i<=n;i++)g[i]=0; ce=0; } } |
K. Taxi
如果没有的限制,那么是经典问题。根据,有
分别记录、、、的最大值即可在时间内求出所有点到的曼哈顿距离的最大值。
现在考虑加入的限制。将所有城镇按照从小到大排序,并记录排序后每个后缀的、、、的最大值,用于求给定点到该后缀中所有点的距离最大值。
选取按排序后的第个城镇,求出给定点到第个城镇的距离最大值,有两种情况:
- ,那么第个城镇对答案的贡献至少为。用更新答案后,由于第个城镇的值均不超过,因此它们不可能接着更新答案,考虑范围缩小至。
- ,那么第个城镇对答案的贡献为。用更新答案后,考虑范围缩小至。
容易发现每次考虑的范围都是一个区间,如果每次取为区间的中点,那么迭代次即可得到最优解。
时间复杂度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | #include<cstdio> #include<algorithm> using namespace std; const int N=100005,inf=2100000000; int Case,n,m,i,x,y,a[N],b[N],c[N],d[N]; struct E{ int x,y,w;}e[N]; inline bool cmp( const E&a, const E&b){ return a.w<b.w;} inline void up( int &a, int b){a<b?(a=b):0;} int main(){ scanf ( "%d" ,&Case); while (Case--){ scanf ( "%d%d" ,&n,&m); for (i=1;i<=n;i++) scanf ( "%d%d%d" ,&e[i].x,&e[i].y,&e[i].w); sort(e+1,e+n+1,cmp); a[n+1]=b[n+1]=c[n+1]=d[n+1]=-inf; for (i=n;i;i--){ a[i]=max(a[i+1],-e[i].x-e[i].y); b[i]=max(b[i+1],-e[i].x+e[i].y); c[i]=max(c[i+1],e[i].x-e[i].y); d[i]=max(d[i+1],e[i].x+e[i].y); } while (m--){ scanf ( "%d%d" ,&x,&y); int l=1,r=n,mid,tmp,ans=0; while (l<=r){ mid=(l+r)>>1; tmp=x+y+a[mid]; up(tmp,x-y+b[mid]); up(tmp,-x+y+c[mid]); up(tmp,-x-y+d[mid]); if (e[mid].w<tmp){ l=mid+1; up(ans,e[mid].w); } else { r=mid-1; up(ans,tmp); } } printf ( "%d\n" ,ans); } } } |
L. Two Permutations
首先特判序列中每个数字出现次数不都为的情况,此时答案为。
动态规划,设表示的前项匹配上了,且匹配中数字第次出现的位置时,有多少种合法的方案。由于中每个数字出现次数都为,因此状态数为。转移时枚举匹配哪个位置,那么匹配的位置与匹配的位置中间的那段连续子串需要完全匹配中对应的子串,使用字符串Hash进行判断即可。
时间复杂度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | #include<cstdio> typedef unsigned long long ull; const int N=300005,P=998244353,S=233; int Case,n,i,j,k,x,y; int a[N],b[N],c[N*2],pc[N][2],f[N][2],ans; ull p[N*2],fb[N],fc[N*2]; inline void up( int &a, int b){a=a+b<P?a+b:a+b-P;} inline ull ask(ull*f, int l, int r){ return f[r]-f[l-1]*p[r-l+1];} inline bool check( int bl, int br, int cl, int cr){ if (bl>br) return 1; if (bl<1||br>n||cl<1||cr>n+n) return 0; return ask(fb,bl,br)==ask(fc,cl,cr); } int main(){ for (p[0]=i=1;i<N*2;i++)p[i]=p[i-1]*S; scanf ( "%d" ,&Case); while (Case--){ scanf ( "%d" ,&n); for (i=1;i<=n;i++)pc[i][0]=pc[i][1]=0; for (i=1;i<=n;i++) scanf ( "%d" ,&a[i]); for (i=1;i<=n;i++) scanf ( "%d" ,&b[i]),fb[i]=fb[i-1]*S+b[i]; for (i=1;i<=n+n;i++){ scanf ( "%d" ,&x); c[i]=x; fc[i]=fc[i-1]*S+x; if (!pc[x][0])pc[x][0]=i; else pc[x][1]=i; } for (i=1;i<=n;i++) if (!pc[i][0]||!pc[i][1]) break ; if (i<=n){ puts ( "0" ); continue ; } for (i=1;i<=n;i++) for (j=0;j<2;j++)f[i][j]=0; for (j=0;j<2;j++){ x=pc[a[1]][j]; if (check(1,x-1,1,x-1))f[1][j]=1; } for (i=1;i<n;i++) for (j=0;j<2;j++) if (f[i][j]){ x=pc[a[i]][j]; for (k=0;k<2;k++){ y=pc[a[i+1]][k]; if (y<=x) continue ; if (check(x-i+1,y-i-1,x+1,y-1))up(f[i+1][k],f[i][j]); } } ans=0; for (j=0;j<2;j++) if (f[n][j]){ x=pc[a[n]][j]; if (check(x-n+1,n,x+1,n+n))up(ans,f[n][j]); } printf ( "%d\n" ,ans); } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 字符编码:从基础到乱码解决
2017-07-26 BZOJ4167 : 永远的竹笋采摘
2015-07-26 BZOJ3456 : 城市规划