2016-2017 National Taiwan University World Final Team Selection Contest (Codeforces Gym) 部分题解
D
考虑每个点被删除时其他点对它的贡献,然后发现要求出距离为1~k的点对有多少个。
树分治+FFT。分治时把所有点放一起做一遍FFT,然后减去把每棵子树单独做FFT求出来的值。
复杂度nlog^2n
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 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | #include<bits/stdc++.h> #define N 270000 #define pi acos(-1) #define ll long long #define inf 0x3f3f3f3f using namespace std; const int p = 1000000007; int pw( int x, int y) { int lst=1; while (y) { if (y&1)lst=1LL*lst*x%p; y>>=1; x=1LL*x*x%p; } return lst; } int head[N],ver[2*N],nxt[2*N],tot; void add( int a, int b) { tot++;nxt[tot]=head[a];head[a]=tot;ver[tot]=b; return ; } struct E { double x,y; E(){;} E( double _x, double _y) { x=_x;y=_y; } friend E operator + (E &a,E &b) { return E(a.x+b.x,a.y+b.y); } friend E operator - (E &a,E &b) { return E(a.x-b.x,a.y-b.y); } friend E operator * (E &a,E &b) { return E(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x); } }a[N],b[N],c[N]; int R[N]; ll ans[N]; void fft(E *a, int f, int n) { for ( int i=0;i<n;i++) if (i>R[i])swap(a[i],a[R[i]]); for ( int i=1;i<n;i<<=1) { E wn( cos (pi/i),f* sin (pi/i)); for ( int j=0;j<n;j+=(i<<1)) { E w(1,0); for ( int k=0;k<i;k++,w=w*wn) { E x=a[j+k],y=a[j+k+i]*w; a[j+k]=x+y;a[j+k+i]=x-y; } } } if (f==-1) { for ( int i=0;i<n;i++)a[i].x/=n; } return ; } void FFT( int *sa, int m, int f) { int l=0,n=1; while (n<=2*m)n<<=1,l++; for ( int i=0;i<n;i++) { a[i].y=a[i].x=b[i].x=b[i].y=0; if (i<=m)a[i].x=b[i].x=sa[i]; } for ( int i=0;i<n;i++)R[i]=(R[i>>1]>>1)|((i&1)<<(l-1)); fft(a,1,n);fft(b,1,n); for ( int i=0;i<n;i++)c[i]=a[i]*b[i]; fft(c,-1,n); for ( int i=2;i<n;i++) { ll tmp=(ll)(c[i].x+0.5); ans[i-1]+=f*tmp; } return ; } int size[N],mn,id,sum,v[N]; int n; void dfs( int x, int f) { int mx=0; size[x]=1; for ( int i=head[x];i;i=nxt[i]) { if (v[ver[i]]||ver[i]==f) continue ; dfs(ver[i],x); size[x]+=size[ver[i]]; mx=max(mx,size[ver[i]]); }mx=max(mx,sum-size[x]); if (mx<mn) { mn=mx; id=x; } return ; } int now[N],nw[N]; int mxx=0; void dffs( int x, int f, int dp) { size[x]=1;ans[dp]+=2;now[dp]++,nw[dp]++; if (dp>mxx)mxx=dp; for ( int i=head[x];i;i=nxt[i]) { if (v[ver[i]]||ver[i]==f) continue ; dffs(ver[i],x,dp+1); size[x]+=size[ver[i]]; } return ; } void solve( int x) { sum=size[x];mn=inf;id=x; dfs(x,-1); x=id; v[x]=1;size[x]=1;ans[1]++; for ( int i=0;i<=sum;i++)now[i]=0; int mx=0; for ( int i=head[x];i;i=nxt[i]) { if (v[ver[i]]) continue ; mxx=0; dffs(ver[i],x,2); size[x]+=size[ver[i]]; FFT(nw,mxx,-1); mx=max(mx,mxx); for ( int j=0;j<=mxx;j++)nw[j]=0; } if (mx)FFT(now,mx,1); for ( int i=head[x];i;i=nxt[i]) { if (!v[ver[i]])solve(ver[i]); } } int main() { scanf ( "%d" ,&n); int t1,t2; for ( int i=1;i<n;i++) { scanf ( "%d%d" ,&t1,&t2); add(t1,t2);add(t2,t1); } size[1]=n; solve(1); ll as=0; for ( int i=1;i<=n;i++) { ans[i]%=p; as+=ans[i]*pw(i,p-2)%p; } as%=p; for ( int i=2;i<=n;i++) { as=as*i%p; } printf ( "%I64d\n" ,as); return 0; } |
E
把每条线段看成二维平面上的一个点。
相当于求从(0,0)点到(n+1,n+1)的一条权值和最小的一条路径,且相邻两个点之间不能有其他点。
CDQ分治+单调栈+线段树
和bzoj 4273很像。
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 | #include<bits/stdc++.h> #define N 100055 #define inf 2147483647 #define ls x<<1,l,mid #define rs x<<1|1,mid+1,r using namespace std; int n; int p[N],v[N]; int a[N*4]; void gai( int x, int l, int r, int pos, int z) { if (l==r) { a[x]=z; return ; } int mid=(l+r)>>1; if (pos<=mid)gai(ls,pos,z); else gai(rs,pos,z); a[x]=min(a[x<<1],a[x<<1|1]); return ; } int qur( int x, int l, int r, int ll, int rr) { if (l>=ll&&r<=rr) return a[x]; int mid=(l+r)>>1; if (ll>mid) return qur(rs,ll,rr); if (rr<=mid) return qur(ls,ll,rr); return min(qur(rs,ll,rr),qur(ls,ll,rr)); } int st1[N],top1,st2[N],top2; int q1[N],cnt1,q2[N],cnt2,f[N]; bool cmp( int x, int y) { return p[x]<p[y]; } void solve( int l, int r) { if (l==r) return ; int mid=(l+r)>>1; solve(l,mid); top1=top2=cnt1=cnt2=0; for ( int i=l;i<=mid;i++)q1[++cnt1]=i; for ( int i=mid+1;i<=r;i++)q2[++cnt2]=i; sort(q1+1,q1+cnt1+1,cmp);sort(q2+1,q2+cnt2+1,cmp); int pt=1; for ( int i=1;i<=cnt2;i++) { while (pt<=cnt1&&p[q1[pt]]<=p[q2[i]]) { while (top1&&st1[top1]<q1[pt]) { gai(1,1,n,p[st1[top1]],inf); top1--; } gai(1,1,n,p[q1[pt]],f[q1[pt]]+v[q1[pt]]); st1[++top1]=q1[pt]; pt++; } while (top2&&st2[top2]>q2[i])top2--; int tmp=0; if (top2)tmp=p[st2[top2]]+1; f[q2[i]]=min(f[q2[i]],qur(1,1,n,tmp,p[q2[i]])); st2[++top2]=q2[i]; } while (top1)gai(1,1,n,p[st1[top1]],inf),top1--; solve(mid+1,r); } int main() { scanf ( "%d" ,&n); for ( int i=1;i<=n;i++) scanf ( "%d" ,&p[i]); for ( int i=1;i<=n;i++) scanf ( "%d" ,&v[i]); for ( int i=1;i<=4*(n+1);i++)a[i]=inf; p[n+1]=n+1;p[0]=0; memset (f,0x3f, sizeof (f)); f[0]=0; solve(0,n+1); printf ( "%d\n" ,f[n+1]); return 0; } |
H
按极角排序,然后用一条线扫过去。
然后狂WA第5个点,去膜了下Claris的代码,发现有些细节写错了。。。
当一个点左右两个点极角比它小的时候,那么答案会加一,否则会减一。
如果连续一条直线上极角都相等,只拿端点算贡献。
然后这些点分为两类,假设现在答案要加一。
如果这个点在右下(意会一下),那么只有扫描线扫过这个点时答案才会加一。
如果在左上,那么扫到这个点时答案已经加了一。
讨论一下,具体看代码。
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 | #include<bits/stdc++.h> #define N 100005 #define ll long long using namespace std; int n; struct node { int x,y; node(){;} node( int _x, int _y) { x=_x;y=_y; } friend node operator - ( const node &aa, const node &bb) { return node(aa.x-bb.x,aa.y-bb.y); } }a[N]; int p[N]; ll cj( const node &aa, const node &bb) { return 1LL*aa.x*bb.y-1LL*aa.y*bb.x; } bool in[N],ok[N]; bool cmp( int x, int y) { return cj(a[x],a[y])>0; } int main() { scanf ( "%d" ,&n); for ( int i=1;i<=n;i++) scanf ( "%d%d" ,&a[i].x,&a[i].y); a[0]=a[n];a[n+1]=a[1]; for ( int i=1;i<=n;i++)p[i]=i; sort(p+1,p+n+1,cmp); int sum=0,ans=0; for ( int i=1;i<=n;) { node as=a[p[i]]; int tmp=0; for (;i<=n&&cj(as,a[p[i]])==0;i++) { if (cj(a[p[i]+1],a[p[i]])<0&&cj(a[p[i]-1],a[p[i]])<=0) { if (cj(a[p[i]-1]-a[p[i]],a[p[i]+1]-a[p[i]])>0)sum++; else tmp++; } if (cj(a[p[i]+1],a[p[i]])>0&&cj(a[p[i]-1],a[p[i]])>=0) { if (cj(a[p[i]-1]-a[p[i]],a[p[i]+1]-a[p[i]])<0)sum--; else tmp--; } } ans=max(ans,sum); sum+=tmp; ans=max(ans,sum); } printf ( "%d\n" ,ans+1); return 0; } |
I
傻逼题,一个子树要么给上边提供一个两个叶子的小子树,要么是一个叶子或零个,其他的只能直接配对,画画图大力分类讨论+贪心。
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 | #include<bits/stdc++.h> #define N 100005 using namespace std; int n; int head[N],ver[N*2],nxt[N*2],tot; void add( int a, int b) { tot++;nxt[tot]=head[a];head[a]=tot;ver[tot]=b; return ; } int du[N],root; int ans; int dfs( int x, int f) { int t1=0,t2=0; int cnt=0; for ( int i=head[x];i;i=nxt[i]) { if (ver[i]==f) continue ; cnt++; int tmp=dfs(ver[i],x); if (tmp==1)t1++; else if (tmp==2)t2++; } if (!cnt) return 1; while (t2>=2)t2-=2,ans++; while (t1>=3)t1-=2,ans++; if (!t1) { if (t2==1) return 2; return 0; } if (t1==1) { if (t2==1) return 2; return 1; } else { if (!t2) return 2; else if (t2==1){ans++; return 1;} else {ans++; return 2;} } } int main() { scanf ( "%d" ,&n); if (n==2) { puts ( "1" ); return 0; } int t1,t2; for ( int i=1;i<n;i++) { scanf ( "%d%d" ,&t1,&t2); add(t1,t2);add(t2,t1); du[t1]++;du[t2]++; } for ( int i=1;i<=n;i++) if (du[i]>1)root=i; int tmp=dfs(root,-1); if (tmp==2)ans++; printf ( "%d\n" ,ans); return 0; } |
J
先假设一共有无数个0。
枚举右端点,枚举左端点,然后把中间的1去掉,剩下的操作往里边塞0。
列下式子发现左端点单调,可以用单调队列优化。
最后把ans和0的个数取个min。
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 | #include<bits/stdc++.h> #define N 1000005 using namespace std; char s[N]; int n,mx,sum[N]; int q[N]; void solve( int x) { int ha=1,ta=1; q[1]=0; int ans=0; for ( int i=1;i<=n;i++) { while (ta>=ha&&sum[i]-sum[q[ha]]>x)ha++; if (ta>=ha) { ans=max(ans,i-2*sum[i]+x+2*sum[q[ha]]-q[ha]); } while (ta>=ha&&2*sum[q[ta]]-q[ta]<=2*sum[i]-i)ta--; q[++ta]=i; } printf ( "%d\n" ,min(ans,mx)); return ; } int main() { scanf ( "%s" ,s+1); n= strlen (s+1); for ( int i=1;i<=n;i++) { sum[i]=sum[i-1]; if (s[i]== '1' )sum[i]++; } for ( int i=1;i<=n;i++) if (s[i]== '0' )mx++; int q; scanf ( "%d" ,&q); for ( int i=1;i<=q;i++) { int cnt; scanf ( "%d" ,&cnt); solve(cnt); } return 0; } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步