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)}\),那么

\[\begin{aligned} g_{i,j}&=\sum\limits_{k=i}^{n-2}\dfrac{1}{2^{(k-i)+(n-1-j)}}\dbinom{(k-i)+(n-1-j)}{(k-i)}2^{n+k}+\sum\limits_{k=j}^{n-2}\dfrac{1}{2^{(n-1-i)+(k-j)}}\dbinom{(n-1-i)+(k-j)}{(k-j)}2^{n+k}\\ &=2^{i+j+1}\left(\sum\limits_{k=0}^{n-2-i}\dbinom{k+(n-1-j)}{k}+\sum\limits_{k=0}^{n-2-j}\dbinom{(n-1-i)+k}{k}\right)\\ \end{aligned} \]

把后面拎出来,记 \(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}\) 为例:

\[\begin{aligned} \dbinom{i+j-1}{i-1}&=\dbinom{i+j-2}{i-1}+\dbinom{i+j-2}{i-2}\\ &=\dbinom{(i-1)+j-1}{(i-1)}+(\dbinom{i+j-3}{i-2}+\dbinom{i+j-3}{i-3})\\ &=\dbinom{(i-1)+j-1}{(i-1)}+\dbinom{(i-2)+j-1}{(i-2)}+(\dbinom{i+j-4}{i-3}+\dbinom{i+j-4}{i-4})\\ &=\ldots\\ &=\sum\limits_{k=0}^{i-1}\dbinom{k+j-1}{k} \end{aligned} \]

于是可以 \(\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组】大国沙文

P9051 [PA2021] Wystawa。妙题!

考虑朴素的 \(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;
}

C 【1229 A组】豆蔻花

posted @ 2023-12-14 09:54  xx019  阅读(37)  评论(0)    收藏  举报