APIO2019题解
T1.桥梁(bridges/restriction)
Subtask1:暴力,$O(n^2)$。
1 #include<cstdio> 2 #include<algorithm> 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 4 #define For(i,x) for (int i=h[x],k; i; i=nxt[i]) 5 using namespace std; 6 7 const int N=100010; 8 int n,m,u,v,d,cnt,T,op,x,y,q[N],vis[N],e[N],h[N],to[N<<1],nxt[N<<1],val[N<<1]; 9 10 void add(int u,int v,int w){ to[++cnt]=v; nxt[cnt]=h[u]; val[cnt]=w; h[u]=cnt; } 11 12 int bfs(int S,int w){ 13 rep(i,1,n) vis[i]=0; 14 q[1]=S; vis[S]=1; int st=0,ed=1; 15 while (st!=ed){ 16 int x=q[++st]; 17 For(i,x) if (e[val[i]]>=w && !vis[k=to[i]]) vis[k]=1,q[++ed]=k; 18 } 19 return ed; 20 } 21 22 int main(){ 23 freopen("restriction.in","r",stdin); 24 freopen("restriction.out","w",stdout); 25 scanf("%d%d",&n,&m); 26 rep(i,1,m) scanf("%d%d%d",&u,&v,&d),e[i]=d,add(u,v,i),add(v,u,i); 27 for (scanf("%d",&T); T--; ){ 28 scanf("%d%d%d",&op,&x,&y); 29 if (op==1) e[x]=y; else printf("%d\n",bfs(x,y)); 30 } 31 return 0; 32 }
Subtask2:一条链,对于每个询问我们可以二分它能到的左右端点,然后线段树求区间最小值判断是否可行。应该可以线段树上二分但是不太好写,就写了$O(n\log^2n)$的。
1 #include<cstdio> 2 #include<algorithm> 3 #define ls (x<<1) 4 #define rs (ls|1) 5 #define lson ls,L,mid 6 #define rson rs,mid+1,R 7 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 8 #define For(i,x) for (int i=h[x],k; i; i=nxt[i]) 9 using namespace std; 10 11 const int N=100010; 12 int n,m,T,op,x,y,a[N],mn[N<<2]; 13 14 void build(int x,int L,int R){ 15 if (L==R){ mn[x]=a[L]; return; } 16 int mid=(L+R)>>1; 17 build(lson); build(rson); mn[x]=min(mn[ls],mn[rs]); 18 } 19 20 void mdf(int x,int L,int R,int p,int k){ 21 if (L==R){ mn[x]=k; return; } 22 int mid=(L+R)>>1; 23 if (p<=mid) mdf(lson,p,k); else mdf(rson,p,k); 24 mn[x]=min(mn[ls],mn[rs]); 25 } 26 27 int que(int x,int L,int R,int l,int r){ 28 if (L==l && r==R) return mn[x]; 29 int mid=(L+R)>>1; 30 if (r<=mid) return que(lson,l,r); 31 else if (l>mid) return que(rson,l,r); 32 else return min(que(lson,l,mid),que(rson,mid+1,r)); 33 } 34 35 int main(){ 36 freopen("restriction.in","r",stdin); 37 freopen("restriction.out","w",stdout); 38 scanf("%d%d",&n,&m); 39 rep(i,1,m) scanf("%*d%*d%d",&x),a[i]=x; 40 if (m) build(1,1,m); 41 for (scanf("%d",&T); T--; ){ 42 scanf("%d%d%d",&op,&x,&y); 43 if (op==1){ if (m) mdf(1,1,m,x,y); } 44 else{ 45 if (n==1){ puts("1"); continue; } 46 int L=x,R=n; 47 while (L<R){ 48 int mid=(L+R+1)>>1; 49 if (mid==x || que(1,1,m,x,mid-1)>=y) L=mid; else R=mid-1; 50 } 51 int rr=L; L=1,R=x; 52 while (L<R){ 53 int mid=(L+R)>>1; 54 if (mid==x || que(1,1,m,mid,x-1)>=y) R=mid; else L=mid+1; 55 } 56 int ll=L; printf("%d\n",rr-ll+1); 57 } 58 } 59 return 0; 60 }
Subtask4:只有询问,相当于[NOI2018归程]弱化版。询问离线按重量从大到小排序,边也按限重从大到小排序,每次将能承受当前询问重量的边全部加入,然后带权并查集求连通块内点的个数即可,$O(n\log n)$。
1 #include<cstdio> 2 #include<algorithm> 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 4 #define For(i,x) for (int i=h[x],k; i; i=nxt[i]) 5 using namespace std; 6 7 const int N=100010; 8 int n,m,u,v,d,T,Q,ans[N],sz[N],fa[N]; 9 struct E{ int u,v,d; }e[N]; 10 bool operator <(const E &a,const E &b){ return a.d>b.d; } 11 struct P{ int x,d,id; }p[N]; 12 bool operator <(const P &a,const P &b){ return a.d>b.d; } 13 14 int get(int x){ return x==fa[x] ? x : fa[x]=get(fa[x]); } 15 16 int main(){ 17 freopen("restriction.in","r",stdin); 18 freopen("restriction.out","w",stdout); 19 scanf("%d%d",&n,&m); 20 rep(i,1,m) scanf("%d%d%d",&u,&v,&d),e[i]=(E){u,v,d}; 21 scanf("%d",&Q); 22 rep(i,1,Q) scanf("%*d%d%d",&p[i].x,&p[i].d),p[i].id=i; 23 sort(p+1,p+Q+1); sort(e+1,e+m+1); int r=0; 24 rep(i,1,n) fa[i]=i,sz[i]=1; 25 rep(i,1,Q){ 26 while (r<m && e[r+1].d>=p[i].d){ 27 r++; int u=get(e[r].u),v=get(e[r].v); 28 if (u!=v) sz[v]+=sz[u],fa[u]=v; 29 } 30 ans[p[i].id]=sz[get(p[i].x)]; 31 } 32 rep(i,1,Q) printf("%d\n",ans[i]); 33 return 0; 34 }
100pts:如果做过[HNOI2016]最小共倍数,并在考场上想到分块做法,这个题就很简单了。
将所有询问和修改放在一起按时间分块,每次处理一个块时,先将这个块之前的所有修改操作全部做完,再将所有边按限重从大到小排序。接着找出所有当前块中修改涉及到的边,把剩下的边全部加入图中。然后依次遍历这个块中的所有询问,对于每个询问,遍历块中所有修改涉及的边并判断它能否加入图中(即当前询问前修改成的那个限重是否大于当前询问的重量),同样用带权并查集支持。求出这个询问的结果后再依次撤销当前块的这些修改涉及的边的影响,以便处理下个询问。$O(n\sqrt{n}\log n)$。(网上有说可以做到$O(n\sqrt{n\log n})$但我没有搞清楚)。
1 #include<cstdio> 2 #include<algorithm> 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 4 using namespace std; 5 6 const int N=100010,B=320,M=400; 7 int n,m,Q,op,rem,x,y,top,ans[N],pos[N],fa[N],sz[N],b[N],id[N],val[M]; 8 struct E{ int x,y,w,id; }e[N],e2[M],t1[N],t2[M]; 9 bool operator <(const E &a,const E &b){ return a.w>b.w; } 10 struct P{ int x,y; }st[N]; 11 struct Op{ int id,w,t; }p[M]; 12 struct Que{ int x,w,t; }q[M]; 13 bool operator <(const Que &a,const Que &b){ return a.w>b.w; } 14 15 int get(int x){ return x==fa[x] ? x : get(fa[x]); } 16 17 void merge(int x,int y){ 18 x=get(x); y=get(y); 19 if (x==y) return; 20 if (sz[x]<sz[y]) swap(x,y); 21 sz[x]+=sz[y]; fa[y]=x; 22 if (rem) st[++top]=(P){x,y}; 23 } 24 25 void roll(){ P t=st[top--]; sz[t.x]-=sz[t.y]; fa[t.y]=t.y; } 26 27 int main(){ 28 freopen("restriction.in","r",stdin); 29 freopen("restriction.out","w",stdout); 30 scanf("%d%d",&n,&m); 31 rep(i,1,m) scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].w),e[i].id=i; 32 sort(e+1,e+m+1); 33 rep(i,1,m) pos[e[i].id]=i; 34 scanf("%d",&Q); 35 for (int lst=1; lst<=Q; lst+=B){ 36 int ln=min(Q-lst+1,B); int pn=0,qn=0; 37 rep(i,0,ln-1){ 38 scanf("%d%d%d",&op,&x,&y); 39 if (op==1) p[++pn]=(Op){pos[x],y,lst+i}; else q[++qn]=(Que){x,y,lst+i}; 40 } 41 sort(q+1,q+qn+1); int m2=0,r=1; 42 rep(i,1,pn) if (!b[p[i].id]) id[p[i].id]=++m2,e2[m2]=e[p[i].id],val[m2]=e2[m2].w,b[p[i].id]=1; 43 rep(i,1,n) fa[i]=i,sz[i]=1; 44 rep(i,1,qn){ 45 rem=0; 46 for (; r<=m && e[r].w>=q[i].w; r++) if (!b[r]) merge(e[r].x,e[r].y); 47 for (int k=1; k<=pn && p[k].t<q[i].t; k++) val[id[p[k].id]]=p[k].w; 48 rem=1; 49 rep(k,1,m2){ 50 if (val[k]>=q[i].w) merge(e2[k].x,e2[k].y); 51 val[k]=e2[k].w; 52 } 53 ans[q[i].t]=sz[get(q[i].x)]; 54 while (top) roll(); 55 } 56 rep(i,1,pn) e[p[i].id].w=p[i].w; 57 int n1=0,n2=0; 58 rep(i,1,m) if (b[i]) t2[++n2]=e[i],b[i]=0; else t1[++n1]=e[i]; 59 sort(t2+1,t2+n2+1); merge(t1+1,t1+n1+1,t2+1,t2+n2+1,e+1); 60 rep(i,1,m) pos[e[i].id]=i; 61 } 62 rep(i,1,Q) if (ans[i]) printf("%d\n",ans[i]); 63 return 0; 64 }
T2.奇怪装置(device)
简单推下式子:若存在t1,t2使得x1=x2,y1=y2,则由第二个式子得:$t1\equiv t2 (\text{mod}\ B)$。故设$t2=t1+kB$,再代入第一个式子:$(t1+\lfloor\frac{t1}{B}\rfloor)\text{mod}\ A=(t1+kB+\lfloor\frac{t1+kB}{B}\rfloor)\text{mod}\ A$,即$k(B+1)\equiv 0(\text{mod}\ A)$,则显然k最小为$\frac{A}{gcd(A,B+1)}$,故$t1\equiv t2(\text{mod}\ \frac{AB}{gcd(A,B+1)})$。
于是每个题给区间都可以对应到模$\frac{AB}{gcd(A,B+1)}$意义下的一或两个线段,于是线段求并即可。$O(n\log n)$。
1 #include<cstdio> 2 #include<algorithm> 3 typedef long long ll; 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 5 using namespace std; 6 7 const int N=2000010; 8 ll n,A,B,k,l,r,tot,ans; 9 struct P{ ll l,r; }q[N]; 10 bool operator <(const P &a,const P &b){ return a.l<b.l; } 11 12 ll gcd(ll a,ll b){ return b ? gcd(b,a%b) : a; } 13 14 int main(){ 15 freopen("device.in","r",stdin); 16 freopen("device.out","w",stdout); 17 scanf("%lld%lld%lld",&n,&A,&B); k=(A/gcd(A,B+1))*B; 18 rep(i,1,n){ 19 scanf("%lld%lld",&l,&r); 20 if (r-l+1>=k){ printf("%lld\n",k); return 0; } 21 if (l==r){ q[++tot]=(P){l%k,l%k}; continue; } 22 if (l%k<=r%k){ q[++tot]=(P){l%k,r%k}; continue; } 23 else q[++tot]=(P){l%k,k-1},q[++tot]=(P){0,r%k}; 24 } 25 sort(q+1,q+tot+1); l=q[1].l; r=q[1].r; 26 rep(i,2,tot) if (q[i].l>r) ans+=r-l+1,l=q[i].l,r=q[i].r; else r=max(r,q[i].r); 27 printf("%lld\n",ans+r-l+1); 28 return 0; 29 }
T3.路灯(lamps/light)
Subtask1:暴力,$O(n^3)$。
1 #include<cstdio> 2 #include<algorithm> 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 4 using namespace std; 5 6 const int N=3010; 7 char s[N],op[N]; 8 int n,Q,tot,x,y,a[N][N]; 9 10 int main(){ 11 freopen("light.in","r",stdin); 12 freopen("light.out","w",stdout); 13 scanf("%d%d%s",&n,&Q,s+1); tot=1; 14 rep(i,1,n) a[0][i]=s[i]=='1'; 15 rep(tt,1,Q){ 16 scanf("%s%d",op,&x); 17 rep(i,1,n) a[tt][i]=a[tt-1][i]; 18 if (op[0]=='t') a[tt][x]^=1; 19 else{ 20 scanf("%d",&y); int res=0; 21 rep(t,0,tt-1){ 22 bool flag=0; 23 rep(i,x,y-1) if (!a[t][i]){ flag=1; break; } 24 if (!flag) res++; 25 } 26 printf("%d\n",res); 27 } 28 } 29 return 0; 30 }
Subtask2:对每个位置统计为1的时间,$O(n)$。
1 #include<cstdio> 2 #include<algorithm> 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 4 using namespace std; 5 6 const int N=300010; 7 char s[N],op[10]; 8 int n,Q,x,a[N],num[N],lst[N]; 9 10 int main(){ 11 freopen("light.in","r",stdin); 12 freopen("light.out","w",stdout); 13 scanf("%d%d%s",&n,&Q,s+1); 14 rep(i,1,n) if (s[i]=='1') a[i]=1; 15 rep(i,1,Q){ 16 scanf("%s%d",op,&x); 17 if (op[0]=='t'){ 18 if (a[x]) a[x]=0,num[x]+=i-lst[x],lst[x]=i; else a[x]=1,lst[x]=i; 19 }else scanf("%*d"),printf("%d\n",num[x]+(i-lst[x])*a[x]); 20 } 21 return 0; 22 }
Subtask3:每个位置记录被点亮的时间,问题就变成区间最大值了,$O(n\log n)$。
1 #include<cstdio> 2 #include<algorithm> 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 4 using namespace std; 5 6 const int N=300010; 7 char s[N],op[10]; 8 int n,Q,x,y,tot,lg[N],tim[N],mx[N][20]; 9 struct P{ int t,x,y; }q[N]; 10 11 int que(int l,int r){ int t=lg[r-l+1]; return max(mx[l][t],mx[r-(1<<t)+1][t]); } 12 13 int main(){ 14 freopen("light.in","r",stdin); 15 freopen("light.out","w",stdout); 16 scanf("%d%d%s",&n,&Q,s+1); lg[1]=0; 17 rep(i,2,n) lg[i]=lg[i>>1]+1; 18 rep(i,1,n) if (s[i]=='1') tim[i]=0; else tim[i]=Q+1; 19 rep(i,1,Q){ 20 scanf("%s%d",op,&x); 21 if (op[0]=='t') tim[x]=i; else scanf("%d",&y),q[++tot]=(P){i,x,y}; 22 } 23 rep(i,1,n) mx[i][0]=tim[i]; 24 rep(j,1,19) rep(i,1,n-(1<<j)+1) mx[i][j]=max(mx[i][j-1],mx[i+(1<<(j-1))][j-1]); 25 rep(i,1,tot) printf("%d\n",max(q[i].t-que(q[i].x,q[i].y-1),0)); 26 return 0; 27 }
100pts:首先一个常用套路是,计算一个状态的延续时间,往往在其开始时给它减去开始时间,结束时给它加上结束时间。
用set维护当前所有0的位置,然后发现每当一个位置被反转时,都有一些左右端点在某个区间中的询问状态会被反转(具体自己推一下,跟其位置在set上的前驱后继有关),左右端点分别投影到x,y轴上就变成了经典的二维区间修改区间求和问题了。
做法很多,这里用了树状数组套线段树,时空复杂度都是$O(n\log^2n)$。
1 #include<set> 2 #include<cstdio> 3 #include<algorithm> 4 #define lson ls[x],L,mid 5 #define rson rs[x],mid+1,R 6 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 7 using namespace std; 8 9 const int N=300010,M=15000010; 10 char str[N],op[10]; 11 int n,Q,nd,x,y,a[N],rt[N],ls[M],rs[M],s[M]; 12 set<int>S; 13 typedef set<int>::iterator It; 14 15 void add0(int &x,int L,int R,int p,int k){ 16 if (!x) x=++nd; 17 s[x]+=k; 18 if (L==R) return; 19 int mid=(L+R)>>1; 20 if (p<=mid) add0(lson,p,k); else add0(rson,p,k); 21 } 22 23 int que0(int x,int L,int R,int p){ 24 if (!x || p<L) return 0; 25 if (p==R) return s[x]; 26 int mid=(L+R)>>1; 27 if (p<=mid) return que0(lson,p); else return s[ls[x]]+que0(rson,p); 28 } 29 30 void add(int x,int y,int k){ for (; x<=n+1; x+=x&-x) add0(rt[x],1,n+1,y,k); } 31 int que(int x,int y){ int res=0; for (; x; x-=x&-x) res+=que0(rt[x],1,n+1,y); return res; } 32 33 int main(){ 34 freopen("light.in","r",stdin); 35 freopen("light.out","w",stdout); 36 scanf("%d%d%s",&n,&Q,str+1); 37 add(1,1,Q); S.insert(0); int lst=1; 38 rep(i,1,n){ 39 a[i]=str[i]-'0'; 40 if (a[i]) continue; 41 S.insert(i); add(lst,i+1,-Q); add(i+1,i+1,Q); lst=i+1; 42 } 43 S.insert(n+1); 44 while (Q--){ 45 scanf("%s%d",op,&x); 46 if (op[0]=='t'){ 47 if (a[x]){ 48 It r=S.lower_bound(x),l=r; l--; 49 add(*l+1,x+1,-Q); add(x+1,x+1,Q); 50 if (*r<=n) add(*l+1,*r+1,Q),add(x+1,*r+1,-Q); 51 S.insert(x); 52 }else{ 53 It r=S.find(x),l=r; r++; l--; 54 add(*l+1,x+1,Q); add(x+1,x+1,-Q); 55 if (*r<=n) add(*l+1,*r+1,-Q),add(x+1,*r+1,Q); 56 S.erase(--r); 57 } 58 a[x]^=1; 59 }else{ 60 scanf("%d",&y); int res=que(x,y); 61 if (S.lower_bound(x)==S.lower_bound(y)) res-=Q; 62 printf("%d\n",res); 63 } 64 } 65 return 0; 66 }