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. 牛客网 牛客练习赛4 A.Laptop-二维偏序+离散化+树状数组

    A.Laptop 链接:https://ac.nowcoder.com/acm/contest/16/A来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 131072K,其 ...

  2. connect via ssh to virtualbox guest vm without knowing ip address

    cat ssh-vm HOSTIP=`ip route get 1 | awk '{match($0, /.+src\s([.0-9]+)/, a);print a[1];exit}'` HOST_N ...

  3. 解决mysql无法显示中文/MySQL中文乱码问号等问题

    一般都是编码格式问题 显示编码格式: show variables like'character_set_%'; 将其中Value不是utf8的改为utf8: set character_set_cl ...

  4. RK3288板子刷linux-ubuntu16固件

    Firefly-RK3288 是一个高性能平台,拥有强大的多线程运算能力.图形处理能力以及硬件解码 能力,而且支 持 Android和Ubuntu双系统; 下面我们使用RK3288的板子刷linux ...

  5. gcc 编译两个so其中soA依赖soB

    有两个so,其中soB中调用soA: 那么我们打包soB的时候连接soA: 在打包test程序的时候连接soB,此时soB会自动查找依赖的soA: 如下测试 在编译之前指定环境变量:export LD ...

  6. 阿里云OSS设置跨域访问 H5的时候

    OSS 提供 HTML5 协议中的跨域资源共享 CORS 设置,帮助您实现跨域访问.当 OSS 收到一个跨域请求(或者 OPTIONS 请求)时,会读取存储空间对应的 CORS 规则,然后进行相应的权 ...

  7. pycharm把制表符(tab)转换为空格(PEP8)

    pycharm把制表符转换为4个空格 pycharm显示空格

  8. 全新思维导图 XMind ZEN v10.0.0 中文破解版

    http://www.carrotchou.blog/20331.html 官网 https://www.xmind.cn/ 注意事项 破解版本已经去除了全部的官方试用版的限制,让大家可以像正版用户一 ...

  9. 使用 atom 将 makedown 编辑并转换成 pdf

    链接: https://www.cnblogs.com/fanzhidongyzby/p/6637084.html

  10. Linux下tar的安装方式

    tar -c: 建立压缩档案-x:解压-t:查看内容-r:向压缩归档文件末尾追加文件-u:更新原压缩包中的文件 这五个是独立的命令,压缩解压都要用到其中一个,可以和别的命令连用但只能用其中一个.下面的 ...