T1.桥梁(bridges/restriction)

Subtask1:暴力,$O(n^2)$。

 #include<cstdio>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
#define For(i,x) for (int i=h[x],k; i; i=nxt[i])
using namespace std; const int N=;
int n,m,u,v,d,cnt,T,op,x,y,q[N],vis[N],e[N],h[N],to[N<<],nxt[N<<],val[N<<]; void add(int u,int v,int w){ to[++cnt]=v; nxt[cnt]=h[u]; val[cnt]=w; h[u]=cnt; } int bfs(int S,int w){
rep(i,,n) vis[i]=;
q[]=S; vis[S]=; int st=,ed=;
while (st!=ed){
int x=q[++st];
For(i,x) if (e[val[i]]>=w && !vis[k=to[i]]) vis[k]=,q[++ed]=k;
}
return ed;
} int main(){
freopen("restriction.in","r",stdin);
freopen("restriction.out","w",stdout);
scanf("%d%d",&n,&m);
rep(i,,m) scanf("%d%d%d",&u,&v,&d),e[i]=d,add(u,v,i),add(v,u,i);
for (scanf("%d",&T); T--; ){
scanf("%d%d%d",&op,&x,&y);
if (op==) e[x]=y; else printf("%d\n",bfs(x,y));
}
return ;
}

13pts

Subtask2:一条链,对于每个询问我们可以二分它能到的左右端点,然后线段树求区间最小值判断是否可行。应该可以线段树上二分但是不太好写,就写了$O(n\log^2n)$的。

 #include<cstdio>
#include<algorithm>
#define ls (x<<1)
#define rs (ls|1)
#define lson ls,L,mid
#define rson rs,mid+1,R
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
#define For(i,x) for (int i=h[x],k; i; i=nxt[i])
using namespace std; const int N=;
int n,m,T,op,x,y,a[N],mn[N<<]; void build(int x,int L,int R){
if (L==R){ mn[x]=a[L]; return; }
int mid=(L+R)>>;
build(lson); build(rson); mn[x]=min(mn[ls],mn[rs]);
} void mdf(int x,int L,int R,int p,int k){
if (L==R){ mn[x]=k; return; }
int mid=(L+R)>>;
if (p<=mid) mdf(lson,p,k); else mdf(rson,p,k);
mn[x]=min(mn[ls],mn[rs]);
} int que(int x,int L,int R,int l,int r){
if (L==l && r==R) return mn[x];
int mid=(L+R)>>;
if (r<=mid) return que(lson,l,r);
else if (l>mid) return que(rson,l,r);
else return min(que(lson,l,mid),que(rson,mid+,r));
} int main(){
freopen("restriction.in","r",stdin);
freopen("restriction.out","w",stdout);
scanf("%d%d",&n,&m);
rep(i,,m) scanf("%*d%*d%d",&x),a[i]=x;
if (m) build(,,m);
for (scanf("%d",&T); T--; ){
scanf("%d%d%d",&op,&x,&y);
if (op==){ if (m) mdf(,,m,x,y); }
else{
if (n==){ puts(""); continue; }
int L=x,R=n;
while (L<R){
int mid=(L+R+)>>;
if (mid==x || que(,,m,x,mid-)>=y) L=mid; else R=mid-;
}
int rr=L; L=,R=x;
while (L<R){
int mid=(L+R)>>;
if (mid==x || que(,,m,mid,x-)>=y) R=mid; else L=mid+;
}
int ll=L; printf("%d\n",rr-ll+);
}
}
return ;
}

16pts

Subtask4:只有询问,相当于[NOI2018归程]弱化版。询问离线按重量从大到小排序,边也按限重从大到小排序,每次将能承受当前询问重量的边全部加入,然后带权并查集求连通块内点的个数即可,$O(n\log n)$。

 #include<cstdio>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
#define For(i,x) for (int i=h[x],k; i; i=nxt[i])
using namespace std; const int N=;
int n,m,u,v,d,T,Q,ans[N],sz[N],fa[N];
struct E{ int u,v,d; }e[N];
bool operator <(const E &a,const E &b){ return a.d>b.d; }
struct P{ int x,d,id; }p[N];
bool operator <(const P &a,const P &b){ return a.d>b.d; } int get(int x){ return x==fa[x] ? x : fa[x]=get(fa[x]); } int main(){
freopen("restriction.in","r",stdin);
freopen("restriction.out","w",stdout);
scanf("%d%d",&n,&m);
rep(i,,m) scanf("%d%d%d",&u,&v,&d),e[i]=(E){u,v,d};
scanf("%d",&Q);
rep(i,,Q) scanf("%*d%d%d",&p[i].x,&p[i].d),p[i].id=i;
sort(p+,p+Q+); sort(e+,e+m+); int r=;
rep(i,,n) fa[i]=i,sz[i]=;
rep(i,,Q){
while (r<m && e[r+].d>=p[i].d){
r++; int u=get(e[r].u),v=get(e[r].v);
if (u!=v) sz[v]+=sz[u],fa[u]=v;
}
ans[p[i].id]=sz[get(p[i].x)];
}
rep(i,,Q) printf("%d\n",ans[i]);
return ;
}

14pts

100pts:如果做过[HNOI2016]最小共倍数,并在考场上想到分块做法,这个题就很简单了。

将所有询问和修改放在一起按时间分块,每次处理一个块时,先将这个块之前的所有修改操作全部做完,再将所有边按限重从大到小排序。接着找出所有当前块中修改涉及到的边,把剩下的边全部加入图中。然后依次遍历这个块中的所有询问,对于每个询问,遍历块中所有修改涉及的边并判断它能否加入图中(即当前询问前修改成的那个限重是否大于当前询问的重量),同样用带权并查集支持。求出这个询问的结果后再依次撤销当前块的这些修改涉及的边的影响,以便处理下个询问。$O(n\sqrt{n}\log n)$。(网上有说可以做到$O(n\sqrt{n\log n})$但我没有搞清楚)。

 #include<cstdio>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int N=,B=,M=;
int n,m,Q,op,rem,x,y,top,ans[N],pos[N],fa[N],sz[N],b[N],id[N],val[M];
struct E{ int x,y,w,id; }e[N],e2[M],t1[N],t2[M];
bool operator <(const E &a,const E &b){ return a.w>b.w; }
struct P{ int x,y; }st[N];
struct Op{ int id,w,t; }p[M];
struct Que{ int x,w,t; }q[M];
bool operator <(const Que &a,const Que &b){ return a.w>b.w; } int get(int x){ return x==fa[x] ? x : get(fa[x]); } void merge(int x,int y){
x=get(x); y=get(y);
if (x==y) return;
if (sz[x]<sz[y]) swap(x,y);
sz[x]+=sz[y]; fa[y]=x;
if (rem) st[++top]=(P){x,y};
} void roll(){ P t=st[top--]; sz[t.x]-=sz[t.y]; fa[t.y]=t.y; } int main(){
freopen("restriction.in","r",stdin);
freopen("restriction.out","w",stdout);
scanf("%d%d",&n,&m);
rep(i,,m) scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].w),e[i].id=i;
sort(e+,e+m+);
rep(i,,m) pos[e[i].id]=i;
scanf("%d",&Q);
for (int lst=; lst<=Q; lst+=B){
int ln=min(Q-lst+,B); int pn=,qn=;
rep(i,,ln-){
scanf("%d%d%d",&op,&x,&y);
if (op==) p[++pn]=(Op){pos[x],y,lst+i}; else q[++qn]=(Que){x,y,lst+i};
}
sort(q+,q+qn+); int m2=,r=;
rep(i,,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]=;
rep(i,,n) fa[i]=i,sz[i]=;
rep(i,,qn){
rem=;
for (; r<=m && e[r].w>=q[i].w; r++) if (!b[r]) merge(e[r].x,e[r].y);
for (int k=; k<=pn && p[k].t<q[i].t; k++) val[id[p[k].id]]=p[k].w;
rem=;
rep(k,,m2){
if (val[k]>=q[i].w) merge(e2[k].x,e2[k].y);
val[k]=e2[k].w;
}
ans[q[i].t]=sz[get(q[i].x)];
while (top) roll();
}
rep(i,,pn) e[p[i].id].w=p[i].w;
int n1=,n2=;
rep(i,,m) if (b[i]) t2[++n2]=e[i],b[i]=; else t1[++n1]=e[i];
sort(t2+,t2+n2+); merge(t1+,t1+n1+,t2+,t2+n2+,e+);
rep(i,,m) pos[e[i].id]=i;
}
rep(i,,Q) if (ans[i]) printf("%d\n",ans[i]);
return ;
}

100pts

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)$。

 #include<cstdio>
#include<algorithm>
typedef long long ll;
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int N=;
ll n,A,B,k,l,r,tot,ans;
struct P{ ll l,r; }q[N];
bool operator <(const P &a,const P &b){ return a.l<b.l; } ll gcd(ll a,ll b){ return b ? gcd(b,a%b) : a; } int main(){
freopen("device.in","r",stdin);
freopen("device.out","w",stdout);
scanf("%lld%lld%lld",&n,&A,&B); k=(A/gcd(A,B+))*B;
rep(i,,n){
scanf("%lld%lld",&l,&r);
if (r-l+>=k){ printf("%lld\n",k); return ; }
if (l==r){ q[++tot]=(P){l%k,l%k}; continue; }
if (l%k<=r%k){ q[++tot]=(P){l%k,r%k}; continue; }
else q[++tot]=(P){l%k,k-},q[++tot]=(P){,r%k};
}
sort(q+,q+tot+); l=q[].l; r=q[].r;
rep(i,,tot) if (q[i].l>r) ans+=r-l+,l=q[i].l,r=q[i].r; else r=max(r,q[i].r);
printf("%lld\n",ans+r-l+);
return ;
}

100pts

T3.路灯(lamps/light)

Subtask1:暴力,$O(n^3)$。

 #include<cstdio>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int N=;
char s[N],op[N];
int n,Q,tot,x,y,a[N][N]; int main(){
freopen("light.in","r",stdin);
freopen("light.out","w",stdout);
scanf("%d%d%s",&n,&Q,s+); tot=;
rep(i,,n) a[][i]=s[i]=='';
rep(tt,,Q){
scanf("%s%d",op,&x);
rep(i,,n) a[tt][i]=a[tt-][i];
if (op[]=='t') a[tt][x]^=;
else{
scanf("%d",&y); int res=;
rep(t,,tt-){
bool flag=;
rep(i,x,y-) if (!a[t][i]){ flag=; break; }
if (!flag) res++;
}
printf("%d\n",res);
}
}
return ;
}

20pts

Subtask2:对每个位置统计为1的时间,$O(n)$。

 #include<cstdio>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int N=;
char s[N],op[];
int n,Q,x,a[N],num[N],lst[N]; int main(){
freopen("light.in","r",stdin);
freopen("light.out","w",stdout);
scanf("%d%d%s",&n,&Q,s+);
rep(i,,n) if (s[i]=='') a[i]=;
rep(i,,Q){
scanf("%s%d",op,&x);
if (op[]=='t'){
if (a[x]) a[x]=,num[x]+=i-lst[x],lst[x]=i; else a[x]=,lst[x]=i;
}else scanf("%*d"),printf("%d\n",num[x]+(i-lst[x])*a[x]);
}
return ;
}

20pts

Subtask3:每个位置记录被点亮的时间,问题就变成区间最大值了,$O(n\log n)$。

 #include<cstdio>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int N=;
char s[N],op[];
int n,Q,x,y,tot,lg[N],tim[N],mx[N][];
struct P{ int t,x,y; }q[N]; int que(int l,int r){ int t=lg[r-l+]; return max(mx[l][t],mx[r-(<<t)+][t]); } int main(){
freopen("light.in","r",stdin);
freopen("light.out","w",stdout);
scanf("%d%d%s",&n,&Q,s+); lg[]=;
rep(i,,n) lg[i]=lg[i>>]+;
rep(i,,n) if (s[i]=='') tim[i]=; else tim[i]=Q+;
rep(i,,Q){
scanf("%s%d",op,&x);
if (op[]=='t') tim[x]=i; else scanf("%d",&y),q[++tot]=(P){i,x,y};
}
rep(i,,n) mx[i][]=tim[i];
rep(j,,) rep(i,,n-(<<j)+) mx[i][j]=max(mx[i][j-],mx[i+(<<(j-))][j-]);
rep(i,,tot) printf("%d\n",max(q[i].t-que(q[i].x,q[i].y-),));
return ;
}

20pts

100pts:首先一个常用套路是,计算一个状态的延续时间,往往在其开始时给它减去开始时间,结束时给它加上结束时间。

用set维护当前所有0的位置,然后发现每当一个位置被反转时,都有一些左右端点在某个区间中的询问状态会被反转(具体自己推一下,跟其位置在set上的前驱后继有关),左右端点分别投影到x,y轴上就变成了经典的二维区间修改区间求和问题了。

做法很多,这里用了树状数组套线段树,时空复杂度都是$O(n\log^2n)$。

 #include<set>
#include<cstdio>
#include<algorithm>
#define lson ls[x],L,mid
#define rson rs[x],mid+1,R
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int N=,M=;
char str[N],op[];
int n,Q,nd,x,y,a[N],rt[N],ls[M],rs[M],s[M];
set<int>S;
typedef set<int>::iterator It; void add0(int &x,int L,int R,int p,int k){
if (!x) x=++nd;
s[x]+=k;
if (L==R) return;
int mid=(L+R)>>;
if (p<=mid) add0(lson,p,k); else add0(rson,p,k);
} int que0(int x,int L,int R,int p){
if (!x || p<L) return ;
if (p==R) return s[x];
int mid=(L+R)>>;
if (p<=mid) return que0(lson,p); else return s[ls[x]]+que0(rson,p);
} void add(int x,int y,int k){ for (; x<=n+; x+=x&-x) add0(rt[x],,n+,y,k); }
int que(int x,int y){ int res=; for (; x; x-=x&-x) res+=que0(rt[x],,n+,y); return res; } int main(){
freopen("light.in","r",stdin);
freopen("light.out","w",stdout);
scanf("%d%d%s",&n,&Q,str+);
add(,,Q); S.insert(); int lst=;
rep(i,,n){
a[i]=str[i]-'';
if (a[i]) continue;
S.insert(i); add(lst,i+,-Q); add(i+,i+,Q); lst=i+;
}
S.insert(n+);
while (Q--){
scanf("%s%d",op,&x);
if (op[]=='t'){
if (a[x]){
It r=S.lower_bound(x),l=r; l--;
add(*l+,x+,-Q); add(x+,x+,Q);
if (*r<=n) add(*l+,*r+,Q),add(x+,*r+,-Q);
S.insert(x);
}else{
It r=S.find(x),l=r; r++; l--;
add(*l+,x+,Q); add(x+,x+,-Q);
if (*r<=n) add(*l+,*r+,-Q),add(x+,*r+,Q);
S.erase(--r);
}
a[x]^=;
}else{
scanf("%d",&y); int res=que(x,y);
if (S.lower_bound(x)==S.lower_bound(y)) res-=Q;
printf("%d\n",res);
}
}
return ;
}

100pts

APIO2019题解的更多相关文章

  1. APIO2019 题解

    APIO2019 题解 T1 奇怪装置 题目传送门 https://loj.ac/problem/3144 题解 很容易发现,这个东西一定会形成一个环.我们只需要求出环的长度就解决了一切问题. 设环的 ...

  2. APIO2019简要题解

    Luogu P5444 [APIO2019]奇怪装置 看到这种题,我们肯定会想到\((x,y)\)一定有循环 我们要找到循环节的长度 推一下发现\(x\)的循环节长为\(\frac{AB}{B+1}\ ...

  3. 题解-APIO2019路灯

    problem \(\mathtt {loj-3146}\) 题意概要:一条直线上有 \(n+1\) 个点和 \(n\) 条道路,每条道路连通相邻两个点.在 \(q\) 个时刻内,每个时刻有如下两种操 ...

  4. 题解-APIO2019桥梁

    problem \(\mathrm {loj-3145}\) 题意概要:给定一张 \(n\) 点 \(m\) 边的无向图,边有边权,共 \(q\) 次操作,每次会将第 \(x\) 条边的权值改为 \( ...

  5. 题解-APIO2019奇怪装置

    problem loj-3144 题意概要:设函数 \(f(t)\) 的返回值为一个二元组,即 \(f(t)=((t+\lfloor \frac tB\rfloor)\bmod A, t\bmod B ...

  6. 题解 洛谷 P5443 【[APIO2019]桥梁】

    考虑若只有查询操作,那么就可以构造\(Kruskal\)重构树,然后在线询问了,也可以更简单的把询问离线,把询问和边都按权值从大到小排序,然后双指针依次加入对于当前询问合法的边,用并查集维护每个点的答 ...

  7. 【LOJ#3146】[APIO2019]路灯(树套树)

    [LOJ#3146][APIO2019]路灯(树套树) 题面 LOJ 题解 考场上因为\(\text{bridge}\)某个\(\text{subtask}\)没有判\(n=1\)的情况导致我卡了\( ...

  8. 【LOJ#3145】[APIO2019]桥梁(分块,并查集)

    [LOJ#3145][APIO2019]桥梁(分块,并查集) 题面 LOJ 题解 因为某个\(\text{subtask}\)没判\(n=1\)的情况导致我自闭了很久的题目... 如果没有修改操作,可 ...

  9. 【LOJ#3144】[APIO2019]奇怪装置(数论)

    [LOJ#3144][APIO2019]奇怪装置(数论) 题面 LOJ 题解 突然发现\(LOJ\)上有\(APIO\)的题啦,赶快来做一做. 这题是窝考场上切了的题嗷.写完暴力之后再推了推就推出正解 ...

随机推荐

  1. CGLIB和Java动态代理的区别(笔记)

    java常用知识点: 1.Java动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承):CGLIB能够代理普通类:2.Jav ...

  2. 实验1c语言开发环境使用和数据类型,运算符和表达式

    /*this is first c program*/ # include<stdio.h> int main() { printf("Hello Mars!"); ; ...

  3. RSA加密公钥系数获取结果多00

    写在前面 本文是在解决加密和解密用的不是同一套密钥对时找到的一篇, 最后问题不在byte数组, 是自己工具类中生成密钥对的问题, 但是本文RSA加密中公钥指数和公钥系数的获取(byte[]部分)讲解比 ...

  4. 【phpstudy2016】apache配置Tp5.0,获取表单数据总是多了一个路由变量,解决

    1.用的apahce配置tp5.0的php环境 2.发现input()过来的数据,总是多了一个变量,那就是路由变量, 类似[array(2) { ["/index/index/form_su ...

  5. mysql中包含长字段索引的优化

    不同于oracle,在mysql的Innodb存储引擎中,对索引的总长度有限制.在mysql 5.7中(https://dev.mysql.com/doc/refman/5.7/en/innodb-r ...

  6. pom.xml activatedProperties --spring.profiles.active=uat 对应

    <profiles> <profile> <id>dev</id> <properties> <!-- 环境标识,需要与配置文件的名称 ...

  7. 笔记-JFB:业务流程梳理

    ylbtech-笔记-JFB:业务流程梳理 1.家政员找工作-->填写基本信息-->上岗申请--> 1.返回顶部 1. 2. 2. 2.返回顶部   3.返回顶部   4.返回顶部 ...

  8. Ubuntu 16.04 Roboware Turtlesim 测试

    博客参考:https://www.jianshu.com/p/5509c8ba522b?utm_campaign 利用Turtlesim,编写简单的消息发布器和订阅器 1. Twist消息,它的Top ...

  9. IfcColumn

    IfcColumn is a vertical structural member which often is aligned with a structural grid intersection ...

  10. k8s记录-k8s基本概念和术语

    每次个节点上当然都要运行Docker.Docker来负责所有具体的映像下载和容器运行. Kubernetes主要由以下几个核心组件组成: etcd保存了整个集群的状态: apiserver提供了资源操 ...