假期没有出去……恰好17号晚上有一场8:00-9:40的ARC092非常适合衡中作息时间,于是就去打了,虽然是英文题面但是题意还是十分好懂,大概是我很喜欢的类型。18号晚上的UR17是大家集体参赛,第一次打UOJ的比赛果然非常interesting。考虑到罚时问题以及感觉自己再怎么样也写不了很多分,最开始就把B题和C题的骗分程序交上去了,A题提交大概也在九点之前。因为是集体比赛就比较正规,19号大半天都用来改题了。难度上当然是UR大一些,不过两场比赛各有特点,选手体验都不错?都是第一次参赛所以都没有掉Rating,ARC只A了C题因为罚时少拿了553Rating,UR尽管只有10+5+10还是+68。
ARC092
C
题意:是给出红点蓝点各$n$个,如果一个红点横纵坐标都小于一个蓝点则它们可以配对,每个点只能用一次,问最多有多少对。$n<=100$
题解:这题难道不是在开玩笑吗……二分图网络流随便跑啊……官方题解给出的是贪心,按横坐标递增处理蓝点,对于每一个蓝点选可行红点中纵坐标最大的。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<queue> 5 using namespace std; 6 const int sj=110; 7 const int inf=0x7fffffff; 8 int n,h[sj<<1],s,t,dep[sj<<1],ans,e; 9 queue<int> q; 10 struct point 11 { 12 int xi,yi; 13 void in() 14 { scanf("%d%d",&xi,&yi); } 15 }p1[sj],p2[sj]; 16 struct B 17 { 18 int ne,v,w; 19 }b[30000]; 20 void add(int x,int y,int z) 21 { 22 b[e].v=y,b[e].ne=h[x],b[e].w=1,h[x]=e++; 23 b[e].v=x,b[e].ne=h[y],b[e].w=0,h[y]=e++; 24 } 25 bool bfs(int x) 26 { 27 while(!q.empty()) q.pop(); 28 memset(dep,0,sizeof(dep)); 29 q.push(x),dep[x]=1; 30 while(!q.empty()) 31 { 32 x=q.front(),q.pop(); 33 for(int i=h[x];i!=-1;i=b[i].ne) 34 if(b[i].w&&!dep[b[i].v]) 35 { 36 dep[b[i].v]=dep[x]+1; 37 if(b[i].v==t) return 1; 38 q.push(b[i].v); 39 } 40 } 41 return 0; 42 } 43 inline int minn(int x,int y) 44 { 45 return x<y?x:y; 46 } 47 int dfs(int x,int f) 48 { 49 if(x==t) return f; 50 int ret=0,d; 51 for(int i=h[x];i!=-1;i=b[i].ne) 52 if(b[i].w&&dep[b[i].v]==dep[x]+1) 53 { 54 d=dfs(b[i].v,minn(f,b[i].w)); 55 ret+=d,f-=d; 56 b[i].w-=d,b[i^1].w+=d; 57 if(!f) break; 58 } 59 if(!ret) dep[x]=-1; 60 return ret; 61 } 62 int main() 63 { 64 scanf("%d",&n); 65 t=2*n+1; 66 memset(h,-1,sizeof(h)); 67 for(int i=1;i<=n;i++) p1[i].in(),add(s,i,1); 68 for(int i=1;i<=n;i++) p2[i].in(),add(i+n,t,1); 69 for(int i=1;i<=n;i++) 70 for(int j=1;j<=n;j++) 71 if(p1[i].xi<p2[j].xi&&p1[i].yi<p2[j].yi) 72 add(i,j+n,1); 73 while(bfs(s)) ans+=dfs(s,inf); 74 printf("%d",ans); 75 return 0; 76 }
D
题意:给出两个长度为$n$的序列$a$、$b$,把它们每一位两两加和($a_1+b_1$、$a_1+b_2$、$a_1+b_3$……$a_1+b_n$、$a_2+b_1$……$a_n+b_n$)得到$n^2$个和,问这$n^2$个和的异或。$n<=2e5$,序列中数$<=2^{28}$
题解:按位考虑,把两个序列都变成模$2^k$意义下,把$a$排序去扫$b$。对于$b$的每一位有影响的$a$一定是2个连续区间(注意加法有进位,所以有$2$个),二分找到这些区间就可以了。
E
题意:给出长度为$n$的一个序列,从中选择元素。如果选的是两端直接删去,否则把这个数两边的元素加和取代它,再把两边删去($a$、$b$、$c$、$d$、$e$选择$c$之后变成$a$、$b+d$、$e$)。最后只剩一个元素,要使这个元素最大,输出最优解并给出方案。给出方案即输出要选择多少次并且给出每次选择的下标,注意如果前面的一个元素被删去后面的下标要前移。$n<=1000$
题解:刚开始觉得这题和《寄语》一个套路,后来注意到只有下标奇偶相同的才能同时留下。输出方案的细节挺多……要注意不能一个元素都不留。先把两边想删的删完,再把中间两边都不留或两边都留的元素依次删去。比赛的时候一直在用队列和栈来实现这个过程,现在看来或许还是链表更合适些,毕竟数据范围是很小的可以扫多次。
F
题意:给出$n$个点$m$条边的一个有向图,对于每一条边询问它反向后强连通分量数会不会变。$n<=1000,m<=2e5$
题解:感觉这好像是个很普通的问题,但是原来一直没有想过?果然我熟悉的算法也是有很多没有被发现的奇妙之处的。对于每条边$u->v$求解两个问题:$v$能不能到$u$?$u$不经过这条边能不能到$v$?如果这两个问题答案相同则强连通分量数不会变,否则会变。第一个问题只要$n*m$搜一下就可以了。第二个问题,先把$u$标记,再依次搜它的每一条出边,对于全图中的所有点记录它第一次被访问是在搜哪一条出边时;按和第一次相反的顺序再搜所有出边,给图中的点打上第二个标记。如果$u$的出边中有哪条边另一个端点有不是它自己的标记,这条边的第二个问题答案为$true$,复杂度仍为$n*m$。
UR17
比赛链接与官方题解。UOJ上的题不想叙述题意了……这个题目风格和AtCoder大概正好相反。
A
千载难逢的FST好题,我在内的一大波HZOI群众都以为部分分是假的性质是真的,今天早起我们发现我们是假的。由于对这个性质的不确定我压根就没有去写$n^2$以下的算法,所以在大家都认为自己能A的时候我十分伤心……伤心了整整一晚上呢。不过即使这$25$分也没有拿全,毕竟我没有写$nlog$做法啊。对于这个贪心的反例构造大概是人类智慧所不能及的,手玩失败之后试图拍了半天也没拍出来,UOJ上大概几万的数据也不过差了$1$或$2$。实在是妙啊。
题解上说$n^n$枚举能过Subtask1,但是我不剪枝并不能过。链上的分$n!$据说能过Subtask2,但是我写了之后只过了Subtask1……大概暴力也是需要优化的,随便地写暴力得分也很随便啊。稍作分析就能发现答案一定是链,反正多与一些总没坏处。但是后面那个字典序最小就不一定了,感觉出题人这个先下诱饵再挖陷阱玩得真是好啊……不过部分分还是要写的,Subtask3可以$n^2$枚举,Subtask4可以先处理所有数共有的位,把这些位提前累计到答案里之后最多$log$次与值就会变为$0$了,所以复杂度为$nlogn$。
脱离那些乱搞去研究真正的正解,依然把共有的位提出来,$f[i]$表示把$i$消到$0$的最小代价,初始化$f[0]=0$,$f[a[i]]=a[i]]$,那么$f[i]=min(f[i],f[i$&$a[j]]+(i$&$a[j]))$。设$m$为序列中元素的最大值,这个算法复杂度为$n*m$,可以通过Subtask5。现在我们采用更快速的消去方式,每次消一个集合,预处理$vi[i]$表示是否存在$j$使得$i$&$a[j]$为$0$,转移$f$时枚举$i$的子集$j$,如果$vi[j]==1$则$f[i]=min(f[i],f[i$^$j]+(i$^$j))$,一个比较显然的剪枝是当$(i$^$j)>=f[i]$时break。我不是很会分析最后这个解法的复杂度……反正能过还跑得挺快?这漫长的一道题终于结束了。
UPD:我终于从wq那里看到了一个贪心的反例:
3
11 12 19(1011,、1100、10011)
如果贪心会先选11,然后19,然后12,得到的答案是11+3+0=14;但如果先选12再选19,答案就只有12了。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #define ll long long 6 using namespace std; 7 const int sj=100010; 8 const ll inf=1ll<<50; 9 int n,a[sj],fa[sj],tp,xr,bin[20]; 10 bool vi[sj]; 11 ll ans,nt,mi; 12 void check() 13 { 14 nt=xr=a[fa[1]]; 15 for(int i=2;i<=n;++i) 16 { 17 xr&=a[fa[i]]; 18 nt+=xr; 19 if(nt>=ans) return; 20 } 21 ans=nt; 22 } 23 void dfs(int x) 24 { 25 if(x==n+1) 26 { 27 check(); 28 return; 29 } 30 for(int i=1;i<=n;i++) 31 if(!vi[i]) 32 { 33 fa[x]=i,vi[i]=1; 34 dfs(x+1); 35 vi[i]=0; 36 } 37 } 38 void work1() 39 { 40 bin[0]=1; 41 for(int i=1;i<=19;i++) bin[i]=bin[i-1]<<1; 42 sort(a+1,a+n+1); 43 for(int i=0;i<=18;i++) 44 { 45 tp=1; 46 for(int j=1;j<=n;j++) 47 if(!(bin[i]&a[j])) 48 { tp=0;break; } 49 if(tp) 50 { 51 ans+=1ll*n*bin[i]; 52 tp=bin[i]^(bin[19]-1); 53 for(int j=1;j<=n;j++) 54 a[j]&=tp; 55 } 56 } 57 xr=a[1],ans+=a[1]; 58 for(int j=2;j<=n;j++) 59 { 60 mi=inf; 61 for(int k=1;k<=n;k++) 62 if((xr&a[k])<mi) 63 mi=a[k]&xr; 64 xr=mi,ans+=xr; 65 if(!xr) break; 66 } 67 printf("%lld",ans); 68 } 69 void work2() 70 { 71 ans=inf; 72 dfs(1); 73 printf("%lld",ans); 74 } 75 int main() 76 { 77 scanf("%d",&n); 78 for(int i=1;i<=n;i++) scanf("%d",&a[i]); 79 if(n>12) work1(); 80 else work2(); 81 return 0; 82 }
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #define ll long long 6 const int sj=100010; 7 int n,a[sj],bin[20],op,tp,op2; 8 ll f[sj<<1],ans; 9 ll minn(ll x,ll y) 10 { 11 if(y<0) return x; 12 return x<y?x:y; 13 } 14 int main() 15 { 16 scanf("%d",&n); 17 for(int i=1;i<=n;i++) scanf("%d",&a[i]); 18 bin[0]=1; 19 for(int j=1;j<=19;j++) bin[j]=bin[j-1]<<1; 20 for(int j=0;j<=18;j++) 21 { 22 op=op2=1; 23 for(int i=1;i<=n;i++) 24 { 25 if(!(bin[j]&a[i])) op=0; 26 else op2=0; 27 } 28 if(op) 29 { 30 ans+=1ll*n*bin[j]; 31 for(int i=1;i<=n;i++) 32 a[i]&=bin[j]^(bin[19]-1); 33 } 34 else if(!op2) tp|=bin[j]; 35 } 36 memset(f,0x7f,sizeof(f)); 37 for(int i=1;i<=n;i++) f[a[i]]=a[i]; 38 f[0]=0; 39 for(int j=1;j<=tp;j++) 40 for(int i=1;i<=n;i++) 41 f[j]=minn(f[j],f[j&a[i]]+(j&a[i])); 42 ans+=f[tp]; 43 printf("%lld",ans); 44 return 0; 45 }
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #define ll long long 6 using namespace std; 7 const int sj=200010; 8 int n,a[sj],bin[20],op,tp,op2; 9 bool vi[sj<<1]; 10 ll f[sj<<1],ans; 11 ll minn(ll x,ll y) 12 { 13 if(y<0) return x; 14 return x<y?x:y; 15 } 16 int main() 17 { 18 bin[0]=1; 19 for(int j=1;j<=19;j++) bin[j]=bin[j-1]<<1; 20 op=bin[19]-1; 21 scanf("%d",&n); 22 for(int i=1;i<=n;i++) 23 { 24 scanf("%d",&a[i]); 25 op&=a[i],tp|=a[i]; 26 } 27 ans=1ll*n*op,tp^=op; 28 memset(f,0x7f,sizeof(f)); 29 for(int i=1;i<=n;i++) 30 { 31 a[i]^=op,f[a[i]]=a[i]; 32 vi[tp^a[i]]=1; 33 } 34 vi[0]=1; 35 for(int j=tp;j>=1;j--) 36 for(int i=0;i<=18;i++) 37 if((j|bin[i])<=tp) 38 vi[j]|=vi[j|bin[i]]; 39 f[0]=0; 40 for(int j=1;j<=tp;j++) 41 for(int i=j;i;(--i)&=j) 42 { 43 if(f[j]<=(j^i)) break; 44 if(vi[i]) f[j]=minn(f[j],f[j^i]+(j^i)); 45 } 46 ans+=f[tp]; 47 printf("%lld",ans); 48 return 0; 49 }
B
比赛的时候看完题就想说计算几何我拒绝……不过改题的过程中发现它并不是普通的计算几何,只是用到了一些几何知识;似乎暑假里讲计算几何的时候学长说这个知识点很难和其他板块联系起来,现在看来这种情况是在渐渐被改变吧。
Subtask1是开玩笑的输出两点之间距离……更搞笑的是样例二的答案也是两点之间距离……唯一有意义的部分分是Subtask4,保证一直有一点在端点,考试的时候理解的仿佛是一个点一直在叶子这也是十分有毒……二分答案,用$f[i][j]$表示一点在$i$另一点在$j$是否可行,初始化$f[stx][sty]=1$。只要一点在端点另一点移动时最大距离一定是两点距离,从初始状态向外选择满足限制的状态DFS即可,复杂度$n^2log$。
从刚才那个算法拓展出正解,现在两点可以同时在边上移动,我们要加入边的状态了。用$f[i][j]$表示一点在$i$另一点在边$j$上离$i$最近的点是否可行,初始化就是点$stx$和与$sty$相连的所有边都可行,反向同理。转移还是引用吧……感觉不能比原题解说得更清楚了
f[i][j]为1当且仅当点i到边j的最短距离≤mid且存在某个f[k][x]为1,其中k是j的某个端点,x是某个以i为端点的边;或存在某个f[t][j]为1,其中t与i有边相连。直接BFS转移就可以了。
所以所谓的几何知识就是求点到线段距离啦……我分类讨论得很麻烦,而且是用DFS实现的。复杂度还是$n^2log$的。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #define eps 1e-8 6 using namespace std; 7 const int sj=1010; 8 int n,stx,sty,h[sj],e,a1,a2,d[sj]; 9 double le,ri,mid; 10 bool vi[sj][sj]; 11 struct point 12 { 13 double xa,ya; 14 }p[sj]; 15 struct B 16 { 17 int ne,v; 18 }b[sj<<1]; 19 void add(int x,int y) 20 { 21 b[e].v=y,b[e].ne=h[x],h[x]=e++; 22 b[e].v=x,b[e].ne=h[y],h[y]=e++; 23 } 24 double dis(point x,point y) 25 { 26 double ret=(x.xa-y.xa)*(x.xa-y.xa)+(x.ya-y.ya)*(x.ya-y.ya); 27 return sqrt(ret); 28 } 29 bool dfs(int x,int y) 30 { 31 if(d[x]==1&&d[y]==1) return 1; 32 if(vi[x][y]) return 0; 33 vi[x][y]=vi[y][x]=1; 34 for(int i=h[x];i!=-1;i=b[i].ne) 35 if(dis(p[b[i].v],p[y])<=mid) 36 if(dfs(b[i].v,y)) 37 return 1; 38 for(int i=h[y];i!=-1;i=b[i].ne) 39 if(dis(p[b[i].v],p[x])<=mid) 40 if(dfs(b[i].v,x)) 41 return 1; 42 return 0; 43 } 44 int main() 45 { 46 memset(h,-1,sizeof(h)); 47 scanf("%d%d%d",&n,&stx,&sty); 48 for(int i=1;i<=n;i++) scanf("%lf%lf",&p[i].xa,&p[i].ya); 49 for(int i=1;i<n;i++) 50 { 51 scanf("%d%d",&a1,&a2); 52 add(a1,a2),d[a1]++,d[a2]++; 53 } 54 if(d[stx]==1&&d[sty]==1) 55 { 56 printf("%.10lf",dis(p[stx],p[sty])); 57 return 0; 58 } 59 le=dis(p[stx],p[sty]),ri=2e9; 60 while(le<ri-eps) 61 { 62 mid=(le+ri)/2.0; 63 memset(vi,0,sizeof(vi)); 64 if(dfs(stx,sty)) ri=mid; 65 else le=mid; 66 } 67 printf("%.10lf",le); 68 return 0; 69 }
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #define eps 1e-8 6 using namespace std; 7 const int sj=1010; 8 int n,stx,sty,h[sj],e,a1,a2,d[sj]; 9 bool vi[sj][sj]; 10 double le,ri,mid; 11 struct point 12 { 13 double xa,ya; 14 friend point operator - (point x,point y) 15 { return (point){x.xa-y.xa,x.ya-y.ya}; } 16 friend double operator * (point x,point y) 17 { return x.xa*y.ya-x.ya*y.xa; } 18 }p[sj]; 19 struct B 20 { 21 int ne,v,u; 22 }b[sj<<1]; 23 void add(int x,int y) 24 { 25 b[e].u=x,b[e].v=y,b[e].ne=h[x],h[x]=e++; 26 b[e].u=y,b[e].v=x,b[e].ne=h[y],h[y]=e++; 27 } 28 double dis(point x,point y) 29 { 30 double ret=(x.xa-y.xa)*(x.xa-y.xa)+(x.ya-y.ya)*(x.ya-y.ya); 31 return sqrt(ret); 32 } 33 double minn(double x,double y) 34 { 35 return x<y?x:y; 36 } 37 double abss(double x) 38 { 39 return x>0?x:-x; 40 } 41 double di(point x,point y,point z) 42 { 43 double d1=z.xa-y.xa,d2=z.ya-y.ya; 44 if(d1==0) 45 { 46 if((x.ya-y.ya)*(x.ya-z.ya)<=0) 47 return abss(y.xa-x.xa); 48 return minn(dis(x,z),dis(x,y)); 49 } 50 if(d2==0) 51 { 52 if((x.xa-y.xa)*(x.xa-z.xa)<=0) 53 return abss(y.ya-x.ya); 54 return minn(dis(x,z),dis(x,y)); 55 } 56 double xi=(y.ya-d2/d1*y.xa-x.ya+d1/(-d2)*x.xa)/(d1/(-d2)-d2/d1); 57 if((y.xa-xi)*(z.xa-xi)>0) return minn(dis(x,z),dis(x,y)); 58 double dot=(y-x)*(z-x); 59 if(dot<0) dot=-dot; 60 return dot/dis(y,z); 61 } 62 bool dfs(int x,int y) 63 { 64 if(vi[x][y]) return 0; 65 vi[x][y]=1; 66 int u1=b[x<<1].u,v1=b[x<<1].v; 67 if(d[u1]==1&&d[y]==1) 68 if(dis(p[u1],p[y])<=mid) 69 return 1; 70 if(d[v1]==1&&d[y]==1) 71 if(dis(p[v1],p[y])<=mid) 72 return 1; 73 for(int i=h[y];i!=-1;i=b[i].ne) 74 { 75 if(di(p[u1],p[b[i].u],p[b[i].v])<=mid&&dfs(i/2,u1)) return 1; 76 if(di(p[v1],p[b[i].u],p[b[i].v])<=mid&&dfs(i/2,v1)) return 1; 77 if(di(p[b[i].v],p[u1],p[v1])<=mid&&dfs(x,b[i].v)) return 1; 78 } 79 return 0; 80 } 81 bool check(double mid) 82 { 83 memset(vi,0,sizeof(vi)); 84 for(int i=h[stx];i!=-1;i=b[i].ne) 85 if(dfs(i/2,sty)) 86 return 1; 87 for(int i=h[sty];i!=-1;i=b[i].ne) 88 if(dfs(i/2,stx)) 89 return 1; 90 return 0; 91 } 92 int main() 93 { 94 memset(h,-1,sizeof(h)),e=2; 95 scanf("%d%d%d",&n,&stx,&sty); 96 for(int i=1;i<=n;i++) scanf("%lf%lf",&p[i].xa,&p[i].ya); 97 for(int i=1;i<n;i++) 98 { 99 scanf("%d%d",&a1,&a2); 100 add(a1,a2),d[a1]++,d[a2]++; 101 } 102 if(d[stx]==1&&d[sty]==1) 103 { 104 printf("%.10lf",dis(p[stx],p[sty])); 105 return 0; 106 } 107 le=dis(p[stx],p[sty]),ri=2e9; 108 while(le<ri-eps) 109 { 110 mid=(le+ri)/2.0; 111 if(check(mid)) ri=mid; 112 else le=mid; 113 } 114 printf("%.10lf",le); 115 return 0; 116 }
C
看到那个均匀分布就感觉这题不可做,而且至今我也不会改……考场上的骗分部分只有Subtask1,每条边都有一端为$1$则答案$=1$的期望$+max(2,3,4,5…n)$的期望$=frac{1}{2}+frac{n-1}{n}$。积分这个东西确实不大会啊。