本场比赛中的大约三四道题目在之前的联测或 APIO 讲课中出现。
打 * 号表示没有写的题,可能是不打算写了,也可能是暂时没来得及写。
A. Advertisement Matching 难度: $\bigstar\bigstar$
题目大意:
有 $n$ 种广告,第 $i$ 种有 $a_i$ 份,还有 $m$ 个用户,第 $i$ 个用户可以接受 $b_i$ 份广告,每个用户不能接受多份同种广告。
接下来有 $q$ 个操作,每个操作为将某个 $a_i$ 或 $b_i$ 加一或减一,每次操作后请计算是否存在将所有广告分发完的方案。
$1\leq n,m,q\leq 2.5\times 10^5$
题目解法:
霍尔定理扩展的经典问题,朝这个方向稍微想想就可知道,将 $a_i$ 从大到小排序后,当且仅当对于每个 $x\in [1,n]$ 都满足 $\sum\limits_{i=1}^x a_i\leq \sum\limits_{i=1}^m \min(x,b_i)$ 时答案才为肯定。
用线段树维护上述式子,由于都是加减一所以可以假设没有大小顺序的变化,时间复杂度为 $O(q\log n+n+m)$。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 #include <bits/stdc++.h> #define ll long long using namespace std;const int MAXN=500010 ;struct P { int pos,v; }p[MAXN]; int n,m,q,op,x,fir[MAXN],las[MAXN],a[MAXN],b[MAXN],c[MAXN],rev[MAXN],sum[MAXN];ll mn[MAXN*2 ],tg[MAXN*2 ]; bool cmp (P a,P b) {return a.v>b.v;}void pd (int p) { if (tg[p]==0 ) {return ;} mn[p<<1 ]+=tg[p],mn[(p<<1 )|1 ]+=tg[p],tg[p<<1 ]+=tg[p],tg[(p<<1 )|1 ]+=tg[p]; tg[p]=0 ; return ; } void modify (int p,int l,int r,int xl,int xr,int v) { if (xr<l||r<xl) {return ;} if (xl<=l&&r<=xr) { mn[p]+=v,tg[p]+=v; return ; } pd (p); int mid=(l+r)>>1 ; modify (p<<1 ,l,mid,xl,xr,v); modify ((p<<1 )|1 ,mid+1 ,r,xl,xr,v); mn[p]=min (mn[p<<1 ],mn[(p<<1 )|1 ]); return ; } int query (int p,int l,int r,int pos) { if (l==r) {return mn[p];} pd (p); int mid=(l+r)>>1 ; if (pos<=mid) {return query (p<<1 ,l,mid,pos);} else {return query ((p<<1 )|1 ,mid+1 ,r,pos);} } int main () { scanf ("%d%d" ,&n,&m); for (int i=1 ;i<=n;i++) { scanf ("%d" ,&p[i].v); p[i].pos=i,a[i]=p[i].v; } sort (p+1 ,p+n+1 ,cmp); c[0 ]=c[n+1 ]=-1 ; for (int i=1 ;i<=n;i++) { c[i]=p[i].v,rev[p[i].pos]=i; if (c[i]!=c[i-1 ]) {fir[c[i]]=i;} if (c[i]!=c[i+1 ]) {las[c[i]]=i;} modify (1 ,1 ,n,i,n,-c[i]); } for (int i=1 ;i<=m;i++) { scanf ("%d" ,&b[i]); sum[b[i]]++; } for (int i=250000 ;i>=1 ;i--) {sum[i]+=sum[i+1 ];} for (int i=1 ;i<=n;i++) {modify (1 ,1 ,n,i,n,sum[i]);} scanf ("%d" ,&q); for (int i=1 ;i<=q;i++) { scanf ("%d%d" ,&op,&x); if (op==1 ) { int tmp=rev[x]; int val=c[tmp]; int fr=fir[val]; if (fr!=tmp) { rev[p[fr].pos]=tmp; rev[x]=fr; p[tmp].pos=p[fr].pos; p[fr].pos=x; } c[fr]++; fir[val]++; if (fir[val]>las[val]) {fir[val]=las[val]=0 ;} modify (1 ,1 ,n,fr,n,-1 ); las[val+1 ]=fr; if (fir[val+1 ]==0 ) {fir[val+1 ]=fr;} } else if (op==2 ) { int tmp=rev[x]; int val=c[tmp]; int ls=las[val]; if (ls!=tmp) { rev[p[ls].pos]=tmp; rev[x]=ls; p[tmp].pos=p[ls].pos; p[ls].pos=x; } c[ls]--; las[val]--; if (fir[val]>las[val]) {fir[val]=las[val]=0 ;} modify (1 ,1 ,n,ls,n,1 ); fir[val-1 ]=ls; if (las[val-1 ]==0 ) {las[val-1 ]=ls;} } else if (op==3 ) { b[x]++; if (b[x]<=n) {modify (1 ,1 ,n,b[x],n,1 );} } else if (op==4 ) { if (b[x]<=n) {modify (1 ,1 ,n,b[x],n,-1 );} b[x]--; } ll tmp=mn[1 ]; if (tmp>=0 ) {printf ("1\n" );} else {printf ("0\n" );} } return 0 ; }
B. Cactus Competition 难度: $\bigstar\bigstar$
题目大意:
有 $n\times m$ 网格,给定数组 $a_1,\ldots,a_n$ 和 $b_1,\ldots,b_m$,当且仅当 $a_i+b_j\ge 0$ 时 $(i,j)$ 是黑格,现在请问有多少对 $(s,t)$ 满足:存在一条从 $(s,1)$ 到 $(t,m)$ 的每次仅能够向右或向下行走一步的只通过黑格的路径。
$1\leq n,m\leq 2\times 10^5$
题目解法:
先讲我的做法:
将满足 $a_x+\min(b_i)\ge 0$ 的行 $x$ 称为桥。
那么显然从 $(s,1)$ 可以走到 $(t,m)$,当且仅当可以从 $(s,1)$ 走到一个桥,再从同一个桥可以走到 $(t,m)$。
把桥从上到下依次编号,我们只需要求出从每一个 $(s,1)$ 可以走到的最大编号的桥,和可以走到 $(t,m)$ 的编号最小的桥,然后就是一个平凡的二维偏序问题。
以前一个问题为例,求 $(s,1)$ 可以走到的编号最大的桥,这个问题可以递推,容易证明如果 $s$ 不是桥,那么设 $p$ 是满足 $p>s$ 且 $a_p\ge a_s$ 的最小的 $p$,则如果从 $(s,1)$ 可以到达第 $p$ 行那么 $(s,1)$ 的答案与 $(p,1)$ 相同,否则 $(s,1)$ 走不到任何一个桥。
于是只需要用 ST 表和二分的基本技巧就可以求出上面问题的答案了,再结合树状数组解决二维偏序即可通过。
时间复杂度为 $O(n\log n)$。
大众做法大概就是考虑 $(1,1)$ 到 $(n,m)$ 什么情况可以走通,就是起点终点不能被封锁,并且不能有行列被封锁,然后基于此考虑,复杂度应该是一样的。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 #include <bits/stdc++.h> #define ll long long using namespace std;const int MAXN=200010 ;int n,m,a[MAXN],b[MAXN],lg[MAXN],stan[MAXN][20 ],stbn[MAXN][20 ],stax[MAXN][20 ],stbx[MAXN][20 ];int cnt,bri[MAXN],mx[MAXN],mn[MAXN],fw[MAXN];ll ans; int chkn (int a[],int x,int y) {return (a[x]<=a[y]?x:y);}int chkx (int a[],int x,int y) {return (a[x]>=a[y]?x:y);}void modify (int x,int v) { for (int i=x;i<=cnt;i+=(i&(-i))) {fw[i]+=v;} return ; } int query (int x) { int res=0 ; for (int i=x;i;i-=(i&(-i))) {res+=fw[i];} return res; } int queryan (int l,int r) { int k=lg[r-l+1 ]; return chkn (a,stan[l][k],stan[r-(1 <<k)+1 ][k]); } int queryax (int l,int r) { int k=lg[r-l+1 ]; return chkx (a,stax[l][k],stax[r-(1 <<k)+1 ][k]); } int querybn (int l,int r) { int k=lg[r-l+1 ]; return chkn (b,stbn[l][k],stbn[r-(1 <<k)+1 ][k]); } int querybx (int l,int r) { int k=lg[r-l+1 ]; return chkx (b,stbx[l][k],stbx[r-(1 <<k)+1 ][k]); } int main () { memset (mn,0x3f ,sizeof (mn)); scanf ("%d%d" ,&n,&m); lg[0 ]=-1 ; for (int i=1 ;i<=n;i++) { scanf ("%d" ,&a[i]); stan[i][0 ]=stax[i][0 ]=i,lg[i]=lg[i>>1 ]+1 ; } int tmp=1e9 ; for (int i=1 ;i<=m;i++) { scanf ("%d" ,&b[i]); stbn[i][0 ]=stbx[i][0 ]=i,lg[i]=lg[i>>1 ]+1 ; tmp=min (tmp,b[i]); } for (int i=1 ;i<=n;i++) {a[i]=min (a[i],-tmp);} for (int i=1 ;i<=18 ;i++) { for (int j=1 ;j+(1 <<i)-1 <=n;j++) { stan[j][i]=chkn (a,stan[j][i-1 ],stan[j+(1 <<(i-1 ))][i-1 ]); stax[j][i]=chkx (a,stax[j][i-1 ],stax[j+(1 <<(i-1 ))][i-1 ]); } for (int j=1 ;j+(1 <<i)-1 <=m;j++) { stbn[j][i]=chkn (b,stbn[j][i-1 ],stbn[j+(1 <<(i-1 ))][i-1 ]); stbx[j][i]=chkx (b,stbx[j][i-1 ],stbx[j+(1 <<(i-1 ))][i-1 ]); } } for (int i=n;i>=1 ;i--) { if (a[i]+b[1 ]<0 ) {continue ;} int l=i+1 ,r=n+1 ; while (l<r) { int mid=(l+r)>>1 ; if (a[queryax (i+1 ,mid)]>=a[i]) {r=mid;} else {l=mid+1 ;} } int tmp=l; l=1 ,r=m; while (l<r) { int mid=(l+r+1 )>>1 ; if (b[querybn (1 ,mid)]+a[i]>=0 ) {l=mid;} else {r=mid-1 ;} } if (l==m) {bri[i]=++cnt;mn[i]=cnt;} if (tmp!=n+1 ) { int tmp2=b[querybx (1 ,l)],tmp3=a[queryan (i,tmp)]; if (tmp2+tmp3>=0 ) {mn[i]=min (mn[i],mn[tmp]);} } } for (int i=1 ;i<=n;i++) { if (a[i]+b[m]<0 ) {continue ;} int l=0 ,r=i-1 ; while (l<r) { int mid=(l+r+1 )>>1 ; if (a[queryax (mid,i-1 )]>=a[i]) {l=mid;} else {r=mid-1 ;} } int tmp=l; l=1 ,r=m; while (l<r) { int mid=(l+r)>>1 ; if (b[querybn (mid,m)]+a[i]>=0 ) {r=mid;} else {l=mid+1 ;} } if (l==1 ) {mx[i]=bri[i];} if (tmp!=0 ) { int tmp2=b[querybx (l,m)],tmp3=a[queryan (tmp,i)]; if (tmp2+tmp3>=0 ) {mx[i]=max (mx[i],mx[tmp]);} } } for (int l=1 ,r;l<=n+1 ;l=r+1 ) { r=l; while (r<=n&&!bri[r]) {r++;} for (int i=max (1 ,l-1 );i<r;i++) {ans+=query (mx[i]);} for (int i=l;i<=min (n,r);i++) {modify (mn[i],1 );} } printf ("%lld\n" ,ans); return 0 ; }
C. Economic One-way Roads 难度: $\bigstar\bigstar\bigstar\bigstar$
题目大意:
给定 $n$ 个点的无向图,你需要给每条边定向,每种方向各有一个边权,你需要使得定向后的图强连通,求定向边权和的最小值。
$2\leq n\leq 18$
题目解法:
本题需要用到强连通图的耳分解相关知识。
设点集为 $V$,对于其一个子集 $S$,如果 $u,w\in S$,而 $v_1,\ldots,v_k\in V-S$,并且存在有向路径 $u\to v_1\to \ldots\to v_k\to w$,那么就称这条路径是 $S$ 的一个耳。
一个有向图是强连通图,当且仅当:
设有一个点集 $S$,初始为 $\{1\}$;
每次选择 $S$ 的一个耳,将其中所有点加入 $S$ 中;
重复上述过程,可以使得 $S=V$。
所以在本题中,我们设 $f(S)$ 表示上述过程中的点集为 $S$ 时,$S$ 内部定向边权和的最小值。
简单的想法是枚举耳的点集,将它加到 $S$ 中,但复杂度可达 $O(3^n\operatorname{poly}(n))$。
考虑一个点一个点地加入一个耳,设 $g(S,v,w)$ 表示当前点集为 $S$,目前正在加一个耳,终点为 $w$,当前这个耳构造到了 $v$(可以发现耳的起点 $u$ 不用算入状态),那么每次枚举 $v$ 的后继 $v’$,转移到 $g(S\cup \{v’\},v’,w)$ 即可。
注意到 $(v,w)$ 和 $(w,v)$ 两条边不能同时出现,所以还需要额外记录一个 $b$ 表示耳的上一条边是否就是 $(w,v)$,如果是的话则不能在下一步选择 $(v,w)$ 这条边使得耳闭合。
时间复杂度为 $O(2^n\times n^3)$。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <bits/stdc++.h> using namespace std;const int MAXN=20 ;int n,del,d[MAXN][MAXN],res[1 <<18 ],f[1 <<18 ][MAXN][MAXN][2 ];int main () { scanf ("%d" ,&n); for (int i=1 ;i<=n;i++) { for (int j=1 ;j<=n;j++) {scanf ("%d" ,&d[i][j]);} } for (int i=1 ;i<=n;i++) { for (int j=i+1 ;j<=n;j++) { int tmp=min (d[i][j],d[j][i]); if (tmp>=0 ) {del+=tmp,d[i][j]-=tmp,d[j][i]-=tmp;} } } memset (res,0x3f ,sizeof (res)); memset (f,0x3f ,sizeof (f)); res[1 ]=0 ; for (int i=1 ;i<(1 <<n);i++) { for (int j=0 ;j<n;j++) { for (int k=0 ;k<n;k++) { if ((i&(1 <<j))&&(i&(1 <<k))&&d[j+1 ][k+1 ]!=-1 ) { res[i]=min (res[i],f[i][j][k][1 ]+d[j+1 ][k+1 ]); } } } for (int j=0 ;j<n;j++) { for (int k=0 ;k<n;k++) { if ((i&(1 <<j))&&(i&(1 <<k))) { f[i][j][k][0 ]=min (f[i][j][k][0 ],res[i]); } } } for (int j=0 ;j<n;j++) { for (int k=0 ;k<n;k++) { for (int l=0 ;l<n;l++) { if ((i&(1 <<j))&&(i&(1 <<k))&&(!(i&(1 <<l)))&&d[j+1 ][l+1 ]!=-1 ) { f[i^(1 <<l)][l][k][j!=k]=min (f[i^(1 <<l)][l][k][j==k], min (f[i][j][k][0 ],f[i][j][k][1 ])+d[j+1 ][l+1 ]); } } } } } if (res[(1 <<n)-1 ]==0x3f3f3f3f ) {printf ("-1\n" );} else {printf ("%d\n" ,del+res[(1 <<n)-1 ]);} return 0 ; }
D. Just Meeting 难度: $\bigstar$
题目大意:
定义一个 $n$ 个点的无向带权完全图($i,j$ 之间边权为 $w(i,j)$ 为正整数)是正规的,当且仅当对于任意不同的 $1\leq i,j,k\leq n$,都有 $w(i,j),w(j,k),w(k,i)$ 三个数中最小值不唯一。
给定点数 $n$ 和 $m$ 条带权边,问是否可以将图补全成正规的图,如果可以则请求出补全后图的边权和最小值。
$1\leq n,m\leq 3\times 10^5$
题目解法:
每个连通块是独立的,连通块之间的所有边权都设为 $1$ 即可。
首先容易证明 $w(i,j)\ge \min(w(j,k),w(i,k))$,所以如果我们构建图的一个生成树,那么非树边 $(i,j)$ 的权值必须大于等于它们树上路径权值的最小值。
再联想到最大生成树中有 $w(i,j)\leq \min(w(j,k),w(i,k))$,所以如果我们取最大生成树,就应该有 $(i,j)$ 间的权值应该恰好等于它们树上路径权值的最小值。
于是用维护子树大小的并查集处理一下即可,注意判定是否有解需要支持查询树链最小值。
时间复杂度为 $O((n+m)\log n)$。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 #include <bits/stdc++.h> #define ll long long using namespace std;const int MAXN=300010 ;struct E { int u,v,w; E (int a=0 ,int b=0 ,int c=0 ) {u=a,v=b,w=c;} }e[MAXN]; int n,m,x,y,z,f[MAXN],g[MAXN],dep[MAXN],fa[MAXN][22 ],mn[MAXN][22 ];ll res,ans; vector < pair<int ,int > > v[MAXN]; bool cmp (E a,E b) {return a.w>b.w;}int findr (int x) {return (f[x]==x?x:f[x]=findr (f[x]));}void dfs (int x,int faa,int w) { fa[x][0 ]=faa,mn[x][0 ]=w,dep[x]=dep[faa]+1 ; for (int i=1 ;i<=20 ;i++) { fa[x][i]=fa[fa[x][i-1 ]][i-1 ]; mn[x][i]=min (mn[x][i-1 ],mn[fa[x][i-1 ]][i-1 ]); } int len=v[x].size (); for (int i=0 ;i<len;i++) { if (v[x][i].first==faa) {continue ;} dfs (v[x][i].first,x,v[x][i].second); } return ; } int query (int x,int y) { int res=1e9 ; if (dep[x]<dep[y]) {swap (x,y);} for (int i=20 ;i>=0 ;i--) { if (dep[x]-(1 <<i)>=dep[y]) { res=min (res,mn[x][i]); x=fa[x][i]; } } if (x==y) {return res;} for (int i=20 ;i>=0 ;i--) { if (fa[x][i]!=fa[y][i]) { res=min (res,min (mn[x][i],mn[y][i])); x=fa[x][i],y=fa[y][i]; } } return min (res,min (mn[x][0 ],mn[y][0 ])); } int main () { scanf ("%d%d" ,&n,&m); for (int i=1 ;i<=m;i++) { scanf ("%d%d%d" ,&x,&y,&z); e[i]=E (x,y,z); } sort (e+1 ,e+m+1 ,cmp); for (int i=1 ;i<=n;i++) {f[i]=i,g[i]=1 ;} res=(1ll *n*(n-1 )/2 ); for (int i=1 ;i<=m;i++) { int fx=findr (e[i].u),fy=findr (e[i].v); if (fx!=fy) { res-=1ll *g[fx]*g[fy]; ans+=1ll *g[fx]*g[fy]*e[i].w; f[fx]=fy; g[fy]+=g[fx]; v[e[i].u].push_back (make_pair (e[i].v,e[i].w)); v[e[i].v].push_back (make_pair (e[i].u,e[i].w)); } } for (int i=1 ;i<=n;i++) { if (findr (i)==i) {dfs (i,0 ,0 );} } for (int i=1 ;i<=m;i++) { if (query (e[i].u,e[i].v)!=e[i].w) {printf ("-1\n" );return 0 ;} } ans+=res; printf ("%lld\n" ,ans); return 0 ; }
*E. Chemistry 难度: $\bigstar\bigstar$
F. Interval Graph 难度: $\bigstar\bigstar$
题目大意:
给定 $n$ 个带权区间,你可以删除一些区间,对剩下的区间建区间图(每个区间对应一个点,两个区间有交就连边),要求该图无环,求剩下的区间权值和的最大值。
$1\leq n\leq 2.5\times 10^5$
题目解法:
区间图是弦图,因此有环当且仅当有三元环,也等价于有三个区间存在公共点。
所以我们需要挑选一些区间,使得被这些区间覆盖后每个点至多被覆盖两次,这是经典的 Delight for a Cat 型费用流建模 ,建出图后用原始对偶费用流求解即可,最大流只为 $2$ 所以只要做两次最长路。
初始势能有一种简单设法:$E_i=-W\times i$,其中 $W$ 是权值最大值。
时间复杂度为 $O(n\log n)$。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 #include <bits/stdc++.h> #define ll long long using namespace std;const int MAXN=750010 ;int n,x[MAXN],y[MAXN],eg,hd[MAXN],ver[2 *MAXN],nx[2 *MAXN],las[MAXN],vis[MAXN];ll ans,shi=1e12 ,z[MAXN],w[2 *MAXN],dis[MAXN],sh[MAXN]; priority_queue < pair<ll,int > > q; void add_edge (int x,int y,ll z) { ver[++eg]=y; nx[eg]=hd[x],w[eg]=z; hd[x]=eg; return ; } int main () { scanf ("%d" ,&n); for (int i=1 ;i<=n;i++) { scanf ("%d%d%lld" ,&x[i],&y[i],&z[i]); y[i]++; add_edge (x[i],y[i],1ll *shi*(y[i]-x[i])-z[i]); } for (int i=1 ;i<=500000 ;i++) {add_edge (i,i+1 ,shi);} memset (dis,0x3f ,sizeof (dis)); dis[1 ]=0 ; q.push (make_pair (0 ,1 )); while (q.size ()) { pair <ll,int > a=q.top (); q.pop (); if (vis[a.second]) {continue ;} vis[a.second]=1 ; for (int i=hd[a.second];i;i=nx[i]) { if (dis[ver[i]]>dis[a.second]+w[i]) { dis[ver[i]]=dis[a.second]+w[i],las[ver[i]]=i; q.push (make_pair (-dis[ver[i]],ver[i])); } } } ans+=shi*500000ll -dis[500001 ]; for (int i=1 ;i<=500001 ;i++) {sh[i]=-shi*i+dis[i];} int tmp=500001 ; eg=0 ; memset (hd,0 ,sizeof (hd)); memset (dis,0x3f ,sizeof (dis)); memset (vis,0 ,sizeof (vis)); while (tmp!=1 ) { int xx=las[tmp]; if (xx<=n) { vis[xx]=1 ; add_edge (y[xx],x[xx],z[xx]+sh[y[xx]]-sh[x[xx]]); tmp=x[xx]; } else { add_edge (tmp,tmp-1 ,sh[tmp]-sh[tmp-1 ]); tmp--; } } for (int i=1 ;i<=500000 ;i++) {add_edge (i,i+1 ,sh[i]-sh[i+1 ]);} for (int i=1 ;i<=n;i++) { if (!vis[i]) {add_edge (x[i],y[i],-z[i]+sh[x[i]]-sh[y[i]]);} } memset (vis,0 ,sizeof (vis)); dis[1 ]=0 ; q.push (make_pair (0 ,1 )); while (q.size ()) { pair <ll,int > a=q.top (); q.pop (); if (vis[a.second]) {continue ;} vis[a.second]=1 ; for (int i=hd[a.second];i;i=nx[i]) { if (dis[ver[i]]>dis[a.second]+w[i]) { dis[ver[i]]=dis[a.second]+w[i],las[ver[i]]=i; q.push (make_pair (-dis[ver[i]],ver[i])); } } } ans+=sh[1 ]-sh[500001 ]-dis[500001 ]; printf ("%lld\n" ,ans); return 0 ; }
G. LCS 8 难度: $\bigstar\bigstar\bigstar\bigstar$
题目大意:
给定长度为 $n$ 的字符串 $S$ 和 $k$,求长度为 $n$ 的字符串 $T$ 的数量,使得 $S$ 与 $T$ 的最长公共子序列长度不小于 $n-k$,字符集为大写字母。
$1\leq n\leq 50000, 0\leq k\leq 3$
题目解法:
解一(代码区发现的解法):
考虑用 DP 解决 LCS 问题时需要用到的结构,尝试对这一结构进行递推计数。
设 $dp(x,a_0,a_1,a_2,a_3)$ 表示已经填入 $T_1,\ldots,T_x$,并且 $a_i$ 是使得 $\operatorname{LCS}(T_1\ldots T_x,S_1\ldots S_{x-i+a_i})=x-i$ 成立的最小自然数,其中下标取 $x-i+a_i$ 的理由是注意到这个下标应当在 $[x-i,x-i+3]$ 之间(否则就是无用的状态),也就是说 $a_i\in [0,3]$,特别地如果 $a_i$ 不存在则认为其等于 $4$。
增加一个数时通过简单的讨论转移即可。
时间复杂度为 $O(k^k\times n|\Sigma|)$。
解二(题解):
直接尝试刻画求 LCS 时 DP 数组的形态。
设 $f(i,j)$ 表示 $\operatorname{LCS}(T_1\ldots T_i,S_1\ldots S_j)$,将状态设为 $(i,f(i,i-k),\ldots,f(i,i),\ldots,f(i,i+k))$,事实上由于 $f(i,j)-f(i,j-1)\in [0,1]$,并且我们只需要 $f(i,i)\in [i-k,i]$ 的状态,所以总的状态数是 $O(2^{2k}\times nk)$。
转移需要进行一定的预处理,比之上一种做法复杂得多。
最终的时间复杂度为 $O(2^{4k}|\Sigma|+2^{2k}\times nk)$。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <bits/stdc++.h> using namespace std;const int MAXN=50010 ,P=1e9 +7 ;int n,k,ans,a[4 ],b[4 ],dp[2 ][630 ],nx[MAXN][26 ];char c[MAXN];int encode () { int res=0 ; for (int i=3 ;i>=0 ;i--) {res=res*5 +a[i];} return res; } void decode (int x) { for (int i=0 ;i<=3 ;i++) {b[i]=x%5 ;x/=5 ;} return ; } int main () { scanf ("%s" ,c+1 ); n=strlen (c+1 ); for (int i=n;i>=1 ;i--) { for (int j=0 ;j<=25 ;j++) { if (c[i]-'A' !=j) {nx[i][j]=nx[i+1 ][j]+1 ;} } } scanf ("%d" ,&k); for (int i=0 ;i<=3 ;i++) {a[i]=i;} dp[0 ][encode ()]=1 ; for (int i=1 ;i<=n;i++) { memset (dp[i&1 ],0 ,sizeof (dp[i&1 ])); for (int j=0 ;j<=624 ;j++) { if (!dp[(i&1 )^1 ][j]) {continue ;} decode (j); for (int k=0 ;k<=25 ;k++) { a[0 ]=4 ,a[1 ]=b[0 ],a[2 ]=b[1 ],a[3 ]=b[2 ]; for (int l=0 ;l<=3 ;l++) {a[l]=min (a[l],b[l]+nx[i-l+b[l]][k]);} dp[i&1 ][encode ()]=(dp[i&1 ][encode ()]+dp[(i&1 )^1 ][j])%P; } } } for (int j=0 ;j<=624 ;j++) { decode (j); if (b[k]<=k) {ans=(ans+dp[n&1 ][j])%P;} } printf ("%d\n" ,ans); return 0 ; }
H. Alchemy 难度: $\bigstar$
题目大意:
现在有一堆 $[0,n)$ 中的数,其中 $i$ 有 $c_i$ 个,你每次可以选择一些数将它们删除,然后加入它们的 $\operatorname{mex}$,最后你需要使得只剩一个数,问这个数的最大值。
$1\leq n\leq 10^5$
题目解法:
假设 $\sum c_i=1$,那么答案是显然的(但注意并不一定是那个为 $1$ 的 $c_i$,当 $i=0$ 时)。
否则,先考虑枚举 $x$,要拼出 $x$,首先可以把所有 $\ge x$ 的数都回收成 $0$,然后这样考虑:要拼出一个 $x$ 需要 $0,\ldots,x-1$ 各一个,要拼出 $x-1$ 需要 $0,\ldots,x-2$ 各一个,要拼出两个 $x-2$ 需要 $0,\ldots,x-3$ 各两个,以此类推一直分解到 $0$,看看 $0$ 够不够用即可,如果过程中某个 $c_i\ne 0$,那么如果 $c_i$ 小于等于实际需要,那么可以将实际需要的抵消掉 $c_i$,如果 $c_i$ 大于实际需要那么可以将多余的回收成 $0$。
根据上述过程可知答案具有可二分性,二分答案并判定,显然答案不超过 $2\times 10^5$,时间复杂度为 $O(n\log n)$。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <bits/stdc++.h> #define ll long long using namespace std;const int MAXN=200010 ;int n,mn,c[MAXN];ll sum; bool chk (int x) { ll cnt=c[0 ]; for (int i=x;i<=100000 ;i++) {cnt+=c[i];} ll sum=1 ; for (int i=x-1 ;i>=1 ;i--) { if (c[i]<sum) {sum+=sum-c[i];} else {cnt+=c[i]-sum;} if (sum>1e17 ) {return 0 ;} } return (sum<=cnt); } int main () { scanf ("%d" ,&n); for (int i=0 ;i<=n-1 ;i++) { scanf ("%d" ,&c[i]); if (c[i]) {mn=i;} sum+=c[i]; } if (sum==1 ) { printf ("%d\n" ,max (mn,1 )); return 0 ; } int l=1 ,r=200000 ; while (l<r) { int mid=(l+r+1 )>>1 ; if (chk (mid)) {l=mid;} else {r=mid-1 ;} } printf ("%d\n" ,l); return 0 ; }
I. Query on a Tree 17 难度: $\bigstar\bigstar\bigstar$
题目大意:
维护 $n$ 个点的以 $1$ 为根的点带权树,支持 $q$ 次子树或路径修改点权,每次修改后输出最浅的带权重心。
$1\leq n,q\leq 10^5$
题目解法:
一个结论是:写出树的 DFS 序,并且将 $i$ 重复 $a_i$(点权)次,那么最浅的带权重心的子树对应的区间一定包含这个序列的中位数(因为其长度应该大于等于总长一半,否则可以再往上走一步)。
于是我们维护这个序列长度和按照 DFS 序的点权前缀和,这当然就契合重链剖分的维护特点,于是我们每次查询中位数,然后从那个点树上倍增就可以找到带权重心。
时间复杂度为 $O(n+q\log^2 n)$。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 #include <bits/stdc++.h> #define ll long long using namespace std;const int MAXN=100010 ;int n,q,op,x,y,tot,f[MAXN][20 ],dep[MAXN],siz[MAXN],wsn[MAXN],tp[MAXN];int dfn[MAXN],ed[MAXN],rev[MAXN];ll s,tg[MAXN*4 ],sum[MAXN*4 ]; vector <int > v[MAXN]; void dfs1 (int x,int fa) { siz[x]=1 ,dep[x]=dep[fa]+1 ,f[x][0 ]=fa; for (int i=1 ;i<=18 ;i++) {f[x][i]=f[f[x][i-1 ]][i-1 ];} int len=v[x].size (); for (int i=0 ;i<len;i++) { if (v[x][i]==fa) {continue ;} dfs1 (v[x][i],x); siz[x]+=siz[v[x][i]]; if (siz[v[x][i]]>siz[wsn[x]]) {wsn[x]=v[x][i];} } return ; } void dfs2 (int x,int fa) { ++tot; dfn[x]=ed[x]=tot,rev[tot]=x; if (wsn[x]) { tp[wsn[x]]=tp[x]; dfs2 (wsn[x],x); ed[x]=ed[wsn[x]]; } int len=v[x].size (); for (int i=0 ;i<len;i++) { if (v[x][i]==fa||v[x][i]==wsn[x]) {continue ;} tp[v[x][i]]=v[x][i]; dfs2 (v[x][i],x); ed[x]=ed[v[x][i]]; } } void pd (int p,int l,int r) { if (tg[p]==0 ) {return ;} int mid=(l+r)>>1 ; tg[p<<1 ]+=tg[p],tg[(p<<1 )|1 ]+=tg[p]; sum[p<<1 ]+=tg[p]*(mid-l+1 ),sum[(p<<1 )|1 ]+=tg[p]*(r-mid); tg[p]=0 ; return ; } void modify (int p,int l,int r,int xl,int xr,int v) { if (xr<l||r<xl) {return ;} if (xl<=l&&r<=xr) { sum[p]+=1ll *(r-l+1 )*v; tg[p]+=v; return ; } pd (p,l,r); int mid=(l+r)>>1 ; modify (p<<1 ,l,mid,xl,xr,v); modify ((p<<1 )|1 ,mid+1 ,r,xl,xr,v); sum[p]=sum[p<<1 ]+sum[(p<<1 )|1 ]; return ; } ll query (int p,int l,int r,int xl,int xr) { if (xr<l||r<xl) {return 0 ;} if (xl<=l&&r<=xr) {return sum[p];} pd (p,l,r); int mid=(l+r)>>1 ; return query (p<<1 ,l,mid,xl,xr)+query ((p<<1 )|1 ,mid+1 ,r,xl,xr); } int query (int x) { for (int i=18 ;i>=0 ;i--) { if (f[x][i]==0 ) {continue ;} if (query (1 ,1 ,n,dfn[f[x][i]],ed[f[x][i]])*2 <=s) {x=f[x][i];} } if (query (1 ,1 ,n,dfn[x],ed[x])*2 <=s) {x=f[x][0 ];} return x; } int main () { scanf ("%d" ,&n); for (int i=1 ;i<=n-1 ;i++) { scanf ("%d%d" ,&x,&y); v[x].push_back (y),v[y].push_back (x); } dfs1 (1 ,0 ); tp[1 ]=1 ; dfs2 (1 ,0 ); scanf ("%d" ,&q); for (int i=1 ;i<=q;i++) { scanf ("%d" ,&op); if (op==1 ) { scanf ("%d" ,&x); modify (1 ,1 ,n,dfn[x],ed[x],1 ); } else { scanf ("%d%d" ,&x,&y); int fx=tp[x],fy=tp[y]; while (fx!=fy) { if (dep[fx]<dep[fy]) {swap (fx,fy),swap (x,y);} modify (1 ,1 ,n,dfn[fx],dfn[x],1 ); x=f[fx][0 ]; fx=tp[x]; } if (dep[x]<dep[y]) {swap (x,y);} modify (1 ,1 ,n,dfn[y],dfn[x],1 ); } int l=1 ,r=n; s=sum[1 ]; while (l<r) { int mid=(l+r)>>1 ; if (query (1 ,1 ,n,1 ,mid)*2 <s) {l=mid+1 ;} else {r=mid;} } printf ("%d\n" ,query (rev[l])); } return 0 ; }
J. Remote Control 难度: $\bigstar$
题目大意:
给定长度为 $n$ 的操作序列,包含 $\text{L,R,U,D}$ 四种指令,一个机器人从坐标系中某点出发,依次按照对应指令向左右上下行走一个单位长度,特别的是,如果行动后会到达 $(0,0)$,那么机器人会忽视这个指令。
现在给定 $q$ 组 $(x,y)$,表示机器人的起点,问每组起点对应的终点。
$1\leq n,q\leq 3\times 10^5$
题目解法:
首先预处理 $f(i)$ 表示如果机器人位于执行一次第 $i$ 个指令后就会到达 $(0,0)$ 的位置,那么它依次执行指令 $i+1,\ldots,n$ 后会到哪里,状态数是 $O(n)$,而转移只需要考虑机器人下一次碰到 $(0,0)$ 四周的时刻即可。
对于询问,也是找到机器人第一次碰到 $(0,0)$ 四周的时刻,然后根据 $f(i)$ 计算答案。
时间复杂度为 $O((n+q)\log n)$。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include <bits/stdc++.h> using namespace std;const int MAXN=300010 ;int n,q,x,y,sumx[MAXN],sumy[MAXN],dpx[MAXN],dpy[MAXN];char c[MAXN];map < pair<int ,int >,int > mp; int main () { scanf ("%d%s" ,&n,c+1 ); for (int i=1 ;i<=n;i++) { sumx[i]=sumx[i-1 ],sumy[i]=sumy[i-1 ]; if (c[i]=='L' ) {sumx[i]--;} if (c[i]=='R' ) {sumx[i]++;} if (c[i]=='U' ) {sumy[i]++;} if (c[i]=='D' ) {sumy[i]--;} } for (int i=n;i>=1 ;i--) { if (c[i]=='L' ) { if (mp.find (make_pair (sumx[i]-1 ,sumy[i]))==mp.end ()) { dpx[i]=1 +sumx[n]-sumx[i],dpy[i]=sumy[n]-sumy[i]; } else { int tmp=mp[make_pair (sumx[i]-1 ,sumy[i])]; dpx[i]=dpx[tmp],dpy[i]=dpy[tmp]; } } else if (c[i]=='R' ) { if (mp.find (make_pair (sumx[i]+1 ,sumy[i]))==mp.end ()) { dpx[i]=-1 +sumx[n]-sumx[i],dpy[i]=sumy[n]-sumy[i]; } else { int tmp=mp[make_pair (sumx[i]+1 ,sumy[i])]; dpx[i]=dpx[tmp],dpy[i]=dpy[tmp]; } } else if (c[i]=='U' ) { if (mp.find (make_pair (sumx[i],sumy[i]+1 ))==mp.end ()) { dpx[i]=sumx[n]-sumx[i],dpy[i]=-1 +sumy[n]-sumy[i]; } else { int tmp=mp[make_pair (sumx[i],sumy[i]+1 )]; dpx[i]=dpx[tmp],dpy[i]=dpy[tmp]; } } else { if (mp.find (make_pair (sumx[i],sumy[i]-1 ))==mp.end ()) { dpx[i]=sumx[n]-sumx[i],dpy[i]=1 +sumy[n]-sumy[i]; } else { int tmp=mp[make_pair (sumx[i],sumy[i]-1 )]; dpx[i]=dpx[tmp],dpy[i]=dpy[tmp]; } } mp[make_pair (sumx[i],sumy[i])]=i; } scanf ("%d" ,&q); for (int i=1 ;i<=q;i++) { scanf ("%d%d" ,&x,&y); if (mp.find (make_pair (-x,-y))==mp.end ()) { printf ("%d %d\n" ,x+sumx[n],y+sumy[n]); } else { int tmp=mp[make_pair (-x,-y)]; printf ("%d %d\n" ,dpx[tmp],dpy[tmp]); } } return 0 ; }
K. Sewing Graph 难度: $\bigstar$
题目大意:
给定 $n$ 个平面上的点,请构造一个尽可能短的由这些点组成的序列 $s_1,\ldots,s_m$,使得:
分别连接 $s_{2k}$ 和 $s_{2k+1}$,构成的图连通且没有两条边交叉;
分别连接 $s_{2k-1}$ 和 $s_{2k}$,构成的图连通且没有两条边交叉;
求构造 $m$ 和点列。
$2\leq n\leq 1000$
题目解法:
按横坐标第一关键字,纵坐标第二关键字排序为 $p_1,\ldots,p_n$,然后令序列为 $p_1,\ldots,p_{n-1},p_n,p_{n-1},\ldots,p_1$ 即可。
时间复杂度为 $O(n\log n)$,这是本场比赛的签到题。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <bits/stdc++.h> using namespace std;const int MAXN=1010 ;struct P { int x,y,id; }p[MAXN]; int n;bool cmp (P a,P b) {return (a.x==b.x?a.y<b.y:a.x<b.x);}int main () { scanf ("%d" ,&n); for (int i=1 ;i<=n;i++) { scanf ("%d%d" ,&p[i].x,&p[i].y); p[i].id=i; } sort (p+1 ,p+n+1 ,cmp); printf ("%d\n" ,2 *n-1 ); for (int i=1 ;i<=n;i++) { printf ("%d " ,p[i].id); } for (int i=n-1 ;i>=1 ;i--) {printf ("%d " ,p[i].id);} printf ("\n" ); return 0 ; }
*L. Steel Slicing 2 难度: $\bigstar\bigstar\bigstar$