12月CWOI杂题
?怎么 12 月都过一半了?
C0425 【1202 A组】模拟测试
A 【1202 A组】景点游览
一个垃圾的 \(\mathcal{O}(n\sqrt{n})\) 做法。先缩点,然后拓扑,求出每个点能到达的所有点中最大的和最小的,记为 \(R_i\) 和 \(L_i\)。那么一段区间 \([l,r]\) 合法的条件就是 \(\min\limits_{i=l}^r L_i=l\land \max\limits_{i=l}^r R_i=r\)。可以 st 表预处理后二分出每个点作为 \(l\) 或 \(r\) 时对应的另一个端点的合法范围,那么只需要两个点都在对方的范围内就可以组成一个合法的区间。可以以右端点的合法区间为询问区间,跑莫队,需要区间加加单点查询,因为加的次数很多而询问只有 \(n\) 次,选择用 \(\mathcal{O}(1)\) 单点加 \(\mathcal{O}(\sqrt{n})\) 区间求和的分块来维护,复杂度 \(\mathcal{O}(n\sqrt{n})\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+7;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,nxt;
}e[600005];
int tot,head[300005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int cur,top,rc,dfn[300005],low[300005],st[300005],in[300005],L[300005],R[300005],bel[300005];
void tarjan(int u){
dfn[u]=low[u]=++cur;st[++top]=u,in[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(!dfn[v])tarjan(v),low[u]=min(low[u],low[v]);
else if(in[v])low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
int v;rc++;
L[rc]=inf,R[rc]=-inf;
do{
v=st[top--],in[v]=0;
bel[v]=rc;
L[rc]=min(L[rc],v);
R[rc]=max(R[rc],v);
}while(v!=u);
}
}
int deg[300005],Log[300005],f[22][300005],g[22][300005];
int askf(int l,int r){
if(l>r)return inf;
int o=Log[r-l+1];
return min(f[o][l],f[o][r-(1ll<<o)+1]);
}
int askg(int l,int r){
if(l>r)return -inf;
int o=Log[r-l+1];
return max(g[o][l],g[o][r-(1ll<<o)+1]);
}
int ll[300005],lr[300005],rl[300005],rr[300005];
struct Que{
int l,r,id;
}qu[300005];
int n,m,siz,num,Bel[300005],bl[605],br[605];
int cmp(Que x,Que y){
return Bel[x.l]<Bel[y.l]||(Bel[x.l]==Bel[y.l]&&((Bel[x.l]&1ll)?x.r<y.r:x.r>y.r));
}
struct Block{
int s1[300005],s2[605];
void Add(int x,int v){
if(1<=x&&x<=n)s1[x]=(s1[x]+v+mod)%mod,s2[Bel[x]]=(s2[Bel[x]]+v+mod)%mod;
}
void Add(int l,int r,int v){
if(l<=r)Add(l,v),Add(r+1,-v);
}
int ask(int l,int r){
if(Bel[l]==Bel[r]){
int res=0;
for(int i=l;i<=r;i++)res=(res+s1[i])%mod;
return res;
}
int res=0;
for(int i=l;i<=br[Bel[l]];i++)res=(res+s1[i])%mod;
for(int i=bl[Bel[r]];i<=r;i++)res=(res+s1[i])%mod;
for(int i=Bel[l]+1;i<=Bel[r]-1;i++)res=(res+s2[i])%mod;
return res;
}
}B;
void Add(int x){
B.Add(ll[x],lr[x],1);
}
void Del(int x){
B.Add(ll[x],lr[x],-1);
}
int ask(int l,int r){
return B.ask(l,r);
}
signed main(){
n=read(),m=read(),siz=(int)sqrt(n),num=(n+siz-1)/siz;
for(int i=1;i<=n;i++)Bel[i]=(i-1)/siz+1;
for(int i=1;i<=num;i++)bl[i]=(i-1)*siz+1,br[i]=min(n,i*siz);
for(int i=1,u,v;i<=m;i++)u=read(),v=read(),add(u,v);
for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);
vector<pair<int,int> >tmp;
for(int u=1;u<=n;u++){
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(bel[u]!=bel[v])tmp.push_back({bel[u],bel[v]});
}
}
tot=0;for(int i=1;i<=n;i++)head[i]=0;
for(auto x:tmp)add(x.second,x.first),deg[x.first]++;
queue<int>q;
for(int i=1;i<=rc;i++){
if(!deg[i])q.push(i);
}
while(!q.empty()){
int u=q.front();q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
L[v]=min(L[v],L[u]),R[v]=max(R[v],R[u]);
if((--deg[v])==0)q.push(v);
}
}
Log[1]=0;for(int i=2;i<=n;i++)Log[i]=Log[i>>1]+1;
for(int i=1;i<=n;i++)f[0][i]=L[bel[i]],g[0][i]=R[bel[i]];
for(int j=1;j<=Log[n];j++){
for(int i=1;i+(1ll<<j)-1<=n;i++){
f[j][i]=min(f[j-1][i],f[j-1][i+(1ll<<(j-1))]);
g[j][i]=max(g[j-1][i],g[j-1][i+(1ll<<(j-1))]);
}
}
for(int i=1;i<=n;i++){
int l=i,r=n,lp=i-1,rp=n+1;
while(l<=r){
int mid=(l+r)>>1;
if(askf(i,mid)>i)lp=mid,l=mid+1;
else r=mid-1;
}
l=i,r=n;
while(l<=r){
int mid=(l+r)>>1;
if(askf(i,mid)<i)rp=mid,r=mid-1;
else l=mid+1;
}
ll[i]=lp+1,lr[i]=rp-1;
}
for(int i=1;i<=n;i++){
int l=1,r=i,lp=i+1,rp=0;
while(l<=r){
int mid=(l+r)>>1;
if(askg(mid,i)<i)lp=mid,r=mid-1;
else l=mid+1;
}
l=1,r=i;
while(l<=r){
int mid=(l+r)>>1;
if(askg(mid,i)>i)rp=mid,l=mid+1;
else r=mid-1;
}
rl[i]=rp+1,rr[i]=lp-1;
}
int cnt=0,ans=0;
for(int i=1;i<=n;i++){
if(rl[i]<=rr[i])qu[++cnt]={rl[i],rr[i],i};
}
sort(qu+1,qu+cnt+1,cmp);
for(int i=1,ql=1,qr=0;i<=cnt;i++){
while(ql>qu[i].l)Add(--ql);
while(qr<qu[i].r)Add(++qr);
while(ql<qu[i].l)Del(ql++);
while(qr>qu[i].r)Del(qr--);
ans=(ans+ask(1,qu[i].id))%mod;
}
printf("%lld\n",ans);
return 0;
}
坏了,赛时的做法好像很唐。
注意到如果一个点对应的合法区间存在,那么这个区间一定有一个端点是它自己!所以直接数点就行,这就是 \(\mathcal{O}(n\log n)\) 的了。
似乎还有分治的做法,不太会。
B 【1202 A组】人生画卷
神秘式子题,没看懂证明,放一个 g8c 的证明。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+7;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int jc[1000005],iv[1000005],ij[1000005];
int C(int n,int m){
if(n<0||m<0||n-m<0)return 0;
return jc[n]*ij[m]%mod*ij[n-m]%mod;
}
signed main(){
int n=read(),m=read(),t1=1,t2=1,ans=0;
jc[0]=1;for(int i=1;i<=m;i++)jc[i]=jc[i-1]*i%mod;
iv[1]=1;for(int i=2;i<=m;i++)iv[i]=mod-(mod/i)*iv[mod%i]%mod;
ij[0]=1;for(int i=1;i<=m;i++)ij[i]=ij[i-1]*iv[i]%mod;
for(int i=1;i<=m;i++){
ans=(ans+t1*t2%mod*jc[i-1]%mod*C(m,i)%mod)%mod;
t1=t1*iv[i]%mod*(n-i+1)%mod,t2=t2*iv[i]%mod*(n-i)%mod;
}
printf("%lld\n",ans);
return 0;
}
C 【1202 A组】博弈游戏
P4694 [PA2013] Raper。鉴定为【模板】模拟费用流。
一眼的费用流。考虑每个 \(i\) 向所有 \(j\ge i\) 的 \(j+n\) 连一条免费边,然后原点向 \(i\) 连 \(a_i\) 的边,\(i+n\) 向汇点连 \(b_i\) 的边,限制一下最大流为 \(k\) 就行。注意到会向后缀连边,可以直接优化一下建图跑 flow,能获得 44pts。
考虑更强力的做法。这个费用流模型告诉我们,问题的答案关于 \(k\) 有凸性,可以使用 wqs 二分来优化。二分额外代价 \(c\),容易发现现在变成了一个 CF865D 那样的经典反悔贪心的样子,直接做就行。复杂度 \(\mathcal{O}(n\log^2 n)\)。
能不能再强力一点?考虑模拟费用流,相当于 \(k\) 次操作,每次选两个位置 \(i,j\),让 \(c_i\) 加一并让 \(c_j\) 减一,代价是 \(a_i+b_j\),需要满足每次操作后 \(c_i\) 的前缀和都非负。为什么可以这样转化?听题解说这是因为每次增广的时候我们只会退流中间的边,所以这只会改变原来选的边的匹配性,原来那些边该选还得选。据说这个结论正确性显然,但我不太会证,感觉就很显然吧.jpg
考虑直接线段树维护 \(a_i+b_j\) 最小的合法选取方案 \((i,j)\)。当 \(i\le j\) 时没有限制,但是 \(i>j\) 时需要让 \([i,j)\) 内的前缀和大于 0。这个信息并不好维护,考虑维护一个很高妙的东西,下面给一坨定义:
记当前区间为 \([l,r]\)。
-
\(\text{mina}/\text{minb}\) 表示区间里面 \(a/b\) 数组最小值所在的位置。
-
\(va\) 表示这个区间里面选择 \(i\le j\) 的 \(a_i+b_j\) 最小的方案。
-
\(vc\) 表示这个区间里面选择 \(i>j\) 的 \(a_i+b_j\) 最小的方案。
-
\(vb\) 表示这个区间里面选择 \(i>j\) 的 \(a_i+b_j\) 最小的,且同时满足 \([i,j)\) 之间的前缀和的最小值大于 \([l,r]\) 里面前缀和的最小值的方案。
-
\(tag/mn\) 表示区间加法懒标记和 \([l,r]\) 内所有位置前缀和的最小值。
-
\(\text{alim}\) 表示满足 \([l,\text{alim})\) 区间中前缀和最小值大于 \([l,r]\) 前缀和最小值的位置中,\(a\) 数组对应位置上值最小的一个。
-
\(\text{blim}\) 表示满足 \([\text{blim},r]\) 区间中前缀和最小值大于 \([l,r]\) 前缀和最小值的位置中,\(b\) 数组对应位置上值最小的一个。
注意:\(\text{alim}\) 定义中区间是不到其本身位置的,但是 \(\text{blim}\) 的定义取到了,所以最初建线段树在 \(l=r\) 时可以给 \(\text{alim}\) 赋值,但不能给 \(\text{blim}\) 赋。
下面是 pushup 的过程:
-
先用左右儿子内的信息直接更新 \(va,vb,vc,\text{mina},\text{minb},mn\)。
-
用 \((\text{mina}(ls),\text{minb}(rs))\) 更新 \(va\),用 \((\text{mina}(rs),\text{minb}(ls))\) 更新 \(vc\)。
然后是大力分讨:
-
当 \(mn(ls)>mn(rs)\) 时,用 \(vc(ls)\) 和 \((\text{alim}(rs),\text{minb}(ls))\) 更新 \(vb\),用 \(\text{alim}(rs)\) 和 \(\text{mina}(ls)\) 更新 \(\text{alim}\),用 \(\text{blim}(rs)\) 更新 \(\text{blim}\)。
-
当 \(mn(ls)<mn(rs)\) 时,用 \(vc(rs)\) 和 \((\text{mina}(rs),\text{blim}(ls))\) 更新 \(vb\),用 \(\text{alim}(ls)\) 更新 \(\text{alim}\),用 \(\text{blim}(rs)\) 和 \(\text{minb}(ls)\) 更新 \(\text{blim}\)。
-
当 \(mn(ls)=mn(rs)\) 时,用 \((\text{alim}(rs),\text{blim}(ls))\) 更新 \(vb\),用 \(\text{alim}(ls)\) 更新 \(\text{alim}\),用 \(\text{blim}(rs)\) 更新 \(\text{blim}\)。
正确性显然,建议自己推一遍。
注意到当 \([l,r]=[1,n]\) 时因为上一次操作后每个位置前缀和都不小于 0,所以全局前缀和最小值一定为 0,这就刚好符合题目条件了。
直接重复 \(k\) 次,每次选最小的位置,复杂度 \(\mathcal{O}(k\log n)\)。
实现的时候最好把 \(a_0,b_0\) 设成 \(\infty\),那些变量没有值的时候设成 0,这样会方便很多。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,m,a[500005],b[500005];
struct Info{
int x,y;
Info operator +(const Info &o)const{
if(a[x]+b[y]<=a[o.x]+b[o.y])return {x,y};
return o;
}
};
struct segtree{
#define ls (p<<1)
#define rs (p<<1|1)
#define lson l,mid,ls
#define rson mid+1,r,rs
struct Node{
int mina,minb;
Info va,vc,vb;
int tag,mn,alim,blim;
}c[2000005];
void pushup(int p){
if(a[c[ls].mina]<a[c[rs].mina])c[p].mina=c[ls].mina;
else c[p].mina=c[rs].mina;
if(b[c[ls].minb]<b[c[rs].minb])c[p].minb=c[ls].minb;
else c[p].minb=c[rs].minb;
c[p].mn=min(c[ls].mn,c[rs].mn);
c[p].va=c[ls].va+c[rs].va;
c[p].vb=c[ls].vb+c[rs].vb;
c[p].vc=c[ls].vc+c[rs].vc;
c[p].va=c[p].va+(Info){c[ls].mina,c[rs].minb};
c[p].vc=c[p].vc+(Info){c[rs].mina,c[ls].minb};
if(c[ls].mn>c[rs].mn){
c[p].vb=c[p].vb+c[ls].vc;
c[p].vb=c[p].vb+(Info){c[rs].alim,c[ls].minb};
if(a[c[ls].mina]<=a[c[rs].alim])c[p].alim=c[ls].mina;
else c[p].alim=c[rs].alim;
c[p].blim=c[rs].blim;
}
else if(c[ls].mn<c[rs].mn){
c[p].vb=c[p].vb+c[rs].vc;
c[p].vb=c[p].vb+(Info){c[rs].mina,c[ls].blim};
c[p].alim=c[ls].alim;
if(b[c[rs].minb]<=b[c[ls].blim])c[p].blim=c[rs].minb;
else c[p].blim=c[ls].blim;
}
else{
c[p].vb=c[p].vb+(Info){c[rs].alim,c[ls].blim};
c[p].alim=c[ls].alim;
c[p].blim=c[rs].blim;
}
}
void pushdown(int p){
c[ls].mn+=c[p].tag,c[ls].tag+=c[p].tag;
c[rs].mn+=c[p].tag,c[rs].tag+=c[p].tag;
c[p].tag=0;
}
void build(int l,int r,int p){
c[p].tag=0;
if(l==r){
c[p].mina=c[p].minb=l;
c[p].va=(Info){l,l},c[p].vb=c[p].vc=(Info){0,0};
c[p].mn=0;
c[p].alim=l;
c[p].blim=0;
return;
}
int mid=(l+r)>>1;
build(lson);build(rson);
pushup(p);
}
void upd(int l,int r,int p,int x){
if(l==r)return;
int mid=(l+r)>>1;pushdown(p);
if(x<=mid)upd(lson,x);
else upd(rson,x);
pushup(p);
}
void add(int l,int r,int p,int L,int R,int k){
if(L>R)return;
if(L<=l&&r<=R){
c[p].mn+=k,c[p].tag+=k;
return;
}
int mid=(l+r)>>1;pushdown(p);
if(L<=mid)add(lson,L,R,k);
if(R>mid)add(rson,L,R,k);
pushup(p);
}
#undef lson
#undef rson
#undef ls
#undef rs
}Tr;
signed main(){
n=read(),m=read();a[0]=b[0]=inf;
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=n;i++)b[i]=read();
Tr.build(1,n,1);int ans=0;
while(m--){
Info tmp=Tr.c[1].va+Tr.c[1].vb;
ans+=a[tmp.x]+b[tmp.y];
if(tmp.x<=tmp.y)Tr.add(1,n,1,tmp.x,tmp.y-1,1);
else Tr.add(1,n,1,tmp.y,tmp.x-1,-1);
a[tmp.x]=inf,b[tmp.y]=inf;
Tr.upd(1,n,1,tmp.x),Tr.upd(1,n,1,tmp.y);
}
printf("%lld\n",ans);
return 0;
}
C0427 【1204 A组】模拟测试
A 【1204 A组】奇迹之夜
简单 dp。以 \(k\) 号地点为根,记 \(f_{i,0/1/2/3}\) 表示在以 \(i\) 为根的子树内,\(i\) 这个点选了日常/选了聚会且旁边已经有了后勤/选了聚会且旁边还没有后勤/选了后勤的最大价值和。可以随便转移一下。每次询问的限制相当于深度不超过 \(L\) 的点可以随便选,如果按深度分层,相当于上面的随便选,中间那层的 \(f\) 求和。dp 完之后预处理一下即可。记得判掉 \(L\ge n\) 的情况。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,nxt;
}e[200005];
int tot,head[100005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int a[100005],b[100005],f[100005][5],g[100005],h[100005],s[100005],dep[100005];
void dfs(int u,int fa,int d){
dep[u]=d;
f[u][0]=a[u],f[u][1]=b[u],f[u][2]=b[u],f[u][3]=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
dfs(v,u,d+1);
}
vector<int>tmp;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
f[u][0]+=max({f[v][0],f[v][1],f[v][3]});
f[u][1]+=max({f[v][0],f[v][1]}),tmp.push_back(f[v][3]-max({f[v][0],f[v][1]}));
f[u][2]+=max({f[v][0],f[v][1]});
f[u][3]+=max({f[v][0],f[v][1],f[v][2],f[v][3]});
}
sort(tmp.begin(),tmp.end(),[](int x,int y){return x>y;});
if(tmp.empty())f[u][1]=-inf;
else{
f[u][1]+=tmp[0];
for(int i=1;i<(int)tmp.size();i++)f[u][1]+=max(0ll,tmp[i]);
}
}
signed main(){
int n=read(),m=read();
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=n;i++)b[i]=read();
for(int i=1;i<=n;i++)b[i]=max(b[i],a[i]);
int root=read();
for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
dfs(root,0,0);
for(int i=1;i<=n;i++)h[dep[i]]+=b[i];
s[0]=h[0];for(int i=1;i<=n;i++)s[i]=s[i-1]+h[i];
for(int i=1;i<=n;i++)g[dep[i]]+=max({f[i][0],f[i][1],f[i][2],f[i][3]});
while(m--){
int l=read();
if(l>=n)printf("%lld\n",s[n]);
else if(l)printf("%lld\n",g[l]+s[l-1]);
else printf("%lld\n",g[l]);
}
return 0;
}
B 【1204 A组】树莓立方体
题解做法没看懂,写一个曦老师优秀做法。
考虑询问离线,挂到右端点,从大到小排序。那么现在从右往左移动右端点时就需要删除一个点,把一段分裂成几段,这个可以用 ST 表加二分处理。发现这个过程反过来就是每次加一个点,然后合并几段,于是容易证明这里均摊复杂度是对的。
为什么要从右往左扫呢?因为这个题是要维护一个每行 \(\min\) 的 \(\max\),如果从左往右扫 \(\min\) 只会越来越小,此时无法忽略之前那个操作区间的影响,就寄了。但是反过来分裂出来的区间只会越来越大,所以之前的区间就直接没影响了,可以直接开一颗线段树维护。支持区间取 max,区间 \(F\) 值历史和,直接套 C 【1129 A组】序列 的板子就行。
有一些小细节:比如每次删点的时候那个点之后就不能再更新历史和了,还有问的是一段时间内的历史和,需要拆询问。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int k,n,q,A,B,C,a[25][50005],b[25][50005],s[50005];
int ql[50005],qr[50005],ans[50005];vector<int>v1[50005],v2[50005];
int F(int x){
if(x==0)return 0;
return A^(B*x+C);
}
struct segtree{
#define ls (p<<1)
#define rs (p<<1|1)
#define lson l,mid,ls
#define rson mid+1,r,rs
struct Node{
int cnt,mn,se,tag,ad,dl,a,ta;
}c[4000005];
void pushup(int p){
c[p].mn=min(c[ls].mn,c[rs].mn);c[p].cnt=0,c[p].se=inf;
if(c[ls].mn==c[p].mn)c[p].cnt+=c[ls].cnt,c[p].se=min(c[p].se,c[ls].se);
else c[p].se=min(c[p].se,c[ls].mn);
if(c[rs].mn==c[p].mn)c[p].cnt+=c[rs].cnt,c[p].se=min(c[p].se,c[rs].se);
else c[p].se=min(c[p].se,c[rs].mn);
c[p].ad=c[ls].ad+c[rs].ad;
c[p].dl=c[ls].dl+c[rs].dl;
}
void pushdown(int l,int r,int p){
int mid=(l+r)>>1,ln=mid-l+1,rn=r-mid;
if(c[ls].mn<c[p].tag)c[ls].ad+=c[ls].cnt*c[p].a,c[ls].dl+=c[ls].cnt*c[p].ta,c[ls].a+=c[p].a,c[ls].ta+=c[p].ta;
if(c[rs].mn<c[p].tag)c[rs].ad+=c[rs].cnt*c[p].a,c[rs].dl+=c[rs].cnt*c[p].ta,c[rs].a+=c[p].a,c[rs].ta+=c[p].ta;
if(c[ls].mn<c[p].tag)c[ls].tag=c[p].tag,c[ls].mn=c[p].tag;
if(c[rs].mn<c[p].tag)c[rs].tag=c[p].tag,c[rs].mn=c[p].tag;
c[p].a=c[p].ta=0,c[p].tag=-inf;
}
void build(int l,int r,int p){
c[p].a=c[p].ta=0,c[p].tag=-inf;
if(l==r){
c[p].mn=c[p].ad=c[p].dl=0,c[p].se=inf,c[p].cnt=1;
return;
}
int mid=(l+r)>>1;
build(lson);build(rson);
pushup(p);
}
void add(int l,int r,int p,int L,int R,int v,int t){
if(c[p].mn>=v)return;
if(L<=l&&r<=R){
if(c[p].mn<v&&v<c[p].se){
c[p].a+=(F(v)-F(c[p].mn)),c[p].ta+=t*(F(v)-F(c[p].mn));
c[p].ad+=c[p].cnt*(F(v)-F(c[p].mn)),c[p].dl+=c[p].cnt*t*(F(v)-F(c[p].mn));
c[p].tag=v,c[p].mn=v;
return;
}
}
if(l==r)return;
int mid=(l+r)>>1;pushdown(l,r,p);
if(L<=mid)add(lson,L,R,v,t);
if(R>mid)add(rson,L,R,v,t);
pushup(p);
}
void upd(int l,int r,int p,int x,int t){
if(l==r){
c[p].a+=(F(0)-F(c[p].mn)),c[p].ta+=t*(F(0)-F(c[p].mn));
c[p].ad+=c[p].cnt*(F(0)-F(c[p].mn)),c[p].dl+=c[p].cnt*t*(F(0)-F(c[p].mn));
c[p].tag=0,c[p].mn=0;
return;
}
int mid=(l+r)>>1;pushdown(l,r,p);
if(x<=mid)upd(lson,x,t);
else upd(rson,x,t);
pushup(p);
}
int ask(int l,int r,int p,int L,int R,int t){
if(L<=l&&r<=R){
return c[p].ad*t-c[p].dl;
}
int mid=(l+r)>>1,res=0;pushdown(l,r,p);
if(L<=mid)res+=ask(lson,L,R,t);
if(R>mid)res+=ask(rson,L,R,t);
return res;
}
#undef lson
#undef rson
#undef ls
#undef rs
}Tr;
int f[25][18][50005];
int ask(int I,int l,int r){
if(l>r)return inf;
int o=__lg(r-l+1);
return min(f[I][o][l],f[I][o][r-(1ll<<o)+1]);
}
signed main(){
k=read(),n=read(),q=read();
for(int i=1;i<=k;i++)for(int j=1;j<=n;j++)a[i][j]=read();
A=read(),B=read(),C=read();
for(int i=1;i<=q;i++){
ql[i]=read(),qr[i]=read(),v1[qr[i]].push_back(i),v2[ql[i]].push_back(i);
}
for(int j=1;j<=k;j++){
for(int i=1;i<=n;i++)f[j][0][i]=a[j][i];
for(int o=1;(1ll<<o)<=n;o++){
for(int i=1;i+(1ll<<o)-1<=n;i++){
f[j][o][i]=min(f[j][o-1][i],f[j][o-1][i+(1ll<<(o-1))]);
}
}
}
Tr.build(1,n,1);
for(int i=n,t=1;i>=1;i--,t++){
for(auto x:v1[i])ans[x]-=Tr.ask(1,n,1,ql[x],qr[x],t-1);
if(i+1<=n)Tr.upd(1,n,1,i+1,t-1);
for(int j=1;j<=k;j++){
int p=i;
while(p>=1&&a[j][p]>a[j][i+1]){
int l=1,r=p,res=0;
while(l<=r){
int mid=(l+r)>>1;
if(ask(j,mid,p)==a[j][p])res=mid,r=mid-1;
else l=mid+1;
}
Tr.add(1,n,1,res,p,a[j][p],t-1),p=res-1;
}
}
for(auto x:v2[i])ans[x]+=Tr.ask(1,n,1,ql[x],qr[x],t);
}
for(int i=1;i<=q;i++)printf("%lld\n",ans[i]);
return 0;
}
C 【1204 A组】爱上火车
考虑一个 25pts 的朴素 dp。定义 \(f_{i,j}\) 表示第 \(i\) 天坐了 \(j\) 次火车的最大价值和,有转移式 \(f_{i,j}=\max(f_{i-1,j},f_{i-1,j-1})+a_{j\bmod k,i}\)。发现题目中有刚好选 \(k\) 个的限制,考虑寻找凸性。大力瞪眼可以发现如果记 \(g_{p,q,i}=f_{i,pk+q}\),那么 \(g_{p,q,i}\) 关于 \(p\) 是凸的。证明略。考虑开一颗线段树维护凸包。具体的,考虑一个状态 \((l,r,p,q)\),表示从第 \(l\) 天到第 \(r\) 天,第 \(l\) 天时在第 \(p\) 座城市,第 \(r\) 天时在第 \(q\) 座城市,可以对线段树上每个区间 \([l,r]\),对每一对 \((p,q)\) 维护一个凸包,凸包上每个点 \((x,y)\) 表示坐了 \(x\) 次火车的最大价值和为 \(y\)。
合并是简单的。对于两个位置 \((x_1,y_1)\) 和 \((x_2,y_2)\),容易发现合并后新的点就是 \((x_1+x_2,y_1+y_2)\)。注意到这就是一个 max,+ 的卷积,即对这两个凸包求闵可夫斯基和,可以做到 \(\mathcal{O}(|S_1|+|S_2|)\)。所以转移就是你枚举 \((l,mid)\) 的开始城市 \(i\),结束城市 \(o\) 和 \((mid+1,r)\) 的结束城市 \(j\)。如果从 \(mid\) 天到第 \(mid+1\) 天没有坐火车就是 \((l,mid,i,o)+(mid+1,r,o,j)\),否则就是 \((l,mid,i,o)+(mid+1,r,(o+1)\bmod k,j)\),注意后面的转移需要让横坐标全部 +1。
算一下空间。\((l,r,p,q)\) 对应的凸包大小就是 \(\dfrac{r-l}{k}\)。由于每个点需要维护 \(k^2\) 个凸包,所以空间是 \(k^2\sum\dfrac{len}{k}=kn\log n\) 的。时间就是 \(k^3\sum\dfrac{len}{k}=k^2n\log n\)。4 秒钟的话常数大点应该也可以过。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
using namespace std;
typedef pair<ll,ll>pii;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,m,q,a[5][100005];
struct convex{
vector<pii>v;
convex add(){
convex b;b.v=v;
for(int i=0;i<(int)v.size();i++)b.v[i].fi++;
return b;
}
convex operator +(const convex &b)const{
if(v.empty()||b.v.empty())return (convex){vector<pii>()};
convex c;int i=1,j=1;
c.v.push_back({v[0].fi+b.v[0].fi,v[0].se+b.v[0].se});
while(i<(int)v.size()&&j<(int)b.v.size()){
if(v[i].se-v[i-1].se>=b.v[j].se-b.v[j-1].se)c.v.push_back({v[i].fi+b.v[j-1].fi,v[i].se+b.v[j-1].se}),i++;
else c.v.push_back({v[i-1].fi+b.v[j].fi,v[i-1].se+b.v[j].se}),j++;
}
while(i<(int)v.size())c.v.push_back({v[i].fi+b.v[j-1].fi,v[i].se+b.v[j-1].se}),i++;
while(j<(int)b.v.size())c.v.push_back({v[i-1].fi+b.v[j].fi,v[i-1].se+b.v[j].se}),j++;
return c;
}
void merge(convex b){
for(int i=0,j=0;i<(int)v.size();i++){
while(j<(int)b.v.size()&&b.v[j].fi<v[i].fi)j++;
if(j<(int)b.v.size()&&b.v[j].fi==v[i].fi)v[i].se=max(v[i].se,b.v[j].se);
}
}
}t[400005][5][5];
void build(int l,int r,int p){
if(l==r){
for(int i=0;i<m;i++)t[p][i][i].v.push_back({0,a[i][l]});
return;
}
int mid=(l+r)>>1;build(l,mid,p<<1);build(mid+1,r,p<<1|1);
for(int i=0;i<m;i++){
for(int j=0;j<m;j++){
for(int x=j-i;x<=r-l;x+=m)if(x>=0)t[p][i][j].v.push_back({x,0});
if(t[p][i][j].v.empty())continue;
for(int o=0;o<m;o++){
t[p][i][j].merge(t[p<<1][i][o]+t[p<<1|1][o][j]);
t[p][i][j].merge((t[p<<1][i][o]+t[p<<1|1][(o+1)%m][j]).add());
}
}
}
for(int l=0;l<m;l++)for(int r=0;r<m;r++){
vector<pii>().swap(t[p<<1][l][r].v);
vector<pii>().swap(t[p<<1|1][l][r].v);
}
}
ll ans[100005];
signed main(){
n=read(),m=read(),q=read();
for(int i=0;i<m;i++)for(int j=1;j<=n;j++)a[i][j]=read();
build(1,n,1);
for(int i=0;i<m;i++){
for(auto x:t[1][0][i].v)ans[x.fi+1]=max(ans[x.fi+1],x.se);
}
while(q--){
int pos=read();
printf("%lld\n",ans[pos]);
}
return 0;
}
C0432 【1208 A组】模拟测试
A 【1208 A组】蛋神的团建
P6571 [BalticOI 2017] Political Development。
又不会做蓝题。
考虑部分分。当 \(d_i\le k\) 时我们可以枚举一个在团里的点,然后团里其他点只会是它相邻的点,可以 \(nk^22^k\) 算。优化就是你发现周围只有至多 10 个点,记周围的点集为 \(S\),可以 \(k^2\log n\) 预处理 \(S\) 中每个点能到达 \(S\) 中哪些点,这样单次检查复杂度变为 \(\mathcal{O}(k)\)。
考虑正解。注意到还有性质没用。发现整张图一定存在至少一个点 \(d_i\le k\),那么对这个点跑上述做法后这个点就没用了,可以删去。删去后这个新图一定也有度数不超过 \(k\) 的点,直接递归做就行。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,k,ans,d[50005],vis[50005],id[50005],f[50005];set<int>g[50005];
void dfs(int u){
vis[u]=1;vector<int>tmp;tmp.push_back(u);
for(auto v:g[u])if(!vis[v])tmp.push_back(v);
int siz=(int)tmp.size();
for(int i=0;i<siz;i++){
f[i]=0;
for(int j=0;j<siz;j++){
int x=tmp[i],v=tmp[j];
if(g[x].count(v)||x==v)f[i]|=1ll<<j;
}
}
for(int i=0;i<(1ll<<siz);i++){
int flag=1;
for(int j=0;j<siz;j++){
if((i>>j)&1ll&&(f[j]&i)!=i){flag=0;break;}
}
if(flag)ans=max(ans,(int)__builtin_popcountll(i));
}
for(auto v:g[u])if(!vis[v])g[v].erase(u),d[v]--;
for(auto v:g[u])if(!vis[v]&&(int)g[v].size()<=k)dfs(v);
}
signed main(){
n=read(),k=read();
for(int i=0;i<n;i++){
d[i]=read();
for(int j=1;j<=d[i];j++)g[i].insert(read());
}
for(int i=0;i<n;i++)if(!vis[i]&&(int)g[i].size()<=k)dfs(i);
printf("%lld\n",ans);
return 0;
}
B 【1208 A组】行列式
大力响应曦老师优秀做法!
考虑一个纯数学的做法。先看 \(x=0\) 的情况。注意到如果一个行列式右上角那个三角内都没有值,那么这个行列式的值就是对角线上所有数乘起来。考虑从下往上消,假设当前消到 \((p_i,i)\),就把第 \(i\) 行抬上来消掉 \((p_i,i)\) 的值。注意到第 \(i\) 行我们似乎只消去了一个后缀,所以加上来的时候第 \(p_i\) 行前面也可能发生变化。但是注意到左下角一行只会有一个 \(b_i\),而且这个 \(b_i\) 在加上来的时候只会加到对角线上,所以可以 \(\mathcal{O}(n)\) 维护。
思考 \(x\neq 0\) 的情况,考虑转化。我们在这个行列式下面增加一行 0,在右边增加一列 0,并让 \(A_{n+1,n+1}\gets x\)。注意到此时新行列式的值就是原来行列式的 \(x\) 倍。然后把最后一列加到每一列上,这样最后一行就全变成 \(x\) 了,然后再把最后一行减到每一行上,这样每个非特殊位置上的 \(x\) 就变成 0 了,最后一列变成 \(-x\)。令 \(a_i\gets a_i-x\),\(b_i\gets b_i-x\),\(c_i\gets c_i-x\),此时这个行列式被转化成了一个类似 \(x=0\) 的情况。
按照 \(x=0\) 的方法,从第 \(n\) 行开始消,同时维护最后一列每个位置的值。然后我们再从第 1 列开始,从左往右把这一列加到最后一列上,消去对应位置的值。最后直接算即可。复杂度 \(\mathcal{O}(n)\)。
这个方法需要一些 \(c_i=0\) 时的补丁,但我没懂,而且不加补丁也能过()
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=998244353;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int qpow(int b,int p){
int res=1;
for(;p;p>>=1,b=b*b%mod)if(p&1ll)res=res*b%mod;
return res;
}
int c[1000005],p[1000005],a[1000005],b[1000005],d[1000005];vector<int>v[1000005];
void solve(){
int n=read(),k=read();
for(int i=1;i<=n;i++)c[i]=read();
for(int i=2;i<=n;i++)p[i]=read(),a[i]=read(),b[i]=read();
if(k==0){
for(int i=2;i<=n;i++)v[p[i]].push_back(i);
for(int i=n;i>=1;i--){
for(auto x:v[i]){
c[p[x]]=(c[p[x]]-b[x]*qpow(c[x],mod-2)%mod*a[x]%mod+mod)%mod;
}
}
int ans=1;
for(int i=1;i<=n;i++)ans=ans*c[i]%mod;
printf("%lld\n",ans);
}
else{
c[n+1]=k;for(int i=1;i<=n;i++)c[i]=(c[i]-k+mod)%mod;
for(int i=2;i<=n;i++)a[i]=(a[i]-k+mod)%mod,b[i]=(b[i]-k+mod)%mod;
for(int i=1;i<=n;i++)d[i]=mod-k;
for(int i=2;i<=n;i++)v[p[i]].push_back(i);
for(int i=n;i>=1;i--){
for(auto x:v[i]){
c[p[x]]=(c[p[x]]-b[x]*qpow(c[x],mod-2)%mod*a[x]%mod+mod)%mod;
d[p[x]]=(d[p[x]]-d[x]*qpow(c[x],mod-2)%mod*a[x]%mod+mod)%mod;
}
}
for(int i=1;i<=n;i++){
int t=d[i]*qpow(c[i],mod-2)%mod;
for(auto x:v[i]){
d[x]=(d[x]-t*b[x]%mod+mod)%mod;
}
c[n+1]=(c[n+1]-t*k%mod+mod)%mod;
}
int ans=1;
for(int i=1;i<=n+1;i++)ans=ans*c[i]%mod;
printf("%lld\n",ans*qpow(k,mod-2)%mod);
}
for(int i=1;i<=n;i++)v[i].clear();
}
signed main(){
int T=read();
while(T--){
solve();
}
return 0;
}
C 【1208 A组】万灵药
还不会。
C0437 【1214 A组】模拟测试
有点智慧的一场。
A 【1214 A组】道路
对于每条边 \((u,v,w)\),判断是否存在一个三元组 \((s,t,l)\) 满足 \(d_{s,u}+w+d_{v,t}\le l\) 或 \(d_{s,v}+w+d_{u,t}\le l\)。直接枚举是 \(\mathcal{O}(n^4)\) 的。
这两个条件差不多,只需要把 \((u,v,w)\) 转成 \((v,u,w)\) 再判断一遍就行了,所以只看第一个条件。考虑枚举 \(s,v\),移项得 \(w+d_{s,u}\le l-d_{v,t}\)。枚举 \(t\) 找到右边那一坨的最大值,再枚举 \(u\) 判断每条边是否满足不等式。复杂度 \(\mathcal{O}(n^3)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int d[305][305],eu[50005],ev[50005],ew[50005],l[305][305],res[90005];vector<int>g[305][305];
signed main(){
int n=read(),m=read(),ans=0;for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)d[i][j]=((i==j)?0ll:inf),l[i][j]=-inf;
for(int i=1,u,v,w;i<=m;i++)u=read(),v=read(),w=read(),d[u][v]=d[v][u]=min(d[u][v],w),eu[i]=u,ev[i]=v,ew[i]=w,g[u][v].push_back(i),g[v][u].push_back(i);
for(int k=1;k<=n;k++)for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
int q=read();for(int i=1,u,v,w;i<=q;i++)u=read(),v=read(),w=read(),l[u][v]=l[v][u]=max(l[u][v],w);
for(int s=1;s<=n;s++){
for(int j=1;j<=n;j++){
int k=-inf;for(int t=1;t<=n;t++)k=max(k,l[s][t]-d[j][t]);
for(int i=1;i<=n;i++)for(auto x:g[i][j])if(ew[x]+d[s][i]<=k)res[x]=1;
}
}
for(int i=1;i<=m;i++)ans+=res[i];
return printf("%lld\n",ans),0;
}
B 【1214 A组】颁奖
自个想出来的做法。
首先有一个转化,对这棵树求一个带权直径,即 \(a_u+a_v+\text{dist}(u,v)\) 的最大值,记为 \(l\),答案就是 \(\max\{\lceil\dfrac{l}{2}\rceil,\max\{a_i\}\}\)。为什么捏?因为每条边长度都是 1,你可以在直径上往较远的一边挪,取 max 是因为有可能点权太大了,挪到了点上还差很多。
问题变为怎么求直径。考虑一个 CF1149C 的做法,就是直径本质上是最长的一条路径,就是 \(a_u+a_v+d_u+d_v-2d_{\text{lca}(u,v)}\) 的最大值。然后显然有 \((dfn_u,dfn_v]\) 间深度最小的点的父亲就是 \(\text{lca}(u,v)\)(不考虑 \(u=v\)),直接用类似的方法线段树维护即可。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,nxt;
}e[400005];
int tot,head[200005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int cur,dfn[200005],rnk[200005],a[200005],dep[200005];
void dfs(int u,int fa){
dfn[u]=++cur,rnk[cur]=u,dep[u]=dep[fa]+1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
dfs(v,u);
}
}
struct Node{
int ulv,ul,lv,mx,mn;
Node operator +(const Node &b)const{
Node c;
c.ulv=max({ulv,b.ulv,ul+b.mx,mx+b.lv});
c.ul=max({ul,b.ul,mx-2*b.mn});
c.lv=max({lv,b.lv,b.mx-mn*2});
c.mx=max(mx,b.mx);
c.mn=min(mn,b.mn);
return c;
}
};
struct segtree{
#define ls p<<1
#define rs p<<1|1
#define lson l,mid,ls
#define rson mid+1,r,rs
Node c[800005];
void pushup(int p){
c[p]=c[ls]+c[rs];
}
void build(int l,int r,int p){
if(l==r){
c[p]=(Node){-inf,a[rnk[l]]-dep[rnk[l]]+2,a[rnk[l]]-dep[rnk[l]]+2,a[rnk[l]]+dep[rnk[l]],dep[rnk[l]]-1};
return;
}
int mid=(l+r)>>1;
build(lson),build(rson);
pushup(p);
}
void upd(int l,int r,int p,int x){
if(l==r){
c[p]=(Node){-inf,a[rnk[l]]-dep[rnk[l]]+2,a[rnk[l]]-dep[rnk[l]]+2,a[rnk[l]]+dep[rnk[l]],dep[rnk[l]]-1};
return;
}
int mid=(l+r)>>1;
if(x<=mid)upd(lson,x);
else upd(rson,x);
pushup(p);
}
#undef lson
#undef rson
#undef ls
#undef rs
}Tr;
signed main(){
int n=read();multiset<int>s;
for(int i=1;i<=n;i++)a[i]=read(),s.insert(a[i]);
for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
dfs(1,0);Tr.build(1,n,1);
int m=read();
while(m--){
int p=read(),x=read();s.erase(s.find(a[p])),a[p]=x,s.insert(x);
Tr.upd(1,n,1,dfn[p]);printf("%lld\n",max((Tr.c[1].ulv+1)/2,*s.rbegin()));
}
return 0;
}
还有一个题解做法:考虑任意两个点集 \(S,T\),记一个 \(f(S)=\{a,b\}\) 表示任意一对满足 \(a_a+a_b+\text{dist}(a,b)=\max\limits_{i,j\in S}\{a_i+a_j+\text{dist}(i,j)\}\) 的 \(\{a,b\}\in S\)。注意到 \(f(S\cap T)\) 的两个点一定在 \(f(S)\) 或 \(f(T)\) 中,直接比较即可。
C0438 【1215 A组】模拟测试
A 【1215 A组】赌博
记 \(g_{i,j}\) 表示 A 赢了 \(i\) 场,B 赢了 \(j\) 场时会下多少注。大力瞪眼可以发现 \(g_{i,n-1}=g_{n-1,i}=2^{i+n}\),且 \(g_{i,j}=\dfrac{g_{i+1,j}+g_{i,j+1}}{2}\)。直接递推可以获得 \(\mathcal{O}(n^2)\) 的 70pts。
考虑 \(\mathcal{O}(1)\) 单点计算 \(g_{i,j}\)。这个可以看成从 \((i,j)\) 开始,只能向右/向下走,那么从 \((i,j)\) 走到 \((k,n-1)\) 的方案数是 \(\dbinom{(k-i)+(n-1-j)}{(k-i)}\),走到 \((n-1,k)\) 的方案数是 \(\dbinom{(n-1-i)+(k-j)}{(k-j)}\),那么
把后面拎出来,记 \(f(i,j)=\sum\limits_{k=0}^{i-1}\dbinom{k+j-1}{k}+\sum\limits_{k=0}^{j-1}\dbinom{i-1+k}{k}\),那么 \(g_{i,j}=2^{i+j+1}f(n-1-i,n-1-j)\)。继续对 \(f\) 进行化简。
显然 \(f(i,j)=\dbinom{i+j}{i}\)。为什么捏?考虑 \(\dbinom{i+j}{i}=\dbinom{i+j-1}{i-1}+\dbinom{i+j-1}{j-1}\)。以 \(\dbinom{i+j-1}{i-1}\) 为例:
于是可以 \(\mathcal{O}(1)\) 计算。
关于瞪眼:其实有更严谨的方式得到递推式。记 \(f_{i,j}\) 表示 A 赢了 \(i\) 场,B 赢了 \(j\) 场时的获利,那么 \(f_{i,n-1}=-2^{2n-1}\),\(f_{n-1,i}=2^{2n-1}\),且 \(f_{i,j}+g_{i,j}=f_{i+1,j}\),\(f_{i,j}-g_{i,j}=f_{i,j+1}\),同样可以得到上式。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e18,mod=1e9+7,i2=(mod+1)/2;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int jc[400005],iv[400005],ij[400005];
int C(int n,int m){
if(n<0||m<0||n-m<0)return 0;
return jc[n]*ij[m]%mod*ij[n-m]%mod;
}
int a[400005],ans[400005],s[400005],t[400005],pw[400005];
signed main(){
int n=read(),m=0,x=0;
jc[0]=1;for(int i=1;i<=n+n;i++)jc[i]=jc[i-1]*i%mod;
iv[1]=1;for(int i=2;i<=n+n;i++)iv[i]=mod-(mod/i)*iv[mod%i]%mod;
ij[0]=1;for(int i=1;i<=n+n;i++)ij[i]=ij[i-1]*iv[i]%mod;
pw[0]=1;for(int i=1;i<=n+n;i++)pw[i]=pw[i-1]*2ll%mod;
while(scanf("%lld",&x)!=EOF)a[++m]=x;
for(int i=1,A=0,B=0;i<=m;i++){
printf("%lld\n",pw[A+B+1]*C((n-1-A)+(n-1-B),(n-1-A))%mod);
if(a[i]==0)A++;
else B++;
}
return 0;
}
B 【1215 A组】镜子
不会。
C 【1215 A组】传染
唉,这么脑瘫的题怎么没写啊。
考虑 \(i\) 向所有距离不超过 \(r_i\) 的点连边,缩点,没有入度的 scc 数量就是答案。考虑优化。涉及距离相关限制,考虑点分治。发现我们只需要缩点,所以重复连边是不会有影响的。那么在当前这一层,记每个点到分治中心的距离是 \(d_i\),那么 \(i\) 会向所有 \(d_j\le d_i-r_i\) 的 \(j\) 连边,可以前缀优化建图,时空复杂度 \(\mathcal{O}(n\log^2 n)\),瓶颈在于排序。
注意优化建图后一个 scc 内可能全都是虚点,需要处理一下。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e9;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,w,nxt;
}e[10000005];
int tot,head[6000005];
void add(int u,int v,int w){
e[++tot]=(edge){v,w,head[u]},head[u]=tot;
}
int siz[300005],vis[300005];
void dfssiz(int u,int fa){
siz[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa||vis[v])continue;
dfssiz(v,u);siz[u]+=siz[v];
}
}
int root,mx[300005];
void dfsroot(int u,int fa,int sum){
mx[u]=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa||vis[v])continue;
dfsroot(v,u,sum);mx[u]=max(mx[u],siz[v]);
}
mx[u]=max(mx[u],sum-siz[u]);
if(mx[u]<mx[root])root=u;
}
int num,cnt,r[300005],a[300005],b[300005],s[300005];ll d[300005];vector<int>g[6000005];
void dfsdis(int u,int fa){
a[++num]=u;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].w;if(v==fa||vis[v])continue;
d[v]=d[u]+w,dfsdis(v,u);
}
}
void link(int u){
num=0,d[u]=0,dfsdis(u,0);
sort(a+1,a+num+1,[](int x,int y){return d[x]<d[y];});
for(int i=1;i<=num;i++)b[i]=a[i];
sort(b+1,b+num+1,[](int x,int y){return r[x]-d[x]<r[y]-d[y];});
for(int i=1;i<=num;i++)s[i]=++cnt,g[s[i]].push_back(a[i]);
for(int i=2;i<=num;i++)g[s[i]].push_back(s[i-1]);
for(int i=1,p=0;i<=num;i++){
while(p+1<=num&&d[a[p+1]]<=r[b[i]]-d[b[i]])p++;
if(p)g[b[i]].push_back(s[p]);
}
}
void solve(int u){
vis[u]=1,link(u);
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(vis[v])continue;
root=0,dfssiz(v,0),dfsroot(v,0,siz[v]),solve(root);
}
}
int n,cur,top,rc,st[6000005],dfn[6000005],low[6000005],in[6000005],bel[6000005],o[6000005];
void tarjan(int u){
dfn[u]=low[u]=++cur;st[++top]=u;in[u]=1;
for(auto v:g[u]){
if(!dfn[v])tarjan(v),low[u]=min(low[u],low[v]);
else if(in[v])low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
int v;rc++;
do{
v=st[top--];in[v]=0;
bel[v]=rc,o[rc]+=(v<=n);
}while(u!=v);
}
}
int deg[6000005],t[6000005];
signed main(){
n=read(),cnt=n;
for(int i=1;i<=n;i++)r[i]=read();
for(int i=1,u,v,w;i<n;i++){
u=read(),v=read(),w=read(),add(u,v,w),add(v,u,w);
}
mx[0]=inf;root=0;dfssiz(1,0);dfsroot(1,0,siz[1]);solve(root);
for(int i=1;i<=cnt;i++)if(!dfn[i])tarjan(i);
tot=0;for(int i=1;i<=rc;i++)head[i]=0;
for(int i=1;i<=cnt;i++){
for(auto j:g[i])if(bel[i]!=bel[j])add(bel[i],bel[j],0),deg[bel[j]]++;
}
for(int i=1;i<=rc;i++)t[i]=o[i];
queue<int>q;
for(int i=1;i<=rc;i++)if(!deg[i])q.push(i);
while(!q.empty()){
int u=q.front();q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;t[v]+=t[u];
if((--deg[v])==0)q.push(v);
}
}
int ans=0;
for(int i=1;i<=rc;i++)if(o[i]&&t[i]==o[i])ans++;
printf("%d\n",ans);
return 0;
}
C0441 【1222 A组】模拟测试
不做评价。
A 【1222 A组】社会达尔文
P5525 [Ynoi2012] WC2016 充满了失望。😱怎么都会 ynoi😱
B 【1222 A组】大国沙文
考虑朴素的 \(n^2\log V\) dp。定义 \(f_{i,j}\) 表示前 \(i\) 个数,选了 \(j\) 个 \(a_i\) 的以 \(i\) 结尾的最大子段和。二分答案 \(t\),转移就是 \(\max\{0,f_{i-1,j}+b_i\}\to f_{i,j}\),\(\max\{0,f_{i-1,j}+a_i\}\to f_{i,j+1}\),且需要满足 \(f_{i-1,j}\le t\)。注意到如果一个位置 \(i\) 满足 \(a_i\le b_i\),那么选 \(a_i\) 一定更优,记这样的位置的个数为 \(k'\)。如果 \(k'\le k\) 就可以直接强制令这些位置选 \(a_i\),否则可以交换 \(a,b\) 数组,并令 \(k\gets n-k\)。
现在,除去已经选定的位置,其他地方都满足 \(a_i>b_i\),所以 \(f_{i,j}\) 关于 \(j\) 单增,且差分也不降。考虑刻画两种转移,注意到如果我们维护所有 \((j,f_{i,j})\),转移相当于先让所有点上移 \(b_i\),再找到第一个斜率 \(>a_i-b_i\) 的位置,让它及其后面的点向右移 \(1\) 再向上移 \(a_i-b_i\)。考虑维护 \(f\) 的差分,于是上述过程可以简单的变成插入一个 \(a_i-b_i\)。删去 \(f_{i,j}>t\) 的位置是简单的,可以直接删。思考如何对 \(0\) 取 max。
考虑从前往后找到第一个真实值 \(\ge 0\) 的位置,前面的位置我们拉出来丢到一个 vector 里面,不参与以后的转移。为什么这样做捏,首先是为了复杂度,然后这些位置今后的差分一定都是 \(0\),因为如果真实值是正数显然,否则又会对 \(0\) 取 max,相当于没有变化。而且过程中插入的所有数显然都是 \(>0\) 的,所以这些位置一定一直都在最前面。求方案可以在 set 里存 pair,放一个下标,直接找 vector 和 set 里前 \(k\) 个位置就行。复杂度 \(\mathcal{O}(n\log n\log V)\),有问题可以看代码。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
typedef pair<int,int>pii;
const int inf=1e15;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,k,a[100005],b[100005],o[100005];vector<int>v;set<pii>s;
int check(int mid){
int base=0,sum=0;s.clear(),v.clear();
for(int i=1;i<=n;i++){
base+=b[i];
if(!o[i])s.insert({a[i]-b[i],i}),sum+=a[i]-b[i];
while(base+sum>mid){
if(s.empty())return 0;
sum-=(*s.rbegin()).fi,s.erase(prev(s.end()));
}
while(base<0){
if(s.empty()){base=0;break;}
pii u=*s.begin();sum-=u.fi;s.erase(u);
if(base+u.fi<0)base+=u.fi,v.push_back(u.se);
else u.fi+=base,base=0,s.insert(u),sum+=u.fi;
}
}
for(auto x:s)v.push_back(x.se);
return ((int)v.size()>=k);
}
signed main(){
n=read(),k=read();
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=n;i++)b[i]=read();
int cnt=0,all=0;
for(int i=1;i<=n;i++)if(a[i]<b[i])cnt++;
if(cnt>k){k=n-k,all=1;for(int i=1;i<=n;i++)swap(a[i],b[i]);}
for(int i=1;i<=n;i++)if(a[i]<b[i])k--,swap(a[i],b[i]),o[i]=1;
int l=0,r=inf,res=inf;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid))res=mid,r=mid-1;
else l=mid+1;
}
printf("%lld\n",res);assert(check(res));
for(int i=0;i<k;i++)o[v[i]]=1;
for(int i=1;i<=n;i++)putchar((o[i]^all)?'A':'B');
return 0;
}
C 【1222 A组】物种共产
[AGC056F] Degree Sequence in DFS Order。😱*4300😱。
C0442 【1223 A组】模拟测试
A 【1223 A组】车辆段
#3632. 「2021 集训队互测」Lovely Dogs。唉,集训队胡策。
考虑启发式合并,思考如何快速计算加入/删除一个数的贡献。注意到 \(f_d(ij)=f_d(i)f_d(j)f_d^2(ij)\),且如果令 \(g(n)\) 表示最大的 \(p\) 使得 \(p^{d+1}|n\),那么 \(f_d^2(ij)=[g(ij)=1]=\sum\limits_{k^{d+1}|ij}\mu(k)\)。代入得 \(f_d(ij)=f_d(i)f_d(j)\sum\limits_{k^{d+1}|ij}\mu(k)=f_d(i)\sum\limits_{k}\sum\limits_{j}f_d(j)[\dfrac{k^{d+1}}{\gcd(k^{d+1},i)}|j]\)。注意到 \(k\) 只需要枚举 \(i\) 的因数,因为如果 \(k\nmid i\),则 \(k>1\),那么要满足 \(k^{d+1}|ij\) 的话就需要存在 \(p>1,p^{k+1}|j\),即 \(f_d(j)=0\)。由于 \(a_i\) 是排列,于是加入和删除的复杂度优化到了均摊 \(\ln n\)。预处理一下就是 \(\mathcal{O}(n\log n\ln n)\)。
fun fact:预处理的时候写的 if(siz[son[u]]>siz[v])son[u]=v; 没发现导致卡了大半天的常。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
typedef pair<int,int>pii;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,d,t,a[200005],np[200005],p[200005],mu[200005];
int o1[4000005];pii o2[4000005];int c1[200005],c2[200005];
vector<int>tmp[200005];
struct edge{
int v,nxt;
}e[400005];
int tot,head[200005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int cur,siz[200005],son[200005],dfn[200005],rnk[200005];
void dfs1(int u,int fa){
siz[u]=1,son[u]=0,dfn[u]=++cur,rnk[cur]=u;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
dfs1(v,u);siz[u]+=siz[v];
if(siz[son[u]]<siz[v])son[u]=v;
}
}
int res,s[200005],g[200005],ans[200005],vis[200005],cnt[200005];vector<int>pos;
void add(int k){
if(!g[k])return;
for(int i=c1[k];i<c1[k+1];i++){
s[o1[i]]+=g[k];if(!vis[o1[i]])pos.push_back(o1[i]),vis[o1[i]]=1;
}
for(int i=c2[k];i<c2[k+1];i++){
res+=g[k]*o2[i].se*s[o2[i].fi];
}
}
void dfs2(int u,int fa,int t){
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa||v==son[u])continue;
dfs2(v,u,0);
}
if(son[u])dfs2(son[u],u,1);
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa||v==son[u])continue;
for(int j=dfn[v];j<=dfn[v]+siz[v]-1;j++)add(a[rnk[j]]);
}
add(a[u]);ans[u]=res;
if(!t){
for(auto x:pos)s[x]=0,vis[x]=0;
res=0,pos.clear();
}
}
ll qpow(ll b,int p){
ll res=1;
while(p){
if(p&1)res=res*b;
b=b*b,p>>=1;
}
return res;
}
ll gcd(ll x,ll y){
return ((!y)?x:gcd(y,x%y));
}
int mn[200005];
signed main(){
n=read(),d=read();
for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
for(int i=1;i<=n;i++)a[i]=read();
mu[1]=1,g[1]=1;
for(int i=2;i<=n;i++){
if(!np[i])p[++t]=i,mu[i]=-1,mn[i]=i;
for(int j=1;j<=t&&i*p[j]<=n;j++){
np[i*p[j]]=1,mn[i*p[j]]=p[j];
if(i%p[j]==0){mu[i*p[j]]=0;break;}
else mu[i*p[j]]=-mu[i];
}
}
for(int i=1;i<=n;i++){
int val=i,cnt=0,lst=-1;g[i]=1;
while(val>1){
if(mn[val]!=lst)cnt=0;
lst=mn[val],cnt++,g[i]=-g[i],val/=mn[val];
if(cnt>d){g[i]=0;break;}
}
}
for(int i=1;i<=n;i++)for(int j=i;j<=n;j+=i)c1[j]++;
for(int i=1;i<=n+1;i++)c1[i]+=c1[i-1];
for(int i=1;i<=n;i++)for(int j=i;j<=n;j+=i)o1[--c1[j]]=i;
for(int i=1;i<=n;i++){
if(!mu[i])continue;
ll t=qpow(i,d+1);if(t>1ll*n*n)break;
for(int j=i;j<=n;j+=i){
ll p=t/gcd(t,j);if(p<=n)c2[j]++;
}
}
for(int i=1;i<=n+1;i++)c2[i]+=c2[i-1];
for(int i=1;i<=n;i++){
if(!mu[i])continue;
ll t=qpow(i,d+1);if(t>1ll*n*n)break;
for(int j=i;j<=n;j+=i){
ll p=t/gcd(t,j);if(p<=n)o2[--c2[j]]={p,mu[i]};
}
}
dfs1(1,0);dfs2(1,0,0);
for(int i=1;i<=n;i++)printf("%d\n",ans[i]);
return 0;
}
B 【1223 A组】涂装
考虑一个合法的不和谐序列 \(a\) 要满足什么条件。令 \(a_0=a_n=1\),记若干个极大的下标集合 \(p_{i}\),其中 \(\forall i\neq j,p_i\cap p_j=\varnothing\),\(\cup_{i}p_i=\{0\le j\le n|a_j=1\}\),\(\forall i,j\in[1,|p_i|),p_{i,j+1}-p_{i,j}\le k\) 且 \(\forall t\in(p_{i,j},p_{i,j+1}),a_t=0\)。而且 \(\max_j\{p_{i,j}\}<\min_t\{p_{i+1,t}\}\)。注意到 \(a\) 合法的充要条件是,对于每个集合 \(p_i\),要么 \(p_i=\{0\}\) 或 \(\{n\}\),要么 \(\exists j,p_{i,j+1}-p_{i,j}=k\)。充要性可以感性理解。
定义 \(f_{i,j,0/1}\) 表示前 \(i\) 个位置,\(i\) 前面最近的为 \(1\) 的位置是 \(i-j\),且当前这个 \(p\) 中是否存在相邻距离差为 \(k\) 的点的答案(\(1\le j\le k\))。发现这样设状态不太好处理新开一段的转移,于是额外记 \(g_i\) 表示最近的 \(1\) 距离已经超过 \(k\) 的答案。有转移

初值是 \(f_{1,1,0}=1\)。注意到当 \(n>k\) 时钦定 \(a_0=1\) 对我们的纯色段判定产生了一定的影响:由上述转移方程可知,这个 dp 不会将本来合法的、列车有长度为 \(i,i>k\) 的未填色前缀的所有方案计入答案。因此我们再令 \(g_{k+1}=1\),即可补足答案。最后答案就是 \(g_n+f_{n,k,0}\sum_{i\le k} f_{n,i,1}-1\),减一是因为全是 \(0\) 的情况被多算了。
可以矩阵优化转移,复杂度 \(\mathcal{O}(T(2k)^3\log n)\),显然过不去。考虑预处理转移矩阵的 \(2^i\) 次方,这样每次就是向量乘矩阵,复杂度 \(\mathcal{O}((2k)^3\log n+T(2k)^2\log n)\)。
证明:

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int mod=1e9+7;
inline ll read(){
ll x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
ll T,n,m;
struct mat{
int a[105][105];
mat(){memset(a,0,sizeof(a));};
void init(){
for(int i=0;i<2*m+1;i++)a[i][i]=1;
}
mat operator *(const mat &b)const{
mat c;
for(int i=0;i<2*m+1;i++){
for(int k=0;k<2*m+1;k++){
if(a[i][k]==0)continue;
for(int j=0;j<2*m+1;j++){
c.a[i][j]=(c.a[i][j]+1ll*a[i][k]*b.a[k][j]%mod)%mod;
}
}
}
return c;
}
}t,pw[65];
mat qpow(mat b,ll p){
mat res;res.init();
for(;p;p>>=1,b=b*b)if(p&1ll)res=res*b;
return res;
}
void trans(mat &base,ll k){
for(int i=60;i>=0;i--){
if((k>>i)&1ll)base=base*pw[i];
}
}
int solve(){
n=read();if(n==m)return 1;
mat res;res.a[0][0]=1;trans(res,m);
res.a[0][2*m]=(res.a[0][2*m]+1)%mod;trans(res,n-m-1);
int ans=(res.a[0][2*m]+res.a[0][m-1]-1)%mod;if(ans<0)ans+=mod;
for(int j=1;j<=m;j++)ans=(ans+res.a[0][m+j-1])%mod;
return ans;
}
signed main(){
T=read(),m=read();
for(int i=1;i<m;i++)for(int o=0;o<2;o++)t.a[o*m+i-1][o*m+i]=1;
for(int i=1;i<=m;i++)for(int o=0;o<2;o++)t.a[o*m+i-1][(o|(i==m))*m]=1;
t.a[m+m-1][2*m]=t.a[2*m][0]=t.a[2*m][2*m]=1;
pw[0]=t;for(int i=1;i<=60;i++)pw[i]=pw[i-1]*pw[i-1];
while(T--)printf("%d ",solve());
return 0;
}
C 【1223 A组】Metro Walk
gym,会不了一点。
C0447 【1229 A组】模拟测试
A 【1229 A组】波斯菊
简单题。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,nxt;
}e[1000005];
int tot,head[500005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int c[500005],f[500005][2],g[500005][2];
void dfs(int u,int fa){
vector<int>tmp;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
dfs(v,u);tmp.push_back(v);
}
if(tmp.empty()){
f[u][0]=f[u][1]=0;
g[u][0]=g[u][1]=inf;
return;
}
{
f[u][1]=0;
for(int i=0;i<(int)tmp.size();i++){
if(c[tmp[i]])f[u][1]+=f[tmp[i]][0];
else f[u][1]+=min(g[tmp[i]][0],f[tmp[i]][1]+2);
}
f[u][0]=f[u][1];
for(int i=0;i<(int)tmp.size();i++){
if(!c[tmp[i]]){
f[u][0]=min(f[u][0],f[u][1]-min(g[tmp[i]][0],f[tmp[i]][1]+2)+(f[tmp[i]][0]+1));
}
}
}
if(c[u]==0){
g[u][0]=g[u][1]=inf;
int sum=0;
for(int i=0;i<(int)tmp.size();i++){
if(c[tmp[i]])sum+=f[tmp[i]][0];
else sum+=min(g[tmp[i]][0],f[tmp[i]][1]+2);
}
for(int i=0;i<(int)tmp.size();i++){
if(!c[tmp[i]]){
g[u][1]=min(g[u][1],sum-min(g[tmp[i]][0],f[tmp[i]][1]+2)+g[tmp[i]][1]+1);
}
else g[u][1]=min(g[u][1],sum-f[tmp[i]][0]+f[tmp[i]][1]+1);
}
vector<int>pre,suf;
pre.resize((int)tmp.size()+5);
suf.resize((int)tmp.size()+5);
pre[0]=0,suf[(int)tmp.size()+1]=0;
for(int i=0;i<(int)tmp.size();i++){
if(c[tmp[i]])pre[i+1]=min(pre[i],1ll);
else pre[i+1]=min(pre[i],(f[tmp[i]][0]+1)-min(g[tmp[i]][0],f[tmp[i]][1]+2));
}
for(int i=(int)tmp.size()-1;i>=0;i--){
if(c[tmp[i]])suf[i+1]=min(suf[i+2],1ll);
else suf[i+1]=min(suf[i+2],(f[tmp[i]][0]+1)-min(g[tmp[i]][0],f[tmp[i]][1]+2));
}
// printf("sum: [%lld,%lld]\n",u,sum);
for(int i=0;i<(int)tmp.size();i++){
if(!c[tmp[i]])g[u][0]=min(g[u][0],sum-min(g[tmp[i]][0],f[tmp[i]][1]+2)+(g[tmp[i]][1]+1)+min(pre[i],suf[i+2]));
else g[u][0]=min(g[u][0],sum-f[tmp[i]][0]+(f[tmp[i]][1]+1)+min(pre[i],suf[i+2]));
}
}
else{
g[u][0]=g[u][1]=inf;
}
}
signed main(){
int n=read(),k=read(),root=0;
for(int i=1;i<=k;i++)c[read()]=1;
for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
for(int i=1;i<=n;i++)if(c[i]){root=i;break;}
dfs(root,0);
// for(int i=1;i<=n;i++)printf("{%lld,%lld}{%lld,%lld}\n",f[i][0],f[i][1],g[i][0],g[i][1]);
printf("%lld\n",f[root][0]);
return 0;
}
B 【1229 A组】芦荀花
牛的。
连通块个数就等于点数减边数,考虑枚举两个区间 \(i\le j\),数 \(i,j\) 之间有多少条边,即 \(\sum_{x,\text{dfn}(x)\in[l_i,r_i]}[\text{dfn}(fa_x)\in[l_j,r_j]]\),二维数点。
考虑根号分治。当 \(k>\sqrt{n}\) 时直接 \(\mathcal{O}(n)\) 暴力,否则 \(\mathcal{O}(k^2)\) 枚举两个区间,用 \(\mathcal{O}(\sqrt{n})-\mathcal{O}(1)\) 的可持久化分块数点。注意到当 \(m\le\sqrt{n}\) 时 \(\dfrac{n}{m}m^2\) 的最大值就是 \(m=\sqrt{n}\) 的 \(n\sqrt{n}\),所以复杂度 \(\mathcal{O}(n\sqrt{n})\)。
考虑正经的 \(\log\) 做法。注意到有连边的区间对并不多,是 \(\mathcal{O}(k)\) 级别的。为什么捏,注意到这是 dfs 序,所以树上任意两条边端点对应的区间都是包含/不交的关系,所以区间之间的连边关系也是包含/不交的,考虑按包含关系建树,注意到此时只有 \(\mathcal{O}(k)\) 个节点。考虑枚举一个区间 \([l_i,r_i]\),找到小于 \(l_i\) 的最大的一个点,满足它是 \(i\) 里面一个 dfs 序对应的点的父亲的 dfs 序,那么它所在的区间就和 \(i\) 有连边,重复过程即可。不妨在认为在相邻的区间中有一个虚拟的区间,此时只有最多 \(2k+1\) 个区间。时间复杂度 \(\mathcal{O}((n+\sum k)\log n)\)。
啊啊啊啊,好像没太说清楚,再说一遍。考虑包含关系建树怎么搞的捏,就是排序,然后丢到栈里建个边对不对。那么一个区间被弹的时候会连一条,往前找最多多连一条,所以应该是不超过 \(2\sum k\) 条边。
点击查看代码
#include<bits/stdc++.h>
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
vector<int>g[500005];
int cur,dfn[500005],rnk[500005],pa[500005];
void dfs(int u,int fa){
dfn[u]=++cur,rnk[cur]=u,pa[u]=fa;
for(auto v:g[u])if(v!=fa)dfs(v,u);
}
int T,root[500005];
#define ls (c[p].lc)
#define rs (c[p].rc)
#define lson l,mid,ls
#define rson mid+1,r,rs
struct Node{
int lc,rc,s;
}c[15000005];
void pushup(int p){
c[p].s=c[ls].s+c[rs].s;
}
int build(int l,int r){
int p=++T;
if(l==r){c[p].s=0;return p;}
int mid=(l+r)>>1;
ls=build(l,mid),rs=build(mid+1,r);
pushup(p);return p;
}
int add(int l,int r,int q,int x,int v){
int p=++T;c[p]=c[q];
if(l==r){c[p].s+=v;return p;}
int mid=(l+r)>>1;
if(x<=mid)ls=add(l,mid,c[q].lc,x,v);
else rs=add(mid+1,r,c[q].rc,x,v);
pushup(p);return p;
}
int qry(int l,int r,int p,int q,int L,int R){
if(L>R)return 0;
if(L<=l&&r<=R)return c[q].s-c[p].s;
int mid=(l+r)>>1,res=0;
if(L<=mid)res+=qry(lson,c[q].lc,L,R);
if(R>mid)res+=qry(rson,c[q].rc,L,R);
return res;
}
int ask(int l,int r,int p,int q,int x){
if(l==r)return ((c[q].s-c[p].s)?l:0);
int mid=(l+r)>>1;
if(x<=mid)return ask(lson,c[q].lc,x);
if(c[c[q].rc].s-c[rs].s){
int res=ask(rson,c[q].rc,x);
if(res)return res;
}
return ask(lson,c[q].lc,x);
}
int l[500005],r[500005],L[500005],R[500005],o[500005];
signed main(){
int n=read(),q=read(),b=read(),lst=0;
for(int i=1,u,v;i<n;i++)u=read(),v=read(),g[u].push_back(v),g[v].push_back(u);
for(int i=1;i<=n;i++)sort(g[i].begin(),g[i].end());
dfs(1,0);root[0]=root[1]=build(1,n);
for(int i=2;i<=n;i++)root[i]=add(1,n,root[i-1],dfn[pa[rnk[i]]],1);
while(q--){
int k=read(),res=-1;
for(int i=1;i<=k;i++)l[i]=read()^lst,r[i]=read()^lst,res+=r[i]-l[i]+1;
for(int i=1;i<=k;i++){
res-=qry(1,n,root[l[i]-1],root[r[i]],l[i],r[i]);
int pos=l[i]-1;
while(pos>=1){
int tmp=ask(1,n,root[l[i]-1],root[r[i]],pos);if(!tmp)break;
int t=upper_bound(l+1,l+k+1,tmp)-l-1;
if(tmp<=r[t])res-=qry(1,n,root[l[i]-1],root[r[i]],l[t],r[t]),pos=l[t]-1;
else pos=r[t];
}
}
printf("%d\n",res),lst=res*b;
}
return 0;
}

浙公网安备 33010602011771号