HGOI 20191031am 题解
Problem A Divisors
给出$m$个不同的正整数$a_i$,设数论函数 $f(k) = \sum\limits_{i = 1}^{n} [(\sum\limits_{j = 1}^{m} [a_j | i] )== k]$
其中$b|a$表示$a$是$b$的因数,对于所有$k \in [0,m]$,输出答案。
对于$100\%$的数据满足$n\leq 200 , a_i \leq 10^9$
Solution :
如果我们已知$\sum\limits_{i = 1}^{m} f(i)$,那么显然$f(0) = n - \sum\limits_{i = 1}^{m} f(i)$。
考虑到如果$j \in [1,n]$是任意一个$a_i$的因数的时候,它一定被所有$a_i$因数的集合包含。
因为除去这个集合的其他$[1,n]$的数字,必然对$f(0)$贡献。
所以,直接对于每个数暴力出它的所有因数,构出这个集合$S$。其中$|S| = 2 m\sum\limits_{i = 1}^{m} \sqrt{a_i}$
对该集合去重后暴力处理出$f(1) ... f(m)$,那么$f(0) = n - \sum\limits_{i = 1}^{m} f(i) $可以方便出解。
总时间复杂度为$O(m^2\sum\limits_{i = 1}^{m} \sqrt{a_i})$
# include <bits/stdc++.h> using namespace std; const int N=205; int n,m,a[N],ans[N]; vector<int>tmp; void work(int x) { for (int i=1;i*i<=x;i++) if (x%i==0) { tmp.push_back(i); tmp.push_back(x/i); } } int calc(int x) { int t = 0; for (int i=1;i<=m;i++) if (a[i] % x == 0) t++; return t; } int main() { scanf("%d%d",&n,&m); for (int i=1;i<=m;i++) { int t; scanf("%d",&t); work(t); a[i] = t; } sort(tmp.begin(),tmp.end()); tmp.erase(unique(tmp.begin(),tmp.end()),tmp.end()); for (int i=0;i<tmp.size();i++) if (tmp[i]<=n) { ans[calc(tmp[i])]++; } ans[0]=n; for (int i=1;i<=m;i++) ans[0]-=ans[i]; for (int i=0;i<=m;i++) printf("%d\n",ans[i]); return 0; }
Problem B Marke
有$n$个商店,每个商店某一时刻$r_i$及以后会卖$1$种物品且只有$1$个。
每个物品有花费$c_i$和价值$v_i$, 处理$m$个询问,表示在$t_i$时刻购买所有物品且有钱数$w_i$。
输出当前限制的最大价值。
对于$100\%$的数据满足$1 \leq n \leq 300, 1 \leq m \leq 10^5, 1 \leq c_i,w_i \leq 10^9, 1 \leq r_i , t_i \leq 300$
Solution :
表面上看是一个$m$个询问的$0/1$背包,事实上我们观察到$t_i$的值非常的小。
考虑将询问按照时间$t_i$离线。每个时刻如遇到一个商店新开放就更新掉$dp$数组,对后面的询问造成影响。
由于$c_i$的值到达了$10^9$级别而对应的贡献却比较小,只有$300$,所以我们设$f[i]$表示达到至少$i$的贡献最少的钱数。
总贡献不会超过$300^2$,共有$n$次插入,每次插入只要倒序更新一遍$f$数组即可(这样就满足$01$背包的限制)。
时间复杂度是$O(n^2 \max\{w_i\})$
# include <bits/stdc++.h> using namespace std; const int N=305,M=300*300+10,Q=1e5+10; int n,m,f[M],res[Q]; vector< pair<int,int> >r[N],q[N]; int main() { scanf("%d%d",&n,&m); int Max = 0; for (int i=1;i<=n;i++) { int c,v,t; scanf("%d%d%d",&c,&v,&t); r[t].push_back(make_pair(c,v)); Max = max(Max,t); } for (int i=1;i<=m;i++) { int t,v; scanf("%d%d",&t,&v); q[t].push_back(make_pair(v,i)); Max = max(Max,t); } memset(f,0x3f,sizeof(f)); f[0]=0; for (int i=1;i<=Max;i++) { if (r[i].size()) { for (int j=0;j<r[i].size();j++) { int c=r[i][j].first,v=r[i][j].second; for (int k=90000;k>=v;k--) f[k]=min(f[k],f[k-v]+c); for (int k=90000;k>=0;k--) f[k]=min(f[k],f[k+1]); } } if (q[i].size()) { for (int j=0;j<q[i].size();j++) { int l=0,r=90000,ans; int M=q[i][j].first,id=q[i][j].second; while (l<=r) { int mid = (l+r)>>1; if (f[mid]<=M) ans=mid,l=mid+1; else r=mid-1; } res[id] = ans; } } } for (int i=1;i<=m;i++) printf("%d\n",res[i]); return 0; }
Problem C. Dash Speed
一棵含有$n$个节点的树,每一条无向边都有一个区间$[l_i , r_i]$
给出$m$个询问,每次输入一个$x$,设树上一条$k$条边的路径,$p_1 , p_2 , ... ,p_k$
求$x \in \cap _{i = 1}^{k} [l_{p_i}, r_{p_i}]$,最大化$k$的值。
- 对于部分数据满足 $l_i = 1$
- 对于部分数据满足树退化为一条链。
对于$100\%$的数据满足$1 \leq n ,m\leq 7\times 10^4 , 1 \leq l_i \leq r_i \leq n,1 \leq x \leq n$
Solution:
考场写了上述两档部分分,考场得分为$80$分。
- 考虑所有$l_i = 1$的情况。
考虑将所有询问离线,由于每一次移动一个$x$,就相当于每一次删除一条边对后面产生影响。
可以将删边无脑转化为加边,那么问题就转化为按$r_i$的大小依次加每条边使得所有点最终连成一个大集合。
求出每次加边后的森林中所有树的直径最大值。 这个可以用并查集实现。
有个性质,如果$s1,t1$是树$1$的直径,$s2,t2$是树$2$的直径,
那么两棵树合并后的直径必然是上述$4$个点取$2$个点成为直径,共$6$种可能。
直接暴力讨论即可。时间复杂度为$O(n log_2 n)$
- 考虑所有树成为一条链的情况。
考虑将所有询问离线,问题就转化为求连续的$1$最长,支持单点赋值。
这个直接线段树就能做,维护两边连续$1$的个数,和当前线段答案。
可以非常方便的合并。
时间复杂度为$O(n log_2 n)$
# pragma GCC optimize(2) # pragma GCC optimize(3) # include<bits/stdc++.h> using namespace std; const int N=70000+100; int n,m; struct RRR { int a,b,c,d;}input[N]; int qestion[N]; namespace Subtask1 { //无脑暴力 int tot,speed; int head[N]; struct rec{ int pre,to,l,r;}a[N<<1]; void adde(int u,int v,int l,int r) { a[++tot].pre=head[u]; a[tot].to=v; a[tot].l=l; a[tot].r=r; head[u]=tot; } int ans; void dfs(int u,int fa,int L) { ans=max(ans,L); for (int i=head[u];i;i=a[i].pre) { int v=a[i].to; if (v==fa||speed>a[i].r||speed<a[i].l) continue; dfs(v,u,L+1); } } void main() { int now=1; for (int i=2;i<=n;i++) { int u=input[now].a,v=input[now].b,l=input[now].c,r=input[now].d; adde(u,v,l,r); adde(v,u,l,r); now++; } now=1; while (m--) { speed=qestion[now]; ans = 0; for (int i=1;i<=n;i++) dfs(i,0,0); printf("%d\n",ans); now++; } } } namespace Subtask2 { //链的情况 int ans[N]; vector< pair<int,int> >r[N]; vector< int >q[N]; # define lson ls,l,mid # define rson rs,mid+1,r # define mid (l+r>>1) # define ls (x<<1) # define rs (x<<1|1) struct Seg { int lans,rans,ret; }tr[N<<2]; void build(int x,int l,int r) { if (l == r) { tr[x].lans = tr[x].rans= tr[x].ret = 0; return; } build(lson); build(rson); tr[x].ret=max(max(tr[ls].ret,tr[ls].rans+tr[rs].lans),tr[rs].ret); if (tr[ls].lans==mid-l+1) tr[x].lans=tr[ls].lans+tr[rs].lans; else tr[x].lans=tr[ls].lans; if (tr[x].rans==r-mid) tr[x].rans=tr[rs].rans+tr[ls].rans; else tr[x].rans=tr[rs].rans; } void update(int x,int l,int r,int pos,int da) { if (l == r) { tr[x].lans = tr[x].rans= tr[x].ret = da; return; } if (pos<=mid) update(lson,pos,da); else update(rson,pos,da); tr[x].ret=max(max(tr[ls].ret,tr[ls].rans+tr[rs].lans),tr[rs].ret); if (tr[ls].lans==mid-l+1) tr[x].lans=tr[ls].lans+tr[rs].lans; else tr[x].lans=tr[ls].lans; if (tr[rs].rans==r-mid) tr[x].rans=tr[rs].rans+tr[ls].rans; else tr[x].rans=tr[rs].rans; } void main() { int now=1; for (int i=2;i<=n;i++) { int u=input[now].a,v=input[now].b,ll=input[now].c,rr=input[now].d; r[ll].push_back(make_pair(u,1)); r[rr+1].push_back(make_pair(u,0)); now++; } now=1; for (int i=1;i<=m;i++) { int speed = qestion[now]; q[speed].push_back(i); now++; } build(1,1,n); for (int i=1;i<=n;i++) { if (r[i].size()) { for (int j=0;j<r[i].size();j++) update(1,1,n,r[i][j].first,r[i][j].second); } if (q[i].size()) { for (int j=0;j<q[i].size();j++) ans[q[i][j]]=tr[1].ret; } } for (int i=1;i<=m;i++) printf("%d\n",ans[i]); } } namespace Subtask3 { //l_i = 1的情况 struct rec{ int pre,to;}a[N<<1]; struct QwQ{ int s,t;}r[N]; vector< pair<int,int> >ee; vector<int>qes[N]; int tot; int g[N][18]; int head[N],p[N],val[N],f[N],dep[N],an[N]; int del[N],ans[N]; void adde(int u,int v) { a[++tot].pre=head[u]; a[tot].to=v; head[u]=tot; } bool cmp(int x,int y) { return val[x]<val[y];} void dfs(int u,int fa) { dep[u] = dep[fa]+1; g[u][0] = fa; for (int i=head[u];i;i=a[i].pre) { int v=a[i].to; if (v==fa) continue; dfs(v,u); } } int lca(int u,int v) { if (dep[u] < dep[v]) swap(u,v); for (int i=17;i>=0;i--) if (dep[g[u][i]]>=dep[v]) u=g[u][i]; if (u == v) return u; for (int i=17;i>=0;i--) if (g[u][i]!=g[v][i]) u=g[u][i],v=g[v][i]; return g[u][0]; } int dist(int u,int v) { return dep[u]+dep[v]-2*dep[lca(u,v)]; } int father(int x) { if (f[x]==x) return x; return f[x]=father(f[x]); } int tr[N<<2]; # define lson ls,l,mid # define rson rs,mid+1,r # define mid (l+r>>1) # define ls (x<<1) # define rs (x<<1|1) void build(int x,int l,int r) { if (l == r) { tr[x]=1; return;} build(lson); build(rson); tr[x]=max(tr[ls],tr[rs]); } void update(int x,int l,int r,int pos,int val) { if (l == r) { tr[x] = val; return;} if (pos <= mid) update(lson,pos,val); else update(rson,pos,val); tr[x]=max(tr[ls],tr[rs]); } void main() { ee.push_back(make_pair(0,0)); int now=1; for (int i=2;i<=n;i++) { int u=input[now].a,v=input[now].b,l=input[now].c,r=input[now].d; ee.push_back(make_pair(u,v)); adde(u,v); adde(v,u); val[i-1]=r; del[r+1]++; now++; } dfs(1,0); for (int i=1;i<=17;i++) for (int j=1;j<=n;j++) g[j][i]=g[g[j][i-1]][i-1]; build(1,1,n); for (int i=1;i<=n-1;i++) p[i]=i; sort(p+1,p+n,cmp); for (int i=1;i<=n;i++) { f[i]=i; r[i].s=r[i].t=i;} an[n]=0; for (int i=n-1;i>=1;i--) { int u=ee[p[i]].first,v=ee[p[i]].second; int fa_u=father(u),fa_v=father(v); int s1=r[fa_u].s,t1=r[fa_u].t; int s2=r[fa_v].s,t2=r[fa_v].t; int d1=dist(s1,t1),d2=dist(s1,s2),d3=dist(s1,t2); int d4=dist(t1,s2),d5=dist(t1,t2),d6=dist(s2,t2); int op=1; if (d2>d1) d1=d2,op=2; if (d3>d1) d1=d3,op=3; if (d4>d1) d1=d4,op=4; if (d5>d1) d1=d5,op=5; if (d6>d1) d1=d6,op=6; update(1,1,n,fa_u,0); f[fa_u] = fa_v; if (op==1) { r[fa_v].s=s1, r[fa_v].t=t1; }else if (op==2) { r[fa_v].s=s1, r[fa_v].t=s2; }else if (op==3) { r[fa_v].s=s1, r[fa_v].t=t2; }else if (op==4) { r[fa_v].s=t1, r[fa_v].t=s2; }else if (op==5) { r[fa_v].s=t1, r[fa_v].t=t2; }else { r[fa_v].s=s2, r[fa_v].t=t2; } update(1,1,n,fa_v,dist(r[fa_v].s,r[fa_v].t)); an[i]=tr[1]; } now=1; for (int i=1;i<=m;i++) { int v = qestion[now]; qes[v].push_back(i); now++; } now=1; for (int i=1;i<=n;i++) { now+=del[i]; if (qes[i].size()) { for (int j=0;j<qes[i].size();j++) ans[qes[i][j]]=an[now]; } } for (int i=1;i<=m;i++) printf("%d\n",ans[i]); } } int main() { scanf("%d%d",&n,&m); bool flag = 1; for (int i=1;i<=n-1;i++) { scanf("%d%d%d%d",&input[i].a,&input[i].b,&input[i].c,&input[i].d); if (input[i].b != input[i].a+1) flag=0; } for (int i=1;i<=m;i++) { scanf("%d",&qestion[i]); } if (n<=20 && m<=20) { Subtask1::main(); exit(0); } else if (flag) { Subtask2::main(); exit(0); } else { Subtask3::main(); exit(0); } return 0; }
本题有一个$O(n{log_2}^2 n)$的美妙做法。
考虑分治,处理出所有答案,在线输出即可。
对答案进行分治(类似于整体二分的思想),首先对于输入的每一条边在第一次遇到的位置打上标记。
这个区间在该位置第一次被这条边完全包含,这意味着无论将这个线段如何继续二分,该区间仍然包含,即对应的区间仍然有效。
由于将值域划分类似于线段树的值域划分,所以在读入边的时候,用类似于线段树的操作,将边放置到对应的第一次遇到的位置。
遇到一条边就将对应的两个联通块合并,这个可以使用之前的并查集维护。
现在考虑如何回溯的时候回退,显然不能使用路径压缩的并查集,否则不能回退。故采用按秩合并的并查集来维护。
同时用一个栈来储存在该节点所有的操作,在回退的时候按照栈的操作依次弹出做对应的恢复操作即可。
所以本题的时间复杂度是一个按秩合并的复杂度套一个对值域二分的复杂度,即$O(n{log_2}^2 n)$
# pragma GCC optimize(3) # include <bits/stdc++.h> using namespace std; const int N=7e4+10; struct rec{ int pre,to;}a[N<<1]; struct QwQ { int s,t,size;}r[N]; vector<int> tr[N<<2]; int n,m,tot; int dep[N],g[N][18],head[N],f[N],ans[N]; namespace fast_IO{ const int IN_LEN = 10000000, OUT_LEN = 10000000; char ibuf[IN_LEN], obuf[OUT_LEN], *ih = ibuf + IN_LEN, *oh = obuf, *lastin = ibuf + IN_LEN, *lastout = obuf + OUT_LEN - 1; inline char getchar_(){return (ih == lastin) && (lastin = (ih = ibuf) + fread(ibuf, 1, IN_LEN, stdin), ih == lastin) ? EOF : *ih++;} inline void putchar_(const char x){if(oh == lastout) fwrite(obuf, 1, oh - obuf, stdout), oh = obuf; *oh ++= x;} inline void flush(){fwrite(obuf, 1, oh - obuf, stdout);} int read(){ int x = 0; char ch = ' '; while (ch < '0' || ch > '9') ch = getchar_(); while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar_(); return x; } void write(int x){ if (x > 9) write(x / 10); putchar_(x % 10 + '0'); } } using namespace fast_IO; vector< pair<int,int> >e; void adde(int u,int v) { a[++tot].pre=head[u]; a[tot].to=v; head[u]=tot; } void insert(int x,int l,int r,int opl,int opr,int key) { if (opl<=l&&r<=opr) { tr[x].push_back(key); return;} int mid=(l+r)>>1; if (opl<=mid) insert(x<<1,l,mid,opl,opr,key); if (opr>mid) insert(x<<1|1,mid+1,r,opl,opr,key); } void dfs(int u,int fa) { dep[u] = dep[fa]+1; g[u][0] = fa; for (int i=head[u];i;i=a[i].pre) { int v=a[i].to; if (v == fa) continue; dfs(v,u); } } int lca(int u,int v) { if (dep[u]<dep[v]) swap(u,v); for (int i=17;~i;i--) if (dep[g[u][i]]>=dep[v]) u=g[u][i]; if (u == v) return u; for (int i=17;~i;i--) if (g[u][i]!=g[v][i]) u=g[u][i],v=g[v][i]; return g[u][0]; } int dist(int u,int v) { return dep[u]+dep[v]-2*dep[lca(u,v)]; } int father(int x) { if (f[x] == x) return x; return father(f[x]); } struct HHH { int a; QwQ x; int b; QwQ y; }s[N]; int top=0; void merge(int u,int v,int &ret) { int fa_u=father(u),fa_v=father(v); if (r[fa_u].size > r[fa_u].size) swap(u,v),swap(fa_u,fa_v); int s1=r[fa_u].s,t1=r[fa_u].t; int s2=r[fa_v].s,t2=r[fa_v].t; int d1=dist(s1,t1),d2=dist(s1,s2),d3=dist(s1,t2); int d4=dist(t1,s2),d5=dist(t1,t2),d6=dist(s2,t2); int op=1; if (d2>d1) d1=d2,op=2; if (d3>d1) d1=d3,op=3; if (d4>d1) d1=d4,op=4; if (d5>d1) d1=d5,op=5; if (d6>d1) d1=d6,op=6; ret=max(ret,d1); HHH ttt; ttt.a = fa_u; ttt.x = r[fa_u]; ttt.b = fa_v; ttt.y = r[fa_v]; s[++top] = ttt; if (op==1) { r[fa_v].s=s1, r[fa_v].t=t1; }else if (op==2) { r[fa_v].s=s1, r[fa_v].t=s2; }else if (op==3) { r[fa_v].s=s1, r[fa_v].t=t2; }else if (op==4) { r[fa_v].s=t1, r[fa_v].t=s2; }else if (op==5) { r[fa_v].s=t1, r[fa_v].t=t2; }else { r[fa_v].s=s2, r[fa_v].t=t2; } r[fa_u].size+=r[fa_v].size; f[fa_u] = fa_v; } void replace(int lim) { while (top!=lim) { HHH ttt = s[top]; f[ttt.a]=ttt.a; r[ttt.a]=ttt.x; f[ttt.b]=ttt.b; r[ttt.b]=ttt.y; top--; } } void solve(int x,int l,int r,int ret) { int last = top; int t_s = tr[x].size(); for (int i=0;i<t_s;i++) merge(e[tr[x][i]].first,e[tr[x][i]].second,ret); if (l == r) { ans[l]=ret; replace(last); return; } int mid=l+r>>1; solve(x<<1,l,mid,ret); solve(x<<1|1,mid+1,r,ret); replace(last); } int main() { n=read();m=read(); for (int i=2;i<=n;i++) { int u=read(),v=read(),l=read(),r=read(); adde(u,v); adde(v,u); e.push_back(make_pair(u,v)); insert(1,1,n,l,r,i-2); } for (int i=1;i<=n;i++) { f[i]=i; r[i].s=r[i].t=i; r[i].size=1; } dfs(1,0); for (int i=1;i<=17;i++) for (int j=1;j<=n;j++) g[j][i] = g[g[j][i-1]][i-1]; solve(1,1,n,0); for (int i=1;i<=m;i++) { int t=read(); write(ans[t]); putchar_('\n'); } flush(); return 0; }