APIO2019题解
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题解的更多相关文章
- APIO2019 题解
APIO2019 题解 T1 奇怪装置 题目传送门 https://loj.ac/problem/3144 题解 很容易发现,这个东西一定会形成一个环.我们只需要求出环的长度就解决了一切问题. 设环的 ...
- APIO2019简要题解
Luogu P5444 [APIO2019]奇怪装置 看到这种题,我们肯定会想到\((x,y)\)一定有循环 我们要找到循环节的长度 推一下发现\(x\)的循环节长为\(\frac{AB}{B+1}\ ...
- 题解-APIO2019路灯
problem \(\mathtt {loj-3146}\) 题意概要:一条直线上有 \(n+1\) 个点和 \(n\) 条道路,每条道路连通相邻两个点.在 \(q\) 个时刻内,每个时刻有如下两种操 ...
- 题解-APIO2019桥梁
problem \(\mathrm {loj-3145}\) 题意概要:给定一张 \(n\) 点 \(m\) 边的无向图,边有边权,共 \(q\) 次操作,每次会将第 \(x\) 条边的权值改为 \( ...
- 题解-APIO2019奇怪装置
problem loj-3144 题意概要:设函数 \(f(t)\) 的返回值为一个二元组,即 \(f(t)=((t+\lfloor \frac tB\rfloor)\bmod A, t\bmod B ...
- 题解 洛谷 P5443 【[APIO2019]桥梁】
考虑若只有查询操作,那么就可以构造\(Kruskal\)重构树,然后在线询问了,也可以更简单的把询问离线,把询问和边都按权值从大到小排序,然后双指针依次加入对于当前询问合法的边,用并查集维护每个点的答 ...
- 【LOJ#3146】[APIO2019]路灯(树套树)
[LOJ#3146][APIO2019]路灯(树套树) 题面 LOJ 题解 考场上因为\(\text{bridge}\)某个\(\text{subtask}\)没有判\(n=1\)的情况导致我卡了\( ...
- 【LOJ#3145】[APIO2019]桥梁(分块,并查集)
[LOJ#3145][APIO2019]桥梁(分块,并查集) 题面 LOJ 题解 因为某个\(\text{subtask}\)没判\(n=1\)的情况导致我自闭了很久的题目... 如果没有修改操作,可 ...
- 【LOJ#3144】[APIO2019]奇怪装置(数论)
[LOJ#3144][APIO2019]奇怪装置(数论) 题面 LOJ 题解 突然发现\(LOJ\)上有\(APIO\)的题啦,赶快来做一做. 这题是窝考场上切了的题嗷.写完暴力之后再推了推就推出正解 ...
随机推荐
- 安全测试基础-SQL注入详解
1:什么是SQL注入 SQL注入是一种将SQL代码插入或添加到应用(用户)的输入参数中的攻击,之后再将这些参数传递给后台的SQL服务器加以解析并执行. www.xx.com/news.php?id=1 ...
- hyper-v显示分辨率如何自动调整
打开文件/etc/default/grub 找到GRUB_CMDLINE_LINUX_DEFAULT所在行,在最后加上 video=hyperv_fb:[分辨率],比如我想要的分辨率是1600×900 ...
- SpringMVC(十五):Dispatcher的重要组件之一MultipartResolver(StandardServletMultipartResolver和CommonsMultipartResolver)的用法
MultipartResolver组件 从Spring官网上可以看到MultipartResolver接口的定义信息: public interface MultipartResolver A str ...
- linux下如何使用docker二进制文件安装_docker离线安装
1,下载二进制文件 https://download.docker.com/linux/static/stable/x86_64/docker-18.03.1-ce.tgz 2,解压二进制文件 tar ...
- 大数据 Hibernate
大数据 Hibernate - 国内版 Binghttps://cn.bing.com/search?FORM=U227DF&PC=U227&q=%E5%A4%A7%E6%95%B0% ...
- 如何在本地使用scala或python运行Spark程序
如何在本地使用scala或python运行Spark程序 包含两个部分: 本地scala语言编写程序,并编译打包成jar,在本地运行. 本地使用python语言编写程序,直接调用spark的接口, ...
- QT5.12 qtcreate 在Ubuntu14.04
Ubuntu14.04 下出现了 symbol dbus_message_get_allow_interactive_authorization, version LIBDBUS_1_3 not de ...
- Linux 对音频万能处理的命令——SOX
what's the SOX SoX(即 Sound eXchange)是一个跨平台(Windows,Linux,MacOS 等)的命令行实用程序,可以将各种格式的音频文件转换为需要的 ...
- a dynamic resume
介绍 发现一款开源的动画简历工程, 很是吸引眼球, 分享出来. 技术: 1. npm工程管理 2. vuejs 设计上,将工程分为两个组件: 0.程序入口组件 1. 编辑器组件 2. 简历展示组件 流 ...
- shell中函数的使用
函数是一个脚本代码块,你可以对它进行自定义命名,并且可以在脚本中任意位置使用这个函数.如果想要这个函数,只要调用这个函数的名称就可以了.使用函数的好处在于模块化以及代码可读性强. (1).函数的创建语 ...