4th UCUP 比赛记录

队友是 @wtcqwq 和 @DrAlfred,队名叫 maze.size() = 0,但是被邪恶青鱼吞了变成 maze.size 了。

会记录比赛大体情况和我过的题以及赛后订的题。

Stage 1: Grand Prix of Korolyov

6 题,顺序是 CDFKHE。

过 4 题吃 1 罚,直接成为队伍大爹。

赛后 9 min 过 B。

B. Domain Compression

考虑对每对 \((u,v)\) 分别计数它出现了几次,那么要求 \(u,v\) 均未被删除且 \((u,v)\) 路径上的中间点全被删除,对删点树为 \(k\) 的答案贡献为: \(\displaystyle \binom {k}{\operatorname{dist}(u,v)-1}2^{n-{dist}(u,v)-1}\)

发现这个东西只和 \(\operatorname{dist}(u,v)\) 相关,令 \(\displaystyle c_k=\sum_{1\leq u\lt v\leq n} [\operatorname{dist}(u,v)=k]\),那么 \(\displaystyle ans_k=\sum_{d=1}^{k+1} c_d2^{n-d-1}\binom{k}{d}\)

这显然是卷积的形式,那么只要求出 \(c_k\) 就可以了,直接点分治+卷积计算可以做到 \(O(n\log^2 n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=1e5+9;
const int mod=998244353;

inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int QPow(int x,int y){
	int res=1;
	while(y){
		if(y&1) MulAs(res,x);
		MulAs(x,x);
		y>>=1;
	}
	return res;
}
#define Inv(x) QPow(x,mod-2)

const int gmod=3;
const int invg=Inv(gmod);
using Poly=vector<int>;

inline void NTT(Poly &f,Poly &r,int lim,int flag){
    for(int i=0;i<lim;i++) if(i<r[i]) swap(f[i],f[r[i]]);
    for(int k=1;k<lim;k<<=1){
        int len=k<<1,gn=QPow(flag>0?gmod:invg,(mod-1)/len);
        for(int i=0;i<lim;i+=len){
            for(int j=0,g=1;j<k;j++,MulAs(g,gn)){
                int tmp=Mul(f[i+j+k],g);
                f[i+j+k]=Sub(f[i+j],tmp);
                f[i+j]=Add(f[i+j],tmp);
            }
        }
    }
    if(!~flag){
        int inv=Inv(lim);
        for(int &x:f) MulAs(x,inv);
    }
}
inline Poly Conv(Poly f,Poly g){
    int len=f.size()+g.size()-1,lim=1;
    while(lim<len) lim<<=1;
    f.resize(lim,0),g.resize(lim,0);

    Poly rev(lim,0),h(lim,0);
    for(int i=0;i<lim;i++){
        rev[i]=rev[i>>1]>>1;
        if(i&1) rev[i]|=lim>>1;
    }
    NTT(f,rev,lim,1),NTT(g,rev,lim,1);
    for(int i=0;i<lim;i++) h[i]=Mul(f[i],g[i]);
    NTT(h,rev,lim,-1);

    h.resize(len,0);
    return h;
}

int fac[N],ifac[N];
inline void Init(int lim){
	fac[0]=1;
	for(int i=1;i<=lim;i++) fac[i]=Mul(fac[i-1],i);
	ifac[lim]=Inv(fac[lim]);
	for(int i=lim-1;~i;i--) ifac[i]=Mul(ifac[i+1],i+1);
}
inline int C(int n,int m){
	if(m<0||m>n) return 0;
	else return Mul(fac[n],Mul(ifac[m],ifac[n-m]));
}

const int inv2=Inv(2);
int c[N],ans[N],n;
vector<int> e[N];

int siz[N],vis[N];
inline void GetGrv(int x,int fa,int tot,int &grv){
	bool flag=0;
	siz[x]=1;
	for(int y:e[x]){
		if(vis[y]) continue ;
		if(y==fa) continue ;
		GetGrv(y,x,tot,grv);
		siz[x]+=siz[y];
		if(siz[y]>tot/2) flag=1;
	}
	if(tot-siz[x]>tot/2) flag=1;
	if(!flag) grv=x;
}
inline void GetNode(int x,int fa,vector<int> &v){
	v.push_back(x);
	for(int y:e[x]){
		if(vis[y]) continue ;
		if(y==fa) continue ;
		GetNode(y,x,v);
	}
}
int dep[N];
inline void GetDep(int x,int fa){
	if(!~fa) dep[x]=0;
	for(int y:e[x]){
		if(vis[y]) continue ;
		if(y==fa) continue ;
		dep[y]=dep[x]+1;
		GetDep(y,x);
	}
}
inline void Solve(int x,int tot,int fa,int ftot){
	GetGrv(x,-1,tot,x);
	GetGrv(x,-1,tot,x);
	vis[x]=1;
	
	vector<int> node;
	GetNode(x,-1,node);
	if(~fa){
		int tmp=0;
		for(int u:node) tmp=max(tmp,dep[u]);
		tmp<<=1;
		vector<int> f(tmp+5,0);
		for(int u:node) f[dep[u]]++;
		f=Conv(f,f);
		for(int u:node) f[dep[u]*2]--;
		for(int i=0;i<=tmp;i++) MulAs(f[i],inv2);
		for(int i=0;i<=tmp;i++) SubAs(c[i+1],f[i]);
	}
	GetDep(x,-1);
	int tmp=0;
	for(int u:node) tmp=max(tmp,dep[u]);
	tmp<<=1;
	vector<int> g(tmp+5,0);
	for(int u:node) g[dep[u]]++;
	g=Conv(g,g);
	for(int u:node) g[dep[u]*2]--;
	for(int i=0;i<=tmp;i++) MulAs(g[i],inv2);
	for(int i=0;i<=tmp;i++) AddAs(c[i+1],g[i]);

	for(int y:e[x]){
		if(vis[y]) continue ;
		Solve(y,siz[y],x,tot);
	}
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);

	cin>>n;
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}

	Init(n);
	Solve(1,n,-1,n);

	vector<int> f(n+1,0),g(n+1,0);
	for(int i=0;i<=n;i++){
		f[i]=ifac[i];
		g[i]=Mul(fac[n-i],c[i]);
	}
	vector<int> h=Conv(f,g);
	for(int i=1;i<=n-2;i++) ans[i]=Mul(Mul(fac[i],ifac[n-i-2]),h[i+2]);

	for(int i=1;i<=n;i++) cout<<ans[i]<<' ';cout<<endl;

    return 0;
}

C. Staple Stable

因为 \(hw\leq S\),所以 \(\min(h,w)\leq \sqrt S\)。然后枚举 \(\min(h,w)\) 之后可以得出另一维最小划分代价,对所有方案取最小值即可。

#include<bits/stdc++.h>

using namespace std;

signed main(){
	int T;
	cin>>T;
	while(T--){
		int n,m,s;
		cin>>n>>m>>s;

		int ans=n+m,tmp=sqrt(s);
		for(int len=1;len<=min(tmp,n);len++){
			int i=(n+len-1)/len-1;
			int t=s/len;
			if(!t) continue ;
			int c=(m+t-1)/t-1;
			ans=min(ans,i+c);
		}
		for(int len=1;len<=min(tmp,m);len++){
			int i=(m+len-1)/len-1;
			int t=s/len;
			if(!t) continue ;
			int c=(n+t-1)/t-1;
			ans=min(ans,i+c);
		}

		cout<<ans<<endl;
	}

	return 0;
}

F. Yet Another MST Problem

因为这是一个排列,所以每个数只出现一次,那么从小到大枚举 \(i\),将所有 \(r\lt pos_i\)\(l\gt pos_i\) 的区间 \([l,r]\) 缩在一起,代价是 \(i\),拿两个 list 就可以维护了,时间复杂度 \(O(n\alpha (n))\)

#include<bits/stdc++.h>

using namespace std;

using ll=long long;
const int N=3e5+9;
const int lgN=2e1;

list<int> lp,rp;
int p[N],ip[N],l[N],r[N],fa[N],n,m;
inline int Find(int x){return x==fa[x]?x:fa[x]=Find(fa[x]);}
inline void Merge(int x,int y){fa[Find(y)]=Find(x);}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);

	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>p[i],ip[p[i]]=i;
	for(int i=1;i<=m;i++) cin>>l[i]>>r[i];
	ip[n]=1e9;

	iota(fa+1,fa+m+1,1);
	
	vector<int> id(m);
	iota(id.begin(),id.end(),1);
	sort(id.begin(),id.end(),[](int i,int j){return r[i]<r[j];});
	lp=list<int>(id.begin(),id.end());
	sort(id.begin(),id.end(),[](int i,int j){return l[i]<l[j];});
	rp=list<int>(id.begin(),id.end());

	ll ans=0;
	for(int t=0;t<=n;t++){
		vector<int> tmpl;
		while(lp.size()&&r[lp.front()]<ip[t]){
			tmpl.push_back(lp.front());
			lp.pop_front();
		}
		vector<int> tmpr;
		while(rp.size()&&l[rp.back()]>ip[t]){
			tmpr.push_back(rp.back());
			rp.pop_back();
		}
		int x=0;
		if(!x&&tmpl.size()) x=tmpl.front();
		if(!x&&tmpr.size()) x=tmpr.back();
		if(!x) continue ;
		for(int y:tmpl) if(Find(x)!=Find(y)) Merge(x,y),ans+=t;
		for(int y:tmpr) if(Find(x)!=Find(y)) Merge(x,y),ans+=t;
		if(tmpl.size()) lp.push_front(tmpl.front());
		if(tmpr.size()) rp.push_back(tmpr.front());
	}

	cout<<ans<<endl;

	return 0;
}

H. Misread Problem

首先题面可以转化成求一组 \(x_{1\sim n}\) 使得 \(\displaystyle \sum_{i=1}^n\sum_{j=1}^k|x_i-a_{i,j}|\) 最小且 \(\displaystyle \sum_{i=1}^n x_i=m\)

显然 \(x_i\) 取到 \(x\) 的代价函数为 \(\displaystyle f_{i}(x)=\sum_{j=1}^k|x-a_{i,j}|\),然后发现它是下凸的。于是考虑在 \(f_i(x)\) 的取到最小值的方案上,再添加 \(x_i\) 之和的限制,那么每个函数每个单位的代价可以分成 \(O(k)\) 段,一共是 \(O(nk)\) 段,逐步贪心地添加即可。比较蠢地用堆实现了,时间复杂度 \(O(nk\log n)\)

#include<bits/stdc++.h>

using namespace std;

using ll=long long;
const int N=4e2+9;
const ll inf=1e18;

int a[N][N],n,m,k;
vector<ll> len[N];

signed main(){
	cin>>n>>m>>k;
	for(int i=1;i<=k;i++){
		for(int j=1;j<=n;j++) cin>>a[i][j];
	}

	ll cst=0,sum=0;
	for(int i=1;i<=n;i++){
		vector<int> tmp;
		for(int j=1;j<=k;j++) tmp.push_back(a[j][i]);
		sort(tmp.begin(),tmp.end());
		ll x=tmp[k-1>>1];
		for(int j=1;j<=k;j++) cst+=abs(a[j][i]-x);
		sum+=x;
	}
	
	for(int i=1;i<=n;i++){
		vector<int> tmp;
		for(int j=1;j<=k;j++) tmp.push_back(a[j][i]);
		sort(tmp.begin(),tmp.end());
		if(sum>m){
			for(int j=k-1>>1;~j;j--){
				if(j==(k-1>>1)) len[i].push_back(0);
				else len[i].push_back(tmp[j+1]-tmp[j]);
			}
			len[i].push_back(tmp.front());
		}else{
			for(int j=k>>1;j<k;j++){
				if(j==(k-1>>1)) len[i].push_back(0);
				else len[i].push_back(tmp[j]-tmp[j-1]);
			}
			len[i].push_back(inf);
		}
	}

	priority_queue<array<int,2>> q;
	for(int i=1;i<=n;i++) q.push({-0,i});
	while(sum!=m&&q.size()){
		int i=q.top()[1],j=-q.top()[0];
		q.pop();
		ll dlt=min(len[i][j],abs(m-sum));
		if(sum<=m) sum+=dlt;
		else sum-=dlt;
		cst+=(2ll*j-(k&1))*dlt;
		if(j+1<len[i].size()) q.push({-(j+1),i});
	}
	cst>>=1;

	cout<<cst<<endl;

	return 0;
}

K. Robot Construction

首先任意时刻能凑出来的值一定是一个前缀,所以可以做以下分讨:

  • \(lim\geq2a_i\):直接 \(lim'\leftarrow lim-a_i\)
  • \(lim\lt a_i\):低分通过,\(lim'\leftarrow lim\)
  • \(a_i\leq lim\lt 2a_i\):那么还不如低分通过,\(lim'\leftarrow a_i-1\)

显然 \(lim\) 有区间单调性,所以直接对 \(l\) 扫描线,每次二分出这三段的分界点,再修改即可,时间复杂度 \(O((n+q)\log n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=3e5+9;
const ll inf=1e18;

struct Node{
	int l,r;
	ll dat,atag,ctag;
}tr[N<<2];
#define l(x) tr[x].l
#define r(x) tr[x].r
#define dat(x) tr[x].dat
#define atag(x) tr[x].atag
#define ctag(x) tr[x].ctag

inline void PushUp(int x){dat(x)=max(dat(x<<1),dat(x<<1|1));}
inline void PushAdd(int x,ll k){dat(x)+=k,atag(x)+=k;}
inline void PushCov(int x,ll k){dat(x)=ctag(x)=k,atag(x)=0;}
inline void PushDown(int x){
	if(~ctag(x)) PushCov(x<<1,ctag(x)),PushCov(x<<1|1,ctag(x)),ctag(x)=-1;
	if(atag(x)) PushAdd(x<<1,atag(x)),PushAdd(x<<1|1,atag(x)),atag(x)=0;
}

inline void Build(int x,int l,int r){
	l(x)=l,r(x)=r,dat(x)=atag(x)=0,ctag(x)=-1;
	if(l(x)==r(x)) return ;
	int mid=l(x)+r(x)>>1;
	Build(x<<1,l,mid),Build(x<<1|1,mid+1,r);
	PushUp(x);
}
inline void AddVal(int x,int l,int r,ll k){
	if(l<=l(x)&&r(x)<=r) return PushAdd(x,k);
	int mid=l(x)+r(x)>>1;
	PushDown(x);
	if(l<=mid) AddVal(x<<1,l,r,k);
	if(r>mid) AddVal(x<<1|1,l,r,k);
	PushUp(x);
}
inline void Cover(int x,int l,int r,ll k){
	if(l<=l(x)&&r(x)<=r) return PushCov(x,k);
	int mid=l(x)+r(x)>>1;
	PushDown(x);
	if(l<=mid) Cover(x<<1,l,r,k);
	if(r>mid) Cover(x<<1|1,l,r,k);
	PushUp(x);
}
inline int LowerBound(int x,ll k){
	if(l(x)==r(x)) return l(x)+(dat(x)<k);
	int mid=l(x)+r(x)>>1;
	PushDown(x);
	if(k<=dat(x<<1)) return LowerBound(x<<1,k);
	else return LowerBound(x<<1|1,k);
}
inline ll Get(int x,int pos){
	if(l(x)==r(x)) return dat(x);
	int mid=l(x)+r(x)>>1;
	PushDown(x);
	if(pos<=mid) return Get(x<<1,pos);
	else return Get(x<<1|1,pos);
}
inline void Set(int x,int pos,ll k){
	if(l(x)==r(x)) return dat(x)=k,void();
	int mid=l(x)+r(x)>>1;
	PushDown(x);
	if(pos<=mid) Set(x<<1,pos,k);
	else Set(x<<1|1,pos,k);
	PushUp(x);
}

int a[N],ql[N],qr[N],ans[N],n,q,d;
vector<int> qry[N];

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);

	cin>>n>>q>>d;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=q;i++) cin>>ql[i]>>qr[i],qry[qr[i]].push_back(i);

	Build(1,1,n);
	Cover(1,1,n,inf);
	for(int i=1;i<=n;i++){
		Set(1,i,d);
		int lp=LowerBound(1,a[i]);
		int rp=LowerBound(1,a[i]*2);
		if(rp<=n) AddVal(1,rp,n,-a[i]);
		if(lp<rp) Cover(1,lp,rp-1,a[i]-1);
		for(int j:qry[i]) ans[j]=Get(1,ql[j]);
	}

	for(int i=1;i<=q;i++) cout<<ans[i]<<endl;

	return 0;
}

Stage 2: Grand Prix of Paris

7 题,GKBFHAI,光荣成为队伍战犯。

A. Apple Tree

首先题目条件转化之后变成树高最多是 \(\sqrt{2n}\),考虑枚举中心节点 \(v\),令 \(c_{v,d}\) 为到 \(v\) 距离恰好为 \(d\) 的点的个数,那么最终答案即为 \(\displaystyle \sum_{v\in V} \sum_{d=1}^{\lfloor\sqrt{2n}\rfloor} \binom{c_{v,d}}{k-1}\)

然后直接换根求 \(c_{v,d}\) 即可,注意到 \(\displaystyle \sum_{v\in V} siz_v\)\(O(n\sqrt n)\) 级别的,所以每个点暴力枚举子树内节点是可以接受的。

时间复杂度 \(O(n\sqrt n)\)

#include<bits/stdc++.h>

using namespace std;

using ll=long long;
const int N=5e5+9;
const int B=1e3+9;
const int mod=998244353;

inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int QPow(int x,int y){
	int res=1;
	while(y){
		if(y&1) MulAs(res,x);
		MulAs(x,x);
		y>>=1;
	}
	return res;
}
#define Inv(x) QPow(x,mod-2)

int fac[N],ifac[N];
inline void Init(int lim){
	fac[0]=1;
	for(int i=1;i<=lim;i++) fac[i]=Mul(fac[i-1],i);
	ifac[lim]=Inv(fac[lim]);
	for(int i=lim-1;~i;i--) ifac[i]=Mul(ifac[i+1],i+1);
}
inline int C(int n,int m){
	if(m<0||m>n) return 0;
	else return Mul(fac[n],Mul(ifac[m],ifac[n-m]));
}

vector<int> e[N];

int dep[N],fa[N],dfn[N],idfn[N],siz[N],n,k,lim,dcnt;
inline void GetDep(int x){
	dfn[x]=++dcnt;
	idfn[dcnt]=x;
	siz[x]=1;
	for(int y:e[x]){
		dep[y]=dep[x]+1;
		GetDep(y);
		siz[x]+=siz[y];
	}
}

int cnt[B<<1],ans;
inline void Work(int x){
	if(fa[x]){
		for(int i=lim;i;i--) cnt[i]=cnt[i-1];
		cnt[0]=0;
		for(int i=dfn[x];i<dfn[x]+siz[x];i++){
			int y=idfn[i];
			cnt[dep[y]-dep[x]+2]--;
			cnt[dep[y]-dep[x]]++;
		}
	}
	for(int i=1;i<=lim;i++) AddAs(ans,C(cnt[i],k-1));
	int rec[B<<1];
	memcpy(rec,cnt,sizeof cnt);
	for(int y:e[x]){
		Work(y);
		memcpy(cnt,rec,sizeof cnt);
	}
}

inline void Solve(){
	cin>>n>>k;
	for(int i=2;i<=n;i++){
		cin>>fa[i];
		e[fa[i]].push_back(i);
	}

	Init(n);
	GetDep(1);
	for(int i=1;i<=n;i++) cnt[dep[i]]++,lim=max(lim,dep[i]);
	lim<<=1;
	Work(1);
	if(k==1) ans=n;

	cout<<ans<<endl;

	for(int i=0;i<=lim;i++) cnt[i]=0;
	for(int i=1;i<=n;i++){
		dep[i]=fa[i]=dfn[i]=idfn[i]=siz[i]=0;
		e[i].clear();
	}
	dcnt=ans=lim=0;
}

signed main(){
	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

B. Balatro

棒棒糖(/bangbangt)题。

直接分别对 \(a_i \leq 100\)\(b_i\leq 100\) 的数做背包,两边拼起来就可以了,时间复杂度 \(O(nk^2C+k^3)\),其中 \(C = 100\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=1e5+9;
const int T=5e2+9;
const int K=6;
const ll inf=1e18;

ll f[K][T],g[K][T];
int a[N],b[N],n,k;

inline void Solve(){
	cin>>n>>k;
	for(int i=1;i<=n;i++) cin>>a[i]>>b[i];

	int lim=k*100;
	for(int i=0;i<=k;i++) for(int j=0;j<=lim;j++) f[i][j]=g[i][j]=-inf;
	f[0][0]=g[0][0]=0;
	for(int i=1;i<=n;i++){
		if(a[i]<=100){
			for(int j=k-1;~j;j--){
				for(int p=0;p+a[i]<=lim;p++){
					f[j+1][p+a[i]]=max(f[j+1][p+a[i]],f[j][p]+b[i]);
				}
			}
		}else{
			for(int j=k-1;~j;j--){
				for(int p=0;p+b[i]<=lim;p++){
					g[j+1][p+b[i]]=max(g[j+1][p+b[i]],g[j][p]+a[i]);
				}
			}
		}
	}

	ll ans=0;
	for(int i=0;i<=k;i++){
		vector<int> s,t;
		for(int p=0;p<=lim;p++) if(f[i][p]>=0) s.push_back(p);
		for(int q=0;q<=lim;q++) if(g[k-i][q]>=0) t.push_back(q);
		for(int p:s){
			for(int q:t){
				ans=max(ans,(f[i][p]+q)*(g[k-i][q]+p));
			}
		}
	}

	cout<<ans<<endl;

	for(int i=0;i<=k;i++) for(int j=0;j<=lim;j++) f[i][j]=g[i][j]=0;
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);

	int T;
	cin>>T;
	while(T--) Solve();
	
	return 0;
}

D. Digit Division

乱搞过了。

首先将 \(k\) 的倍数进行字符串拼接一定也是 \(k\) 的倍数,那么考虑用 \(k\) 的倍数拼出一个数位和为 \(k\) 的数。

注意到 \(n\) 的数位和最多是 \(9\lg n\),而且 \(ik\) 的数位和分布的较为随机,因此猜想大概存在用两个数凑出来方案,因此预处理出 \(k\)\(1\)\(2k\) 倍,离散化之后两两 exgcd 即可。

然后就过了。

#include<bits/stdc++.h>

using namespace std;

using ll=long long;
const int N=2e5+9;

inline int Digit(ll k){
	int sum=0;
	while(k) sum+=k%10,k/=10;
	return sum;
}

inline void ExGCD(ll a,ll b,ll &x,ll &y){
	if(!b) return x=1,y=0,void();
	ExGCD(b,a%b,x,y);
	ll z=x;
	x=y;
	y=z-(a/b)*y;
}

inline void Solve(){
	ll k;
	cin>>k;

	map<int,ll> mp;
	for(int i=1;i<=2*k;i++){
		ll tmp=k*i,c=Digit(tmp);
		if(!mp[c]) mp[c]=tmp;
	}

	for(auto p:mp){
		for(auto q:mp){
			ll a=p.first,b=q.first,x=0,y=0,g=__gcd(a,b);
			if(k%g) continue ;
			ExGCD(a,b,x,y);
			x*=k/g,y*=k/g;
			ll ta=b/g,tb=a/g;
			if(x<0){
				ll c=(abs(x)+ta-1)/ta;
				x+=ta*c,y-=tb*c;
			}
			if(x>0){
				ll c=x/ta;
				x-=ta*c,y+=tb*c;
			}
			if(y<0) continue ;
			ll s=p.second,t=q.second;
			for(int i=1;i<=x;i++) cout<<s;
			for(int i=1;i<=y;i++) cout<<t;
			cout<<endl;
			return ;
		}
	}

	assert(0);
}

signed main(){
	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

F. Framboise 2

枚举第 \(1/2\) 行最左/右边是否向左/右连了,剩下对于每一列:

  • 上下都有的,贡献是 \(4\)
  • 只有一行有的,上下连不和已有的水平的交叉的,贡献是 \(4\)
  • 只有一行有的,上下连会和已有的水平的交叉的,贡献是 \(2\)

乘在一起算贡献积即可,时间复杂度 \(O(n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=2e6+9;
const int mod=998244353;

inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int QPow(int x,int y){
	int res=1;
	while(y){
		if(y&1) MulAs(res,x);
		MulAs(x,x);
		y>>=1;
	}
	return res;
}
#define Inv(x) QPow(x,mod-2)

int x[N],y[N],cnt[N],r[N],o[N],n,k;

inline void Solve(){
	cin>>n>>k;
	for(int i=1;i<=k;i++) cin>>x[i]>>y[i],x[i]--;

	vector<int> yval(y+1,y+k+1);
	yval.push_back(-1);
	sort(yval.begin(),yval.end());
	yval.erase(unique(yval.begin(),yval.end()),yval.end());
	for(int i=1;i<=k;i++) y[i]=lower_bound(yval.begin(),yval.end(),y[i])-yval.begin();

	for(int i=1;i<=k;i++) cnt[y[i]]++;

	int l[2]={k+1,k+1},r[2]={0,0};
	for(int i=1;i<=k;i++){
		o[y[i]]|=x[i];
		l[x[i]]=min(l[x[i]],y[i]);
		r[x[i]]=max(r[x[i]],y[i]);
	}

	int ans=0;
	int t[2]={0,0};
	for(t[0]=0;t[0]<4;t[0]++){
		if((t[0]&1)&&l[0]>k) continue ;
		if((t[0]&2)&&r[0]<1) continue ;
		for(t[1]=0;t[1]<4;t[1]++){
			if((t[1]&1)&&l[1]>k) continue ;
			if((t[1]&2)&&r[1]<1) continue ;
			int res=1;
			for(int i=1;i<=k;i++){
				if(cnt[i]==2) MulAs(res,4);
				else if(cnt[i]==1){
					if((t[o[i]^1]&1)&&i<l[o[i]^1]) MulAs(res,2);
					else if((t[o[i]^1]&2)&&i>r[o[i]^1]) MulAs(res,2);
					else MulAs(res,4);
				}
			}
			AddAs(ans,res);
		}
	}

	cout<<ans<<endl;

	for(int i=1;i<=k;i++) cnt[i]=o[i]=0;
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);

	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

J. JamBrains

手玩一些数据发现倒闭的一定是所有行的一个后缀,判断依据从该行的最左端向上跳 \(u\) 行跨过的格子是不是超过 \(r\) 个,这个拿区间加区间最小值线段树维护即可,时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=1e5+9;

struct Node{
	int l,r;
	ll dat,tag;
}tr[N<<2];
#define l(x) tr[x].l
#define r(x) tr[x].r
#define dat(x) tr[x].dat
#define tag(x) tr[x].tag

inline void PushUp(int x){dat(x)=min(dat(x<<1),dat(x<<1|1));}
inline void Push(int x,ll k){dat(x)+=k,tag(x)+=k;}
inline void PushDown(int x){
	if(!tag(x)) return ;
	Push(x<<1,tag(x)),Push(x<<1|1,tag(x));
	tag(x)=0;
}
inline void Build(int x,int l,int r){
	l(x)=l,r(x)=r;
	if(l(x)==r(x)) return ;
	int mid=l(x)+r(x)>>1;
	Build(x<<1,l,mid),Build(x<<1|1,mid+1,r);
	PushUp(x);
}
inline void Modify(int x,int l,int r,ll k){
	if(l<=l(x)&&r(x)<=r) return Push(x,k);
	int mid=l(x)+r(x)>>1;
	PushDown(x);
	if(l<=mid) Modify(x<<1,l,r,k);
	if(r>mid) Modify(x<<1|1,l,r,k);
	PushUp(x);
}
inline int Find(int x){
	if(l(x)==r(x)) return l(x)+(dat(x)>0);
	PushDown(x);
	if(dat(x<<1)<=0) return Find(x<<1);
	else return Find(x<<1|1);
}

int n,q,len;
ll a[N],t[N],u,r;
inline void Add(int x,ll k){while(x<=n) t[x]+=k,x+=x&-x;}
inline ll Ask(int x){ll sum=0;while(x) sum+=t[x],x&=x-1;return sum;}
inline ll Ask(int l,int r){return Ask(r)-Ask(l-1);}
inline ll Query(){return Ask(min(n,Find(1)+len-1));}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);

	cin>>n>>u>>r;
	for(int i=1;i<=n;i++) cin>>a[i];
	
	len=min(u,ll(n));
	Build(1,1,n-len+1);
	for(int i=1;i<=n;i++){
		int l=max(2,i-len+1),r=min(i,n-len+1);
		Modify(1,l,r,a[i]);
		Add(i,a[i]);
	}
	Modify(1,1,n,-r);
	Modify(1,1,1,r+1);

	cout<<Query()<<endl;

	cin>>q;
	while(q--){
		int i;ll k;
		cin>>i>>k;
		int l=max(2,i-len+1),r=min(i,n-len+1);
		Modify(1,l,r,k-a[i]);
		Add(i,k-a[i]);
		a[i]=k;
		cout<<Query()<<endl;
	}

	return 0;
}

Stage 3: Polar Grand Prix

5 题,CDGIK,第一次没有被【数据删除】击败。

但是我怎么胡了 3 个就写了一个啊。

B. Christmas Tree

这题没过真的是我全责。

\(f_{u,i,j}\) 表示当前考虑完 \(u\) 子树内的贡献,\(u\) 向内可以到达 \(i\) 个点,并钦定 \(u\) 向内向外一共可以到达 \(j\) 个点。那么对于一条 \(u\leftrightarrow v\) 的边的定向可以分为以下两种转移:

  • \(u\rightarrow v\):那么 \(v\) 能到达的点 \(u\) 也能到达,所以有 \(f'_{u,i+p,j}\leftarrow f_{u,i,j}+f_{v,p,p}\)
  • \(u\leftarrow v\):那么 \(u\) 能到达的点 \(v\) 也能到达,所以有 \(f'_{u,i,j}\leftarrow f_{u,i,j}+f_{v,p,j+p}\)

最后还有 \(f'_{u,i,j}\leftarrow f_{u,i,j}+ja_u\),答案是 \(\displaystyle \min_{p=1}^n f_{root,p,p}\)

复杂度是树上背包的复杂度再多乘一个 \(n\),即 \(O(n^3)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=5e2+9;
const ll inf=1e18;

vector<int> e[N];
ll f[N][N][N],tmp[N][N],a[N];
int fa[N],siz[N],n;
inline void DFS(int x){
	siz[x]=1;
	for(int i=1;i<=n;i++) f[x][1][i]=i*a[x];
	for(int y:e[x]){
		if(y==fa[x]) continue ;
		fa[y]=x;
		DFS(y);
		for(int i=1;i<=siz[x]+siz[y];i++){
			for(int j=1;j<=n;j++) tmp[i][j]=inf;
		}
		for(int i=1;i<=siz[x];i++){
			for(int j=1;j<=siz[y];j++){
				for(int k=1;k<=n;k++){
					if(j+k<=n) tmp[i][k]=min(tmp[i][k],f[x][i][k]+f[y][j][j+k]);
					tmp[i+j][k]=min(tmp[i+j][k],f[x][i][k]+f[y][j][j]);
				}
			}
		}
		for(int i=1;i<=siz[x]+siz[y];i++){
			for(int j=1;j<=n;j++) f[x][i][j]=tmp[i][j];
		}
		siz[x]+=siz[y];
	}
}

inline void Solve(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	
	DFS(1);
	
	ll ans=inf;
	for(int i=1;i<=n;i++) ans=min(ans,f[1][i][i]);
	
	cout<<ans<<endl;
	
	for(int i=1;i<=n;i++) e[i].clear();
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);

	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

C. Roman Numerals

按照优先级先建出笛卡尔树,那么一个点的左儿子对它的贡献系数是 \(-1\),右儿子是 \(1\),查询的是区间笛卡尔树的 DP 值。

直接上离线的区间笛卡尔树即可。

#include<bits/stdc++.h>
 
using namespace std;
 
#define endl '\n'
using ll=long long;
const int N=3e5+9;
const int lgN=20;

map<string,int> id;
string s[N],t[N];
int prio[N],val[N],a[N],v[N],pos[N],ql[N],qr[N],n,m,q;
ll ans[N],cur[N];
vector<int> lq[N],rq[N];
 
int mxp[N][lgN],lg[N];
inline int Cmp(int i,int j){
	if(a[i]^a[j]) return a[i]<a[j]?j:i;
	else return i<j?i:j;
}
inline void Init(){
	lg[0]=-1;
	for(int i=1;i<=n;i++) lg[i]=lg[i>>1]+1;
	for(int i=1;i<=n;i++) mxp[i][0]=i;
	for(int k=1;k<=lg[n];k++){
		for(int i=1;i<=n-(1<<k)+1;i++) mxp[i][k]=Cmp(mxp[i][k-1],mxp[i+(1<<k-1)][k-1]);
	}
}
inline int MaxPos(int l,int r){
	int k=lg[r-l+1];
	return Cmp(mxp[l][k],mxp[r-(1<<k)+1][k]);
}

ll tr[N];
inline void Add(int x,ll k){while(x<=n) tr[x]+=k,x+=x&-x;}
inline ll Ask(int x){ll sum=0;while(x) sum+=tr[x],x&=x-1;return sum;}
inline ll Ask(int l,int r){return Ask(r)-Ask(l-1);}
inline ll F(int x){return x&1?-1:1;}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
 
	cin>>m>>n>>q;
	for(int i=1;i<=m;i++) cin>>prio[i]>>val[i]>>s[i];
	for(int i=1;i<=n;i++) cin>>t[i];
	for(int i=1;i<=q;i++) cin>>ql[i]>>qr[i];

	for(int i=1;i<=m;i++) id[s[i]]=i;
	for(int i=1;i<=n;i++) a[i]=prio[id[t[i]]],v[i]=val[id[t[i]]];
 
	Init();
	for(int i=1;i<=q;i++){
		if(ql[i]>qr[i]) continue ;
		pos[i]=MaxPos(ql[i],qr[i]);
		ans[i]=v[pos[i]];
		lq[ql[i]].push_back(i);
		rq[qr[i]].push_back(i);
	}
	
	vector<int> stk;
	for(int i=1;i<=n;i++){
		int lst=0;
		while(stk.size()&&Cmp(stk.back(),i)==i){
			Add(stk.size(),-cur[stk.back()]);
			cur[stk.back()]+=cur[lst];
			lst=stk.back();
			stk.pop_back();
		}
		cur[i]=v[i]-cur[lst];
		stk.push_back(i);
		Add(stk.size(),cur[i]);
		for(int j:rq[i]){
			int p=lower_bound(stk.begin(),stk.end(),pos[j])-stk.begin()+1;
			ans[j]+=Ask(p+1,n);
		}
	}

	stk.clear();
	for(int i=1;i<=n;i++) cur[i]=0;
	for(int i=1;i<=n;i++) tr[i]=0;

	for(int i=n;i>=1;i--){
		int lst=0;
		while(stk.size()&&Cmp(stk.back(),i)==i){
			Add(stk.size(),-(cur[stk.back()]*F(stk.size())));
			cur[stk.back()]-=cur[lst];
			lst=stk.back();
			stk.pop_back();
		}
		cur[i]=v[i]+cur[lst];
		stk.push_back(i);
		Add(stk.size(),cur[i]*F(stk.size()));
		for(int j:lq[i]){
			int p=lower_bound(stk.begin(),stk.end(),pos[j],greater<int>())-stk.begin()+1;
			ans[j]-=Ask(p+1,n)*F(p+1);
		}
	}

	for(int i=1;i<=q;i++) cout<<ans[i]<<endl;

	return 0;
}

D. Disjoint Set Splitting

考虑到答案最终一定是前面一串 1 后面都是 0,因此可以把强制在线变成离线,二分出变化的转折点即可,需要卡卡常。

或者从后往前加边好像也是可以的。

代码是队友 @DrAlfred 写的。

G. Far Away

首先如果两个点不在一个连通块里答案就是 Yes,如果都在一个大小不超过 \(2\times 10^4\) 的连通块里答案就是 No。

剩下的点随机 roll 200 个分别跑 bfs 看能不能作为中转点即可。

代码是队友 @wtcqwq 写的。

J. One Permutation

首先,\(a_k\) 是上凸的,即 \(2a_k\geq a_{k-1}+a_{k+1}\)

证明:

考虑将原问题转化成排列 \(p\)\(1,2,3,\ldots,n,1,2,3,\ldots,n,\ldots,1,2,3,\ldots,n\)(共 \(k\) 次)的 LCS,显然该问题的答案就是 \(a_k\)

那么,考虑建出经典 LCS DP 算法的自动机,即一个大小为 \(nk\times n\) 的网格图,边权为 \(0\)\(1\),原问题的答案在这个图上体现为从左上到右下的最长路。

\(n(k-1)\times n\) 的网格图和 \(n(k+1)\times n\) 的网格图重叠在一起,使他们共用一个中心,那么这两条路径必然相交。通过重新划分这两条路径,我们得到了两条 \(nk\times n\) 的网格图上的路径,而他们的长度显然不大于 \(a_k\)

因此,\(a_{k-1}+a_{k+1}\leq 2a_k\)

然后我们对每种斜率 \(\lambda\) 考察其作用域。

  • 对于 \(\lambda \leq \sqrt n\),我们可以通过 wqs 二分的套路找出凸壳上斜率大于等于 \(\lambda\) 的最右侧的点。
  • 对于 \(\lambda \gt \sqrt n\),我们知道,最多只有 \(O(n\lambda^{-1})\) 个整点的斜率是大于等于 \(\lambda\) 的,换言之,这个情况下的斜率只会出现在最左侧的 \(\sqrt n\) 个点上,二分找出他们即可。

时间复杂度 \(O(n\sqrt n\log n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=1e5+9;

int p[N],n;

array<ll,2> tr[N];
inline void Add(int x,array<ll,2> k){while(x<=n) tr[x]=max(tr[x],k),x+=x&-x;}
inline array<ll,2> Ask(int x){array<ll,2> res={0,0};while(x) res=max(res,tr[x]),x&=x-1;return res;}
inline void Clear(){for(int i=1;i<=n;i++) tr[i]={0,0};}

inline int GetP(int k){
	for(int i=1;i<=n;i++){
		array<ll,2> s=Ask(p[i]),t=Ask(n);
		Add(p[i],{s[0]+1,s[1]});
		Add(p[i],{t[0]+1-k,t[1]+1});
	}
	array<ll,2> s=Ask(n);
	Clear();
	return s[1]+1;
}

int k[N],f[N];
inline void Work(int l,int r){
	if(l>r) return ;
	if(k[l-1]==k[r+1]){
		for(int i=l;i<=r;i++) k[i]=k[l-1];
		return ;
	}
	int mid=l+r>>1;
	k[mid]=GetP(mid);
	Work(l,mid-1),Work(mid+1,r);
}

inline void Solve(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>p[i];

	k[0]=n+1,k[n+1]=0;
	Work(1,n);
	for(int i=0;i<n;i++){
		for(int j=k[i+1];j<k[i];j++) f[j]=i;
	}
	f[n]=n;
	for(int i=n-1;i>=1;i--) f[i]=f[i+1]-f[i];

	for(int i=1;i<=n;i++) cout<<f[i]<<' ';cout<<endl;
	for(int i=0;i<=n+1;i++) f[i]=k[i]=0;
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);

	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

Stage 4: Grand Prix of Chengdu

呃呃,8 题,JGABLDKC。

C. Crossing River

首先,如果一个人的过河时间不构成瓶颈的话,是可以适当地把他的过河时间尽量向后延的。换句话说,在不影响最终答案的情况下,存在一种方式使得在两边人数充足的时候船是来回划的。因此直接从后向前贪,枚举最后船在哪侧,剩下的就是按题意模拟。

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=2e5+9;

int n,m,k;
array<int,2> a[N],b[N];

inline ll Calc(int op){
	ll t=0;
	int i=1,j=1,cnt=0;
	while(i<=n||j<=m){
		cnt++;
		if(op==0){
			if(i<=n){
				t=max(t,1ll*k*cnt+a[i][0]);
				i++;
			}
		}else if(op==1){
			if(j<=m){
				t=max(t,1ll*k*cnt+b[j][0]);
				j++;
			}
		}
		op^=1;
	}
	return t;
}
inline vector<array<ll,3>> Work(ll t,int op){
	vector<array<ll,3>> ans;
	int i=1,j=1,cnt=0;
	while(i<=n||j<=m){
		cnt++;
		if(op==0){
			if(i<=n){
				ans.push_back({t-1ll*k*cnt,op,a[i][1]});
				i++;
			}
		}else if(op==1){
			if(j<=m){
				ans.push_back({t-1ll*k*cnt,op,b[j][1]});
				j++;
			}
		}
		op^=1;
	}
	reverse(ans.begin(),ans.end());
	return ans;
}

inline void Solve(){
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++) cin>>a[i][0],a[i][1]=i;
	for(int i=1;i<=m;i++) cin>>b[i][0],b[i][1]=i;

	sort(a+1,a+n+1,greater<array<int,2>>());
	sort(b+1,b+m+1,greater<array<int,2>>());

	if(Calc(0)<Calc(1)){
		ll t=Calc(0);
		cout<<t<<endl;
		auto ans=Work(t,0);
		for(auto p:ans) cout<<p[0]<<' '<<p[1]<<' '<<p[2]<<endl;
	}else{
		ll t=Calc(1);
		cout<<t<<endl;
		auto ans=Work(t,1);
		for(auto p:ans) cout<<p[0]<<' '<<p[1]<<' '<<p[2]<<endl;
	}
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);

	Solve();

	return 0;
}

G. GCD of Subsets

签到题做半个小时吃两罚,是人?

首先肯定先把不是 \(k\) 的全变成 \(k\),再把 \(k\) 的倍数逐一变成 \(k\),剩下的相邻的 \(k\) 的倍数相邻的两两配对,原因显然。

#include<bits/stdc++.h>

using namespace std;

using ll=long long;

signed main(){
	int T;
	cin>>T;
	while(T--){
		ll n,m,k;
		cin>>n>>k>>m;
		m=min(m,n-1);

		ll lft=n-n/k;
		if(m<=lft) cout<<m+1+((n/k-1)/2)<<endl;
		else cout<<m+1+((n-m-1)/2)<<endl;
	}

	return 0;
}

K. K-Coverage

根据题意,至多可以移动一个段,那么考虑枚举这个段,并将不移动看成是该段移动回原来的位置了。

为了方便,下文中令 \(c_i\) 表示某一位置被覆盖的次数,原位置和新位置分别为 \(S=[l,l+L),T=[l',l'+L)\)\(\displaystyle cnt_k(S)=\sum_{x\in S}[c_x=k]\)

那么:

  • 若移动到的新位置与原来位置无交:那么增长量即为 \(cnt_{k+1}(S)-cnt_k(S)+cnt_{k-1}(T)-cnt_k( T)\)。因此,只要找到和 \(S\) 无交的 \(cnt_{k-1}(T)-cnt_k(T)\) 最大的 \(T\) 即可。
  • 若移动到的新位置与原来位置于左侧有交:那么增长量为 \(cnt_{k+1}(S')-cnt_k(S')+cnt_{k-1}(T')-cnt_k( T')\),其中 \(S'=S/(S\cup T)=[l'+L,l+L),T'=T/(S\cup T)=[l',l)\)。令 \(w_i=[c_i=k+1]-[c_i=k]+[c_{i-L}=k-1]-[c_{i-L}=k]\),不难发现,增长量其实是 \(\displaystyle\sum_{i=l'+L}^{l+L-1} w_i\)。也就是说,最优的的是 \(w_i\)\(l+L-1\) 为右端点且长度不超过 \(L\) 的最大后缀和,这个可以简单维护。
  • 新位置与原来位置于右侧有交和上一种情况是类似的。
#include<bits/stdc++.h>

using namespace std;

const int N=1e6+9;
const int lgN=2e1+1;

int a[N],c[N],pref[N],suff[N],posf[N],posg[N],wrg[N],n,len,k;

struct RMQ{
	int mn[N][lgN],lg[N],op;
	inline void Init(int n,int *a,int o){
		op=o;
		for(int i=2;i<=n;i++) lg[i]=lg[i>>1]+1;
		for(int i=0;i<=n;i++) mn[i][0]=a[i]*op;
		for(int k=1;k<=lg[n];k++){
			for(int i=0;i<=n-(1<<k)+1;i++){
				mn[i][k]=min(mn[i][k-1],mn[i+(1<<k-1)][k-1]);
			}
		}
	}
	inline int R(int l,int r){
		int k=lg[r-l+1];
		return min(mn[l][k],mn[r-(1<<k)+1][k])*op;
	}
	inline void Clear(int n){
		for(int k=0;k<=lg[n];k++){
			for(int i=0;i<=n;i++) mn[i][k]=0;
		}
		for(int i=0;i<=n;i++) lg[i]=0;
		op=0;
	}
}pmn,smx,wmx;

inline void Solve(){
	cin>>n>>len>>k;
	for(int i=1;i<=n;i++) cin>>a[i],a[i]++;

	int lim=4*n,ans=0,res=0;
	sort(a+1,a+n+1);
	for(int i=1;i<=lim;i++){
		c[i]=upper_bound(a+1,a+n+1,i)-lower_bound(a+1,a+n+1,i-len+1);
		if(c[i]==k) ans++;
	}
	for(int i=1;i<=lim;i++){
		if(i-len>=1){
			if(c[i]==k+1) pref[i]++;
			if(c[i]==k) pref[i]--;
			if(c[i-len]==k-1) pref[i]++;
			if(c[i-len]==k) pref[i]--;
		}
		if(i+len<=lim){
			if(c[i]==k+1) suff[i]++;
			if(c[i]==k) suff[i]--;
			if(c[i+len]==k-1) suff[i]++;
			if(c[i+len]==k) suff[i]--;
		}
		if(c[i]==k) posf[i]--,posg[i]--;
		if(c[i]==k+1) posf[i]++;
		if(c[i]==k-1) posg[i]++;
		pref[i]+=pref[i-1];
		suff[i]+=suff[i-1];
		posf[i]+=posf[i-1];
		posg[i]+=posg[i-1];
		if(i>=len) wrg[i]=posg[i]-posg[i-len];
	}
	wmx.Init(lim,wrg,-1);
	pmn.Init(lim,pref,1);
	smx.Init(lim,suff,-1);

	for(int i=1;i<=n;i++){
		int x=a[i];
		int spf=posf[x+len-1]-posf[x-1];
		if(x-len>=1) res=max(res,wmx.R(1,x-1)+spf);
		if(x+len+len-1<=lim) res=max(res,wmx.R(x+len+len-1,lim)+spf);
		res=max(res,pref[x+len-1]-pmn.R(x-1,x+len-1));
		res=max(res,smx.R(x-1,min(x+len-1,lim))-suff[x-1]);
	}

	cout<<ans+res<<endl;

	wmx.Clear(lim);
	pmn.Clear(lim);
	smx.Clear(lim);
	for(int i=0;i<=lim;i++) a[i]=c[i]=pref[i]=suff[i]=posf[i]=posg[i]=wrg[i]=0;
}

signed main(){
	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

Extra Stage 1: Xi'an

嘟嘟嘟,GJLMIFBK,遗憾 8 题了,死因是没写明白领域加。

B. Beautiful Dangos

这个题是 @wtcqwq 做的前半部分(求区间),我做的后半部分(构造)。

如果可以通过调整一个区间使得答案合法,那么调整其超集也可以,因此答案具有可二分性。

判断 \([l,r]\) 是否合法,若存在 \(\tt CWP\) 中的一种颜色使得:

  • \([l,r]\) 中的该颜色有 \(+1\) 的贡献值,非该颜色有 \(-1\) 的贡献值。
  • \(l-1,r+1\) 位置上的该颜色有 \(+1\) 的贡献值,非该颜色有 \(0\) 的贡献值。
  • \([l-1,r+1]\) 的总贡献大于 \(1\)

那么 \([l,r]\) 不合法,反之则合法。

考虑如下构造:

定义一次对字符串 \(s\) 插入 \(t\) 次字符 \(\tt A\) 的操作为:

  • 如果 \(s\) 中存在形如 \(\tt BB\) 的子串且 \(t\gt 0\),那么将其变成 \(\tt BAB\),同时 \(t\leftarrow t-1\)。重复执行直到 \(s\) 中不存在形如 \(\tt BB\) 的子串或 \(t\leq 0\)
  • 如果 \(s\) 中存在形如 \(\tt BC\) 的子串且 \(t\gt 0\),那么将其变成 \(\tt BAC\),同时 \(t\leftarrow t-1\)。重复执行直到 \(s\) 中不存在形如 \(\tt BB\) 的子串或 \(t\leq 0\)
  • 将剩下的 \(\tt A\) 随意插在 \(s\) 当中,并尽量少产生 \(\tt AA\) 子串。

那么不难发现,对于串 \(\overline{s_{l-1}s_{r+1}}\) ,优先插入 \(s_{l-1}\)\(s_{r+1}\)\([l,r]\) 的出现次数次,最后插入剩下的字符并放回 \(s[l-1:r+1]\),在 \([l,r]\) 合法的情况下,得到的新串即为合法构造,证明略去。

#include<bits/stdc++.h>
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define dbg cerr<<"LINE "<<__LINE__<<":"
using namespace std;
const int N=2000009,inf=1e9,mod=998244353;
int n,cntc[N],cntw[N],cntp[N];
string str; 
int mn,mx;
int check(int len){
    for(int i=1;i+len-1<=n;i++){
        int j=i+len-1;
        if(i<=mn&&j>=mx) ;
        else continue;
        int cC=cntc[j]-cntc[i-1];
        int cW=cntw[j]-cntw[i-1];
        int cP=cntp[j]-cntp[i-1];
        int valc=cC-cW-cP,valw=cW-cC-cP,valp=cP-cC-cW;
        valc+=(i-1>=1&&str[i-1]=='C')+(j+1<=n&&str[j+1]=='C');
        valw+=(i-1>=1&&str[i-1]=='W')+(j+1<=n&&str[j+1]=='W');
        valp+=(i-1>=1&&str[i-1]=='P')+(j+1<=n&&str[j+1]=='P');
        if(valc<=1&&valw<=1&&valp<=1) return i;
    }
    return 0;
}
inline void Insert(string &s,int &t,char c){
	string res;
	if(t){
		res.push_back(s.front());
		for(int i=1;i<s.size();i++){
			if(s[i-1]==s[i]){
				if(t) res.push_back(c),t--;
			}
			res.push_back(s[i]);
		}
		s=res;
		res.clear();
	}
	if(t){
		res.push_back(s.front());
		for(int i=1;i<s.size();i++){
			if(s[i-1]!=c&&s[i]!=c){
				if(t) res.push_back(c),t--;
			}
			res.push_back(s[i]);
		}
		s=res;
		res.clear();
	}
	if(t){
		if(s.front()!=c){
			res.push_back(s.front());
			while(t) res.push_back(c),t--;
			for(int i=1;i<s.size();i++) res.push_back(s[i]);
			s=res;
		}else{
			for(int i=0;i+1<s.size();i++) res.push_back(s[i]);
			while(t) res.push_back(c),t--;
			res.push_back(s.back());
			s=res;
		}
	}
}
void sol(int l,int r){
	char c[3]={'C','W','P'};
	int t[3]={0,0,0};
	t[0]=cntc[r]-cntc[l-1];
	t[1]=cntw[r]-cntw[l-1];
	t[2]=cntp[r]-cntp[l-1];
	map<char,int> id;
	for(int i:{0,1,2}) id[c[i]]=i;
	string now;
	if(l==1) now.push_back('#');
	else now.push_back(str[l-1]);
	if(r==n) now.push_back('#');
	else now.push_back(str[r+1]);
	if(now.front()!='#') Insert(now,t[id[now.front()]],now.front());
	if(now.back()!='#') Insert(now,t[id[now.back()]],now.back());
	for(int i:{0,1,2}) Insert(now,t[i],c[i]);
	for(int i=l;i<=r;i++) str[i]=now[i-l+1];
}
void solve(){
    cin>>n;cin>>str;str=" "+str;
    for(int i=1;i<=n;i++){
        cntc[i]=cntc[i-1]+(str[i]=='C');
        cntw[i]=cntw[i-1]+(str[i]=='W');
        cntp[i]=cntp[i-1]+(str[i]=='P');
    }mn=n+1,mx=0;
    for(int i=2;i<=n;i++){
        if(str[i]==str[i-1]){
            mn=min(mn,i);
            mx=max(mx,i);
        }
    }
    mx--;
    if(mn==n+1&&mx==-1){
        cout<<"Beautiful\n";
        return;
    }
    int l=1,r=n;
    pair<int,int> ans={0,0};
    while(l<=r){
        int mid=(l+r)>>1;
        int g=check(mid);
        if(g){
            r=mid-1;
            ans=mp(g,g+mid-1);
        }
        else l=mid+1;
    }
    if(ans==mp(0,0)){
        cout<<"Impossible\n";
    }
    else{
		cout<<"Possible"<<endl;
        cout<<ans.first<<" "<<ans.second<<"\n";
        sol(ans.first,ans.second);
		cout<<str.substr(1)<<endl;
    }
}
int main(){
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    int _; cin>>_; while(_--)
    solve();
    return 0;
}

C. Catch the Monster

考虑对每个左端点判断能使区间合法的极大右端点。

通过巨量模拟,发现合法的树形态一定是毛毛虫,换句话说,删去所有叶子后剩的是若干条链,即新图不存在三度点。

因此,对于每个度数大于 \(1\) 的点领域加 \(1\),那么如果区间中没有权值大于 \(2\) 就是合法的。

领域加可以使用小 I 神秘 bfn 科技。

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
const int N=1e6+9;
const int inf=1e8;

struct Node{
	int l,r;
	int dat,tag;
}tr[N<<2];
inline void PushUp(int x){tr[x].dat=max(tr[x<<1].dat,tr[x<<1|1].dat)+tr[x].tag;}
inline void Push(int x,int k){tr[x].dat+=k,tr[x].tag+=k;}

inline void Build(int x,int l,int r){
	tr[x].l=l,tr[x].r=r;
	if(tr[x].l==tr[x].r) return ;
	int mid=tr[x].l+tr[x].r>>1;
	Build(x<<1,l,mid),Build(x<<1|1,mid+1,r);
	PushUp(x);
}
inline void Modify(int x,int l,int r,int k){
	if(l<=tr[x].l&&tr[x].r<=r) return Push(x,k);
	int mid=tr[x].l+tr[x].r>>1;
	if(l<=mid) Modify(x<<1,l,r,k);
	if(r>mid) Modify(x<<1|1,l,r,k);
	PushUp(x);
}

vector<int> e[N];
int pos[N],n,m,q;
int bfn[N],fa[N],st[N],ed[N],bcnt;
inline void BFS(int x){
	queue<int> q;
	q.push(x);
	while(q.size()){
		int x=q.front();
		q.pop();
		bfn[x]=++bcnt;
		st[x]=n,ed[x]=0;
		if(fa[x]){
			st[fa[x]]=min(st[fa[x]],bfn[x]);
			ed[fa[x]]=max(ed[fa[x]],bfn[x]);
		}
		for(int y:e[x]){
			if(bfn[y]) continue ;
			fa[y]=x;
			q.push(y);
		}
	}
}
inline void Near(int x,int k){
	if(ed[x]) Modify(1,st[x],ed[x],k);
	if(fa[x]) Modify(1,bfn[fa[x]],bfn[fa[x]],k);
}
int vis[N],d[N];
inline void Add(int u){
	vis[u]=1;
	Modify(1,bfn[u],bfn[u],inf);
	if(d[u]>1) Near(u,1);
	for(int v:e[u]){
		if(d[v]==1&&vis[v]) Near(v,1);
		d[v]++;
	}
}
inline void Del(int u){
	vis[u]=0;
	Modify(1,bfn[u],bfn[u],-inf);
	if(d[u]>1) Near(u,-1);
	for(int v:e[u]){
		d[v]--;
		if(d[v]==1&&vis[v]) Near(v,-1);
	}
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);

	cin>>n>>m>>q;
	for(int i=1,u,v;i<=m;i++){
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}

	for(int i=1;i<=n;i++) if(!bfn[i]) BFS(i);
	Build(1,1,n);
	Modify(1,1,n,-inf);
	for(int i=1,j=0;i<=n;Del(i),i++){
		while(j<n&&tr[1].dat<3) Add(++j);
		if(tr[1].dat<3) pos[i]=j;
		else pos[i]=j-1;
	}

	for(int i=1;i<=q;i++){
		int l,r;
		cin>>l>>r;
		if(pos[l]>=r) cout<<"Yes"<<endl;
		else cout<<"No"<<endl;
	}

	return 0;
}

F. Follow the Penguins

首先偶像关系是一个基环树森林,而大手子都只会向其初始移动方向一直走下去,直到遇到自己的偶像,因此树边是好处理的。

对于环,考虑二分其第一条删去的边,如果两个构成偶像关系的大手子相向而行且距离不大于二分值,那么要么两个相遇了,要么偶像遇到了偶像的偶像,所以至少存在一个见到偶像的大手子(由此也可以知道距离最短的那对就是最早相遇的那对)。然后断边为树,剩下就是一些小分讨。

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
#define int ll
using ll=long long;
const int N=5e5+9;

int a[N],to[N],in[N],vis[N],typ[N],t[N],n;
vector<int> son[N];

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);

	cin>>n;
	for(int i=1;i<=n;i++) cin>>to[i];
	for(int i=1;i<=n;i++) cin>>a[i];
	
	queue<int> q;
	for(int i=1;i<=n;i++) in[to[i]]++;
	for(int i=1;i<=n;i++) if(!in[i]) q.push(i);
	while(q.size()){
		int x=q.front();
		q.pop();
		if(!--in[to[x]]) q.push(to[x]);
	}
	
	for(int i=1;i<=n;i++) typ[i]=a[i]<a[to[i]]?1:-1;
	for(int u=1;u<=n;u++){
		if(!in[u]){
			son[to[u]].push_back(u);
		}
	}
	for(int u=1;u<=n;u++){
		if(!in[u]) continue ;
		if(vis[u]) continue ;
		int x=u;
		vector<int> v;
		while(!vis[x]){
			vis[x]=1;
			v.push_back(x);
			x=to[x];
		}
		
		int lt=0,rt=1e9+7;
		while(lt+1<rt){
			int mid=lt+rt>>1;
			
			bool flag=0;
			for(int i:v){
				if(typ[i]==typ[to[i]]) continue ;
				if(abs(a[i]-a[to[i]])<=mid) flag|=1;
			}

			if(flag) rt=mid;
			else lt=mid;
		}

		int p=-1,m=v.size();
		for(int i=0;i<v.size();i++){
			int x=v[i];
			if(typ[x]==typ[to[x]]) continue ;
			if(abs(a[x]-a[to[x]])==rt) t[x]=rt,p=i;
		}
		assert(~p);
		for(int i=(p-1+m)%m;i!=p;i=(i-1+m)%m){
			int x=v[i];
			if(t[x]) continue ;
			if(typ[x]==typ[to[x]]){
				t[x]=t[to[x]]+2*abs(a[x]-a[to[x]]);
			}else{
				t[x]=abs(a[x]-a[to[x]]);
				if(t[x]>t[to[x]]){
					t[x]=t[to[x]]+2*abs(a[x]-(a[to[x]]+typ[to[x]]*t[to[x]]));
				}
			}
		}
		for(int x:v){
			for(int y:son[x]) q.push(y);
		}
	}

	while(q.size()){
		int x=q.front();
		q.pop();
		
		if(typ[x]==typ[to[x]]){
			t[x]=t[to[x]]+2*abs(a[x]-a[to[x]]);
		}else{
			t[x]=abs(a[x]-a[to[x]]);
			if(t[x]>t[to[x]]){
				t[x]=t[to[x]]+2*abs(a[x]-(a[to[x]]+typ[to[x]]*t[to[x]]));
			}
		}
		
		for(int y:son[x]) q.push(y);
	}

	for(int i=1;i<=n;i++) cout<<t[i]<<' ';cout<<endl;

	return 0;
}

L. Let's Make a Convex!

一个集合 \(S\) 能拼成凸多边形的充要条件是 \(\displaystyle 2\max_{x\in S} x \lt \sum_{x\in S} x\),因此排序后枚举最大值,那么可以构成答案的最小值(左端点)是一个前缀,而对于相同的 \(k\),答案肯定是最大值越大越好,并且是一个区间,贪心地放置即可。

#include<bits/stdc++.h>

using namespace std;

using ll=long long;
const int N=2e5+9;

int a[N],n;
ll s[N],ans[N];

signed main(){
	int T;
	cin>>T;
	while(T--){
		cin>>n;
		for(int i=1;i<=n;i++) cin>>a[i];

		sort(a+1,a+n+1);
		for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i];

		for(int i=n,j=n;i>=1;i--){
			int k=lower_bound(s,s+n+1,s[i]-2*a[i])-s-1;
			if(!~k) continue ;
			for(j=min(j,i);j>=i-k;j--) ans[j]=s[i]-s[i-j];
		}

		for(int i=1;i<=n;i++) cout<<ans[i]<<' ';cout<<endl;

		for(int i=1;i<=n;i++) ans[i]=s[i]=a[i]=0;
	}

	return 0;
}

Stage 5: Grand Prix of Nanjing

嘟!最后半小时三人齐心合力做出 H!

CKFEMGIH。

B. What, More Kangaroos?

考虑到 \(ax+by+c>0,c>0\) 是一个背向 \((0,0)\) 点的半平面,于是考虑算出该半平面所控制的极角的范围,由于整点对于极角的分布是相对均匀的,因此覆盖次数最多的点 \((x,y)\) 的覆盖次数和覆盖次数最多的极角 \(\theta\) 一样多。直接断环为链,求取覆盖次数最多的点即可。

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ld=long double;
const int N=2e5+9;
const ld pi=acosl(-1);
const ld eps=1e-18;

int a[N],b[N],c[N],n;
ld tl[N],tr[N];

inline void Solve(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i]>>b[i]>>c[i];

	vector<ld> val;
	for(int i=1;i<=n;i++){
		if(!a[i]&&!b[i]) continue ;
		tr[i]=atan2l(a[i],-b[i]);
		if(tr[i]>=0) tl[i]=tr[i]-pi;
		else tl[i]=tr[i]+pi;
		tl[i]+=eps,tr[i]-=eps;
		val.push_back(tl[i]);
		val.push_back(tr[i]);
	}
	val.push_back(-pi),val.push_back(pi);

	sort(val.begin(),val.end());
	val.erase(unique(val.begin(),val.end()),val.end());

	vector<int> d(val.size()<<1,0);
	for(int i=1;i<=n;i++){
		if(!a[i]&&!b[i]) continue ;
		int l=lower_bound(val.begin(),val.end(),tl[i])-val.begin();
		int r=lower_bound(val.begin(),val.end(),tr[i])-val.begin();
		if(tl[i]<=tr[i]) d[l<<1|1]++,d[r<<1]--;
		else{
			d[l<<1|1]++,d.back()--;
			d.front()++,d[r<<1]--;
		}
	}
	partial_sum(d.begin(),d.end(),d.begin());

	cout<<*max_element(d.begin(),d.end())<<endl;
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

C. Distributing Candies

long long!?

奇数无解,偶数显然 \(\dfrac n2\)\(\dfrac n2\)

#include<bits/stdc++.h>

using namespace std;

using ll=long long;

signed main(){
	int T;
	cin>>T;
	while(T--){
		ll n;
		cin>>n;
		if(n&1) cout<<"No"<<endl;
		else cout<<"Yes"<<endl<<n/2<<' '<<n/2<<endl;
	}

	return 0;
}

E. Cyan White Tree

没过样例就交了,唐唐。

首先先拆绝对值,分讨是 \(\sum w\leq \sum c\) 还是 \(\sum w\gt\sum c\),贡献分别为 \(3w-c\)\(3c-w\)。上面两种情况分别是 \(w-c\) 的一个前缀和一个后缀,线段树合并+启发式合并对每个值在 \(\sum w-\sum c\) 处维护贡献即可。

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=4e5+9;
const int inf=1e9;

struct Data{
	int f,g;
	Data(int _f=0,int _g=0){f=_f,g=_g;}
	inline friend Data operator +(Data x,Data y){return Data(max(x.f,y.f),max(x.g,y.g));};
};
struct Node{
	int lc,rc;
	Data dat;
}tr[N<<6];

int cnt;
inline int Allc(){return tr[++cnt].dat=Data(-inf,-inf),cnt;}
inline void Clear(){for(int x=1;x<=cnt;x++) tr[x].lc=tr[x].rc=0,tr[x].dat=Data(0,0);cnt=0;}
inline void PushUp(int x){
	tr[x].dat=Data(-inf,-inf);
	if(tr[x].lc) tr[x].dat=tr[x].dat+tr[tr[x].lc].dat;
	if(tr[x].rc) tr[x].dat=tr[x].dat+tr[tr[x].rc].dat;
}
inline void Insert(int &x,int L,int R,int pos,Data k){
	if(!x) x=Allc();
	if(L==R) return tr[x].dat=tr[x].dat+k,void();
	int mid=L+R>>1;
	if(pos<=mid) Insert(tr[x].lc,L,mid,pos,k);
	else Insert(tr[x].rc,mid+1,R,pos,k);
	PushUp(x);
}
inline void Merge(int &x,int y,int L,int R){
	if(!x||!y) return x|=y,void();
	if(L==R){
		tr[x].dat=tr[x].dat+tr[y].dat;
		return ;
	}
	int mid=L+R>>1;
	Merge(tr[x].lc,tr[y].lc,L,mid);
	Merge(tr[x].rc,tr[y].rc,mid+1,R);
	PushUp(x);
}
inline Data Query(int x,int L,int R,int l,int r){
	if(!x||l>r) return Data(-inf,-inf);
	if(l<=L&&R<=r) return tr[x].dat;
	int mid=L+R>>1;
	if(r<=mid) return Query(tr[x].lc,L,mid,l,r);
	else if(l>mid) return Query(tr[x].rc,mid+1,R,l,r);
	else return Query(tr[x].lc,L,mid,l,r)+Query(tr[x].rc,mid+1,R,l,r);
}

char typ[N];
int fa[N],csu[N],wsu[N],root[N],ans[N],siz[N],n;
vector<int> e[N],node[N];
inline void DFS(int x){
	siz[x]=1;
	csu[x]=csu[fa[x]],wsu[x]=wsu[fa[x]];
	if(typ[x]=='0') csu[x]++;
	else wsu[x]++;
	Insert(root[x],-n,n,csu[x]-wsu[x],Data(3*wsu[x]-csu[x],3*csu[x]-wsu[x]));
	node[x].push_back(x);
	for(int y:e[x]){
		if(y==fa[x]) continue ;
		fa[y]=x;
		DFS(y);
		if(siz[x]<siz[y]){
			swap(root[x],root[y]);
			swap(node[x],node[y]);
			swap(siz[x],siz[y]);
		}
		for(int u:node[y]){
			int C=csu[u]-csu[x]-csu[fa[x]];
			int W=wsu[u]-wsu[x]-wsu[fa[x]];
			Data s1=Query(root[x],-n,n,W-C,n);
			ans[x]=max(ans[x],s1.f+3*W-C);
			Data s2=Query(root[x],-n,n,-n,W-C-1);
			ans[x]=max(ans[x],s2.g+3*C-W);
		}
		Merge(root[x],root[y],-n,n);
		node[x].insert(node[x].end(),node[y].begin(),node[y].end());
		node[y].clear();
		node[y].shrink_to_fit();
		siz[x]+=siz[y];
	}
}

inline void Solve(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>typ[i];
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}

	fill(ans+1,ans+n+1,-1);
	DFS(1);
	for(int i=1;i<=n;i++) cout<<ans[i]<<endl;

	for(int i=1;i<=n;i++){
		typ[i]=csu[i]=wsu[i]=root[i]=ans[i]=siz[i]=fa[i]=0;
		e[i].clear(),e[i].shrink_to_fit();
		node[i].clear(),node[i].shrink_to_fit();
	}
	Clear();
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

F. Bitwise And Path

考虑先使用道路重建的手法找出对每个 \(w\) 会成为其最小瓶颈生成树的边,按照时间轴顺序添加即可,查询直接从高往低位贪心即可。

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=1e3+9;
const int Q=1e6+9;
const int V=(1<<12)+9;

char op[Q];
int u[Q],v[Q],w[Q],n,q;
vector<int> e[V],g[V],o[Q];
struct DSU{
	int fa[N];
	inline void Init(int lim){iota(fa+1,fa+lim+1,1);}
	inline int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
	inline void Merge(int x,int y){fa[Find(y)]=Find(x);}
	inline bool Con(int x,int y){return Find(x)==Find(y);}
}d[V];

inline void Solve(){
	cin>>n>>q;
	for(int i=1;i<=q;i++){
		cin>>op[i]>>u[i]>>v[i];
		if(op[i]=='+') cin>>w[i],g[w[i]].push_back(i);
	}

	for(int sta=(1<<12)-1;~sta;sta--){
		for(int i=0;i<12;i++){
			if(sta>>i&1) continue ;
			int tta=sta|(1<<i);
			g[sta].insert(g[sta].begin(),e[tta].begin(),e[tta].end());
		}
		d[sta].Init(n);
		sort(g[sta].begin(),g[sta].end());
		for(int i:g[sta]){
			if(d[sta].Con(u[i],v[i])) continue ;
			d[sta].Merge(u[i],v[i]);
			o[i].push_back(sta);
			e[sta].push_back(i);
		}
		d[sta].Init(n);
	}

	ll sum=0;
	for(int i=1;i<=q;i++){
		if(op[i]=='+'){
			for(int j:o[i]) d[j].Merge(u[i],v[i]);
		}else{
			int ans=0;
			for(int j=11;~j;j--){
				if(d[ans|(1<<j)].Con(u[i],v[i])) ans|=1<<j;
			}
			if(!d[0].Con(u[i],v[i])) ans=-1;
			sum+=ans;
		}
	}
	cout<<sum<<endl;

	for(int i=0;i<(1<<12);i++) e[i].clear(),g[i].clear(),d[i].Init(n);
	for(int i=1;i<=q;i++) o[i].clear();
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

H. Pen Pineapple Apple Pen

\(\displaystyle f(l,r)=\sum_{i=0}^{\lfloor\frac{r-l+1}2\rfloor}[s[l:l+i]=s[r-i:r]]\)\(\displaystyle g(l,r)=\sum_{i=0}^{\min(l-1,n-r)}[s[l-i:l]=s[r:r+i]]\)

那么答案即为:\(\displaystyle \sum_{i=1}^n\sum_{j=i+1}^ng(i,j)\sum_{p=i+1}^j\sum_{q=p+1}^j(p-i-1)f(p,q)\)

如果我们知道了所有 \(f(l,r)\)\(g(l,r)\) 的取值,那么上式可以通过简单地拆贡献和二维数点计算。

  • \(f(l,r)\) 就是 [NOI 2014] 动物园。

  • \(g(l,r)\) 可以看成是 \(s[r:n]\)\(s[1:l]\) 做 KMP 算 border 树上祖先个数。

时间复杂度 \(O(n^2)\)

#include "alfred/all"
using namespace std;
const int N = 5010;
std::string S;
m998 s1[N][N], s2[N][N];
int n, f[N][N], g[N][N], num[N], pi[N];
inline void InitF(void) {
    for (int l = 1, _n; l <= n; l++) {
        std::string s = ' ' + S.substr(l);
        s.pop_back(), _n = s.size() - 1, num[1] = 1;
        for (int i = 1; i < _n; i++) {
            int j = pi[i];
            while (j && s[i + 1] != s[j + 1]) j = pi[j];
            pi[i + 1] = (s[i + 1] == s[j + 1] ? j + 1 : 0);
        }
        for (int i = 1; i <= _n; i++) {
            num[i] = num[pi[i]] + 1;
        }
        for (int i = 1, j = 0; i <= _n; i++) {
            while (j && s[i] != s[j + 1]) j = pi[j];
            if (s[i] == s[j + 1]) j++;
            while (2 * j > i) j = pi[j];
            f[l][l + i - 1] = num[j];
        }
    }
}
inline void InitG() {
    for (int j = 1; j <= n; j++) {
        vector<int> a(n + 1, 0), b(n + 1, 0), cnt(n + 1, 0);
        int ld = j - 1;
        a[ld + 1] = 0, cnt[ld + 1] = 1;
        for (int k = 2, p = 0; ld + k <= n; k++) {
            while (p && S[ld + p + 1] != S[ld + k]) p = a[ld + p];
            if (S[ld + p + 1] == S[ld + k]) p++;
            a[ld + k] = p, cnt[ld + k] = cnt[ld + a[ld + k]] + 1;
        }
        for (int k = 1, p = 0; k <= n; k++) {
            while (p && S[ld + p + 1] != S[k]) p = a[ld + p];
            if (S[ld + p + 1] == S[k]) p++;
            b[k] = p;
        }
        for (int i = 1; i < j; i++) g[i][j] = cnt[ld + b[i]];
    }
}
inline void optimizeIO(void) {
    ios::sync_with_stdio(false);
    cin.tie(NULL), cout.tie(NULL);
}
int main(int argc, char const *argv[]) {
    optimizeIO(), cin >> S;
    n = S.size(), S = '#' + S + ' ';
    InitF(), InitG();
    for (int i = n; i >= 1; i--) {
        for (int j = 1; j <= n; j++) {
            s1[i][j] = s1[i + 1][j] + s1[i][j - 1] - s1[i + 1][j - 1];
            s2[i][j] = s2[i + 1][j] + s2[i][j - 1] - s2[i + 1][j - 1];
            s1[i][j] += f[i][j], s2[i][j] += m998(i - 1) * f[i][j];
        }
    }

    m998 ans = 0;
    for (int l1 = 1; l1 <= n; l1++) {
        for (int r1 = l1 + 2; r1 <= n; r1++) {
            ans += (s2[l1 + 1][r1 - 1] - l1 * s1[l1 + 1][r1 - 1]) * g[l1][r1];
        }
    }
    cout << ans << endl;
    return 0;
}

J. Trajan Algorithm

猜了一手求的是存在于两个大小大于 \(2\) 的点双的点。

然后就 WA on #2 了。

要特判有一个大小为 \(2\) 的点双和割点相连的情况,这种时候该割点还是会被判作割点。

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
const int N=1e5+9;

vector<int> e[N],stk;
vector<vector<int>> vdcc;
int dfn[N],low[N],n,m,dcnt;
inline void Tarjan(int x,int fa){
	bool flag=0;
	stk.push_back(x);
	dfn[x]=low[x]=++dcnt;
	for(int y:e[x]){
		if(y==fa) continue ;
		if(!dfn[y]){
			Tarjan(y,x);
			low[x]=min(low[x],low[y]);
			if(low[y]>=dfn[x]){
				flag=1;
				vdcc.push_back(vector<int>());
				while(stk.size()){
					int p=stk.back();
					stk.pop_back();
					vdcc.back().push_back(p);
					if(p==y) break ;
				}
				vdcc.back().push_back(x);
			}
		}else low[x]=min(low[x],dfn[y]);
	}
	if(!flag&&!~fa) vdcc.push_back({x});
}
inline void Solve(){
	cin>>n>>m;
	for(int i=1,u,v;i<=m;i++){
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}

	for(int i=1;i<=n;i++) if(!dfn[i]) Tarjan(i,-1);

	vector<int> cnt(n+1,0),du(n+1,0);
	for(auto &v:vdcc){
		if(v.size()<=2) for(int x:v) du[x]=1;
		else for(int x:v) cnt[x]++;
	}

	bool flag=0;
	for(int x=1;x<=n;x++) if(cnt[x]>1&&!du[x]) cout<<x<<' ',flag=1;
	if(!flag) cout<<"Empty";
	cout<<endl;
	
	dcnt=0;
	for(int i=1;i<=n;i++) e[i].clear(),dfn[i]=low[i]=0;
	vdcc.clear();
	stk.clear();
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

Stage 6: Grand Prix of Shenyang

虽然没啥贡献,还是记录一下吧。

B. Buggy Painting Software I

枚举总共创建了 \(k\) 层画布,可以证明这 \(k\) 层从上到下涂的分别是出现次数最多的 \(k\) 种颜色,剩下的颜色直接画在画布上,透明直接擦穿画布即可,前缀和计算贡献取最小值即为答案。

#include<bits/stdc++.h>

using namespace std;

using ll=long long;
const int N=5e2+9;

ll sf[N*N],sg[N*N];
int p[N][N],cnt[N*N],n,m,a,b;

inline void Solve(){
	cin>>n>>m>>a>>b;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++) cin>>p[i][j],cnt[p[i][j]]++;
	}

	sort(cnt+1,cnt+n*m+1,greater<int>());
	for(int i=1;i<=n*m;i++) sf[i]=sf[i-1]+1ll*cnt[i]*(i-1)*b,sg[i]=sg[i-1]+1ll*cnt[i]*a;
	ll ans=LLONG_MAX;
	for(int k=0;k<=n*m;k++){
		ll res=1ll*cnt[0]*k*b+sf[k]+sg[n*m]-sg[k];
		ans=min(ans,res);
	}

	cout<<ans<<endl;

	for(int i=0;i<=n*m;i++) cnt[i]=sf[i]=sg[i]=0;
}

signed main(){
	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

J. The Echoes of Chronos

把区间内的所有数全部扔到 \([v,v+m]\) 上,那么答案就是最大的空白段的长度。

发现如果没有 \([v,v+m]\) 的限制就是秃子酋长,尝试继续用只删莫队维护区间最大空白段长度。

考虑对值域分块,每块块内维护最大空白段以及块内出现的极左极右位置,由于块内答案只增,故可以简单地使用链表维护,而块间答案则可以在询问时记录上一个出现的位置,暴力计算块间答案。

时间复杂度 \(O(n\sqrt n+n\sqrt q+q\sqrt n)\)\(n\)\(q\) 同阶,可视为 \(O(n\sqrt n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
const int N=4e5+9;
const int T=7e2+9;
inline void ChMax(int &x,int y){if(y>x) x=y;}

vector<int> val;
int a[N],ql[N],qr[N],qv[N],vl[N],vr[N],n,q,m,v,h;

int cnt;
namespace Block{
	int ocr[N<<1],blk[N<<1],L[T],R[T],B;
	inline void InitBlock(){
		B=sqrt(v);
		for(int i=1;i<=v;i++) blk[i]=(i-1)/B+1;
		for(int i=1;i<=v;i++) R[blk[i]]=i;
		for(int i=v;i>=1;i--) L[blk[i]]=i;
	}
	pair<int*,int> rcll[N];
	int cl[T],cr[T],res[T],pre[N],suc[N],top;
	inline void Build(){
		for(int x=1;x<=blk[v];x++){
			cr[x]=0;
			for(int i=L[x];i<=R[x];i++){
				if(!ocr[i]) continue ;
				pre[i]=cr[x],suc[cr[x]]=i,cr[x]=i;
				if(pre[i]) ChMax(res[x],val[i]-val[pre[i]]);
				else cl[x]=i;
			}
			pre[cl[x]]=suc[cr[x]]=0;
		}
	}
	inline void Del(int i){
		cnt++;
		if(--ocr[i]) return ;
		int x=blk[i];
		if(!pre[i]) cl[x]=suc[i];
		else suc[pre[i]]=suc[i];
		if(!suc[i]) cr[x]=pre[i];
		else pre[suc[i]]=pre[i];
		if(pre[i]&&suc[i]) ChMax(res[x],val[suc[i]]-val[pre[i]]);
	}
	inline void RDel(int i){
		rcll[++top]={&ocr[i],ocr[i]};
		if(--ocr[i]) return ;
		int x=blk[i];
		if(!pre[i]) rcll[++top]={&cl[x],cl[x]},cl[x]=suc[i];
		else rcll[++top]={&suc[pre[i]],suc[pre[i]]},suc[pre[i]]=suc[i];
		if(!suc[i]) rcll[++top]={&cr[x],cr[x]},cr[x]=pre[i];
		else rcll[++top]={&pre[suc[i]],pre[suc[i]]},pre[suc[i]]=pre[i];
		if(pre[i]&&suc[i]){
			rcll[++top]={&res[x],res[x]};
			ChMax(res[x],val[suc[i]]-val[pre[i]]);
		}
	}
	inline void Recall(){
		while(top){
			*rcll[top].first=rcll[top].second;
			top--;
		}
	}
	inline array<int,3> Buery(int l,int r,int nl=0,int nr=0,int ans=0){
		for(int i=l;i<=r;i++){
			if(!ocr[i]) continue ;
			if(nr) ChMax(ans,val[i]-val[nr]);
			else nl=i;
			nr=i;
		}
		return {nl,nr,ans};
	}
	inline array<int,3> Query(int l,int r){
		if(blk[l]==blk[r]) return Buery(l,r);
		array<int,3> tmp=Buery(l,R[blk[l]]);
		int nl=tmp[0],nr=tmp[1],ans=tmp[2];
		for(int i=blk[l]+1;i<blk[r];i++){
			if(!cl[i]) continue ;
			if(nr) ChMax(ans,val[cl[i]]-val[nr]);
			else nl=cl[i];
			ChMax(ans,res[i]);
			nr=cr[i];
		}
		return Buery(L[blk[r]],r,nl,nr,ans);
	}
	inline void Clear(){
		for(int i=0;i<=v;i++) pre[i]=suc[i]=ocr[i]=0;
		for(int i=0;i<=blk[v];i++) cl[i]=cr[i]=res[i]=0;
	}
}
namespace MoAlgo{
	int blk[N<<1],L[T],R[T],B;
	inline void Init(){
		B=sqrt(q);
		for(int i=1;i<=n;i++) blk[i]=(i-1)/B+1;
		for(int i=1;i<=n;i++) R[blk[i]]=i;
		for(int i=n;i>=1;i--) L[blk[i]]=i;
	}
	int ans[N];
	vector<int> qry[T];
	inline void MoAlgo(){
		Init();
		Block::InitBlock();
		for(int i=1;i<=q;i++) qry[blk[ql[i]]].push_back(i);
		for(int i=1;i<=blk[n];i++){
			sort(qry[i].begin(),qry[i].end(),[](int x,int y){return qr[x]>qr[y];});
			int l=L[i],r=n;
			for(int j=l;j<=r;j++) Block::ocr[a[j]]++,Block::ocr[a[j]+h]++;
			Block::Build();
			vector<array<int,2>> p;
			for(int j=L[i];j<=R[i];j++) p.push_back({a[j],j}),p.push_back({a[j]+h,j});
			sort(p.begin(),p.end());
			for(int j:qry[i]){
				if(blk[ql[j]]==blk[qr[j]]){
					int nl=0,nr=0;
					for(auto t:p){
						if(t[1]<ql[j]||t[1]>qr[j]) continue ;
						if(t[0]<vl[j]||t[0]>vr[j]) continue ;
						if(nr) ChMax(ans[j],val[t[0]]-val[nr]);
						else nl=t[0];
						nr=t[0];
					}
					if(nl) ChMax(ans[j],val[nl]-qv[j]);
					if(nr) ChMax(ans[j],qv[j]+m-val[nr]);
					continue ;
				}
				while(r>qr[j]) Block::Del(a[r]),Block::Del(a[r]+h),r--;
				while(l<ql[j]) Block::RDel(a[l]),Block::RDel(a[l]+h),l++;
				auto tmp=Block::Query(vl[j],vr[j]);
				int nl=tmp[0],nr=tmp[1];
				ans[j]=tmp[2];
				if(nl) ChMax(ans[j],val[nl]-qv[j]);
				if(nr) ChMax(ans[j],qv[j]+m-val[nr]);
				Block::Recall();
				l=L[i];
			}
			Block::Clear();
		}
	}
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	cin>>n>>q>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=q;i++) cin>>ql[i]>>qr[i]>>qv[i];

	val.push_back({-1});
	for(int i=1;i<=n;i++) val.push_back(a[i]),val.push_back(a[i]+m);
	sort(val.begin(),val.end());
	val.erase(unique(val.begin(),val.end()),val.end());
	v=val.size()-1,h=v>>1;
	for(int i=1;i<=n;i++) a[i]=lower_bound(val.begin(),val.end(),a[i])-val.begin();
	for(int i=1;i<=q;i++){
		vl[i]=lower_bound(val.begin(),val.end(),qv[i])-val.begin();
		vr[i]=upper_bound(val.begin(),val.end(),qv[i]+m)-val.begin()-1;
	}

	MoAlgo::MoAlgo();

	for(int i=1;i<=q;i++) cout<<m-MoAlgo::ans[i]<<endl;

	return 0;
}

Stage 7: Grand Prix of Zhengzhou

NOIP 晚上三个人连麦打的。

G. Plus Xor

因为 \(b\) 很小,所以直接在 \(\bmod b\cdot 2^k\) 意义下跑同余最短路,其中 \(k\) 是最小的满足 \(2^k\gt b\) 的整数。找到从 \(a\bmod b\cdot 2^k\) 操作到 \(c+\lambda b\bmod b\cdot 2^k\) 需要的最少增量,剩下的距离直接不断加 \(b\) 即可,时间复杂度 \(O(b^2\log b)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=1e3*(1<<10)+9;

int b;
ll a,c;
vector<array<int,2>> e[N];

int inq[N];
ll dis[N];
inline void SPFA(int s){
	for(int i=0;i<(b<<10);i++) dis[i]=1e18;
	queue<int> q;
	dis[s]=0,q.push(s),inq[s]=1;
	while(q.size()){
		int x=q.front();
		q.pop();
		inq[x]=0;
		for(auto p:e[x]){
			if(dis[x]+p[1]<dis[p[0]]){
				dis[p[0]]=dis[x]+p[1];
				if(!inq[p[0]]) q.push(p[0]),inq[p[0]]=1;
			}
		}
	}
}

inline void Solve(){
	cin>>a>>b>>c;
	for(int i=0;i<b;i++){
		for(int j=0;j<1024;j++){
			e[i<<10|j].push_back({i<<10|((j+b)&1023),b});
			e[i<<10|j].push_back({((i+((j^b)-j)%b+b)%b)<<10|(j^b),(j^b)-j});
		}
	}
	SPFA((a%b)<<10|(a&1023));
	if(a+dis[(c%b)<<10|(c&1023)]<=c) cout<<"YES"<<endl;
	else cout<<"NO"<<endl;
	for(int i=0;i<(b<<10);i++) dis[i]=inq[i]=0,e[i].clear();
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

J. Subrectangle Count

通过手模若干种情况,可以得出,一个 \(2\times 2\) 子矩阵如果是 \(\left[\begin{matrix}x&x+1\\x+2&x+3 \end{matrix}\right]\) 形式的,那么它的形式一定是 \(\left[\begin{matrix}(\overline{x00})_2&(\overline{x01})_2\\(\overline{x10})_2&(\overline{x11})_2 \end{matrix}\right]\) 或者\(\left[\begin{matrix}(\overline{x10})_2&(\overline{x11})_2\\ [\overline{(x+1)00}]_2&[\overline{(x+1)01}]_2 \end{matrix}\right]\)

第一种情况比较简单,可以直接预处理出对任意一行有多少符合条件的列。

第二种情况则需要枚举进位是进到哪一位,用 map 存储前面几位的情况。

时间复杂度 \(O(n\log n\log V)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=2e5+9;

int a[N],b[N],n,m;
inline void Solve(){
	cin>>m>>n;
	for(int i=1;i<=m;i++) cin>>b[i];
	for(int i=1;i<=n;i++) cin>>a[i];

	ll ans=0;
	int cnt[2][2]={{0,0},{0,0}};
	unordered_map<int,int> f[2][2][28];
	for(int i=1;i<n;i++){
		if(((a[i]^a[i+1])&3)!=1) continue ;
		if((a[i]^a[i+1])>>2) continue ;
		int p=a[i]&1,q=a[i]>>1&1,x=a[i]>>2;
		for(int j=0;j<28;j++) f[p][q][j][x&((1<<j+1)-1)]++;
		cnt[p][q]++;
	}
	for(int i=1;i<m;i++){
		if(((b[i]^b[i+1])&3)!=2) continue ;
		int p=b[i]&1,q=b[i]>>1&1;
		int x=b[i]>>2,y=b[i+1]>>2;
		if(!(x^y)) ans+=cnt[p][q];
		for(int j=0;j<28;j++){
			if((x^y)>>j+1) continue ;
			int _x=x&((1<<j+1)-1),_y=y&((1<<j+1)-1);
			_x^=(1<<j)-1,_y^=(1<<j);
			if(_x!=_y) continue ;
			if(f[p][q^1][j].count(_x)) ans+=f[p][q^1][j][_x];
		}
	}

	cout<<ans<<endl;
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

Stage 8: Grand Prix of Poland

操怎么离 11 题编辑距离为 1 啊。

A. AIMPPZ

尽量用 \(\tt AI\) 替换 5 的权值即可,因为 \(\tt AI\) 没有 border 所以可以随便填,注意可能存在 \(n\leftarrow n/5\) 之后目前构造的 \(\tt AI\) 长度总和已经大于 \(n\) 的情况,此时要回退一个 \(\tt AI\) 串。

#include<bits/stdc++.h>

using namespace std;

signed main(){
	int n;
	cin>>n;

	int k=0;
	while(n%5==0){
		n/=5;
		k++;
		if(2*k>n){
			k--;
			n*=5;
			break ;
		}
	}

	for(int i=1;i<=k;i++) cout<<"AI";
	for(int i=1;i<=n-2*k;i++) cout<<'J';
	cout<<endl;

	return 0;
}

B. Beats

首先一定存在一种操作方式使得被拎到首位的在值域上是一个前缀,因为显然如果是从某个数最后一位拿到首位和直接拎到最前面是等价的。

因此考虑枚举前 \(i\) 拎到前面,剩下就只能用第二种操作了,此时操作数是 \(n\) 减去值域在 \((i,n]\) 的数在序列上第一段上升段的长度,因为这些是已经有序的。

从后向前枚举 \(i\),使用双端队列维护上升段即可。

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
const int N=2e5+9;

int a[N],b[N],n;

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);

	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i],b[a[i]]=i;

	int ans=n;
	deque<int> q;
	for(int i=n;i>=1;i--){
		if(!q.size()||b[q.front()]>b[i]) q.push_front(i);
		else while(q.size()&&b[q.back()]>b[i]) q.pop_back();
		ans=min(ans,n-signed(q.size()));
	}

	cout<<ans<<endl;
	
	return 0;
}

C. Count Triangular Sequences

虽然不是我写的,但是感觉题挺好,就写在这里了。

首先正难则反,变成计数 \(\forall i,a_i\leq n\wedge a_i+a_{i+1}\leq a_{i+2}\) 的序列的数量。

注意到限制非常像斐波那契数列,考虑转化题意,令 \(f_i\) 表示斐波那契数列的第 \(i\) 项,特别的,有 \(f_0=0,f_1=1\)。那么等价于对满足以下条件的长度为 \(m\) 的数组 \(b\) 计数:

  • \(\forall 1\leq i\leq m,b_i\geq [i=1]\)
  • \(\displaystyle \forall 1\leq i\leq m,1\leq a_i=\sum_{j=1}^ib_jf_{i-j+1} \leq n\)

不难发现 \(a\) 是单调的,因此第二条限制最紧的位置是 \(a_m\),因此只要考虑 \(a_m\) 处的限制即可。

此时就变成了完全背包问题,时间复杂度 \(O(n\log_{1+\phi}n)\)

D. Division with Polylines

一定不要取相似的变量名啊。

考虑枚举两条线的交点,那么整个正方形可以根据这个交点的行和列又分成 4 个区域,其中东南西北区的边界在这 4 个区域种各有一定的单调性。而算上东南西北区的边界可以把整个正方形划分成 8 个小区域,其中共有 7 条已知条件限制其取值。因此如果确定某个区域的取值则可以找出剩下区域的理想取值。

而各个小区域的取值则是可以合法性 DP 计算出的,则通过一系列线性变换对小区域解集取交即可判断合法性。若 DP 使用 bitset 优化可以做到 \(O(\frac {n^4}w)\)

由于这题 \(a_{i,j}\in\{1,0,-1\}\),因此可以证明小区域可以取到的值是一个跨过原点的区间,因此可以直接 \(O(n^2)\) DP 出其解集。

为什么会 typo 呢。

#include<bits/stdc++.h>

using namespace std;

const int T=2e3+9;
const int inf=1e9+7;

using Seg=array<int,2>;

inline bool Valid(Seg x){return x[0]<=x[1];}
inline Seg Check(Seg x){return Valid(x)?x:Seg({inf,-inf});};
inline Seg Cap(Seg x,Seg y){return Check({max(x[0],y[0]),min(x[1],y[1])});}
inline Seg Cup(Seg x,Seg y){return Check({min(x[0],y[0]),max(x[1],y[1])});}
inline Seg Shift(Seg x,int k){return Check({x[0]+k,x[1]+k});}
inline Seg Neg(Seg x){return Check({-x[1],-x[0]});}

int a[T][T],s[T][T],n,N,E,S,W;
Seg NE[T][T],NW[T][T],SE[T][T],SW[T][T];
inline void Solve(){
	cin>>n>>W>>S>>E>>N;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++) cin>>a[i][j];
	}

	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			s[i][j]=a[i][j]+s[i-1][j]+s[i][j-1]-s[i-1][j-1];
		}
	}
	auto Sum=[&](int x0,int x1,int y0,int y1){
		return s[x1][y1]-s[x0-1][y1]-s[x1][y0-1]+s[x0-1][y0-1];
	};

	for(int j=1;j<=n;j++){
		int c=0;
		for(int i=1;i<=n;i++){
			c+=a[i][j];
			NW[i][j]=Shift(NW[i][j-1],c);
			NW[i][j]=Cup(NW[i][j],NW[i-1][j]);
		}
		c=0;
		for(int i=n;i>=1;i--){
			c+=a[i][j];
			SW[i][j]=Shift(SW[i][j-1],c);
			SW[i][j]=Cup(SW[i][j],SW[i+1][j]);
		}
	}
	for(int j=n;j>=1;j--){
		int c=0;
		for(int i=1;i<=n;i++){
			c+=a[i][j];
			NE[i][j]=Shift(NE[i][j+1],c);
			NE[i][j]=Cup(NE[i][j],NE[i-1][j]);
		}
		c=0;
		for(int i=n;i>=1;i--){
			c+=a[i][j];
			SE[i][j]=Shift(SE[i][j+1],c);
			SE[i][j]=Cup(SE[i][j],SE[i+1][j]);
		}
	}

	bool flag=0;
	for(int i=0;i<=n;i++){
		for(int j=0;j<=n;j++){
			Seg nw=NW[i][j],sw=SW[i+1][j],ne=NE[i][j+1],se=SE[i+1][j+1];
			Seg cur=nw;
			cur=Cap(ne,Shift(Neg(cur),N));
			cur=Shift(Neg(cur),Sum(1,i,j+1,n));
			se=Shift(Neg(se),Sum(i+1,n,j+1,n));
			cur=Cap(se,Shift(Neg(cur),E));
			cur=Shift(Neg(cur),Sum(i+1,n,j+1,n));
			cur=Cap(sw,Shift(Neg(cur),S));
			bool tmp=Valid(cur);
			cur=Shift(Neg(cur),Sum(i+1,n,1,j));
			nw=Shift(Neg(nw),Sum(1,i,1,j));
			cur=Cap(nw,Shift(Neg(cur),W));
			if(tmp!=Valid(cur)) cout<<" > "<<i<<' '<<j<<endl;
			flag|=Valid(cur);
		}
	}

	if(flag) cout<<"TAK"<<endl;
	else cout<<"NIE"<<endl;

	for(int i=0;i<=n+1;i++){
		for(int j=0;j<=n+1;j++){
			a[i][j]=s[i][j]=0;
			for(int k:{0,1}) NW[i][j][k]=SW[i][j][k]=NE[i][j][k]=SE[i][j][k]=0;
		}
	}
}

signed main(){
	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

I. ICPC Isolation

直接把所有格子按照 \((i\bmod2,j\bmod 2,i,j)\) 的关键字顺序重排,顺序把队伍依次标号即可。由于不存在一个学校有 \(16\) 个队,所以任意 \(2\times 2\) 子矩阵中不会出现来自相同学校的队伍。

#include<bits/stdc++.h>

using namespace std;

signed main(){
	map<string,int> mp;
	mp["UW"]=11;mp["UJ"]=9;mp["UWR"]=8;mp["MAP"]=7;mp["PW"]=6;mp["AGH"]=5;mp["PG"]=4;mp["NLU"]=3;mp["PUT"]=3;mp["PO"]=2;mp["PWR"]=2;mp["SGGW"]=2;mp["UMCS"]=2;mp["UR"]=2;mp["ZUT"]=2;mp["DTP"]=1;mp["GOO"]=1;mp["HUA"]=1;mp["KUL"]=1;mp["PL"]=1;mp["PM"]=1;mp["PS"]=1;mp["UAM"]=1;mp["UG"]=1;mp["UMK"]=1;mp["UO"]=1;mp["WAT"]=1;

	string tar;
	cin>>tar;

	queue<string> buf;
	while(mp[tar]-->0) buf.push(tar);
	for(auto p:mp) while(p.second-->0) buf.push(p.first);

	string ans[8][10];
	for(int p:{0,1}){
		for(int q:{0,1}){
			for(int i=0;i<4;i++){
				for(int j=0;j<5;j++){
					ans[i<<1|p][j<<1|q]=buf.front();
					buf.pop();
				}
			}
		}
	}

	for(int i=0;i<8;i++){
		for(int j=0;j<10;j++) cout<<ans[i][j]<<' ';
		cout<<endl;
	}

	return 0;
}

J. Jury of AMPPZ

写不明白。

首先其他评审分别评了啥根本不重要,我们只关心 \(a_i+b_i+c_i+d_i\) 的值。

现在要赋权 \(e_i\),考虑二分最后入围了 \(t\) 个计几题,那么入围了 \(k-t\) 个非计几题,因此前 \(t\) 个计几题要比第 \(k-t+1\) 个非计几题要牛。

令共有 \(s_0\) 个非计几题,\(s_1\) 个计几题。那么事实上我们并不关心第 \(1\sim k-t\) 个非计几题的取值以及第 \(t+1\sim s_1\) 个计几题的取值。因此只要把 \(1\sim k-t\) 分给非计几题,把 \(n-(s_1-t)+1\sim n\) 分给计几题即可。

时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=2e5+9;
const int lgN=2e1;

ll a[N],mx[N][lgN];
int b[N],lg[N],n,k;
vector<ll> s[2];

inline ll Max(int l,int r){
	int k=lg[r-l+1];
	return max(mx[l][k],mx[r-(1<<k)+1][k]);
}
inline bool Check(int mid){
	ll lim=Max(mid,s[0].size()-1)-1ll*n*mid,o=1ll*n*(s[0].size()+1);
	int tar=k-mid,cnt=0;
	for(ll x:s[1]){
		while(x+o<lim&&o<=1ll*n*n) o+=n;
		if(o>1ll*n*n) break ;
		cnt++,o+=n;
	}
	return cnt>=tar;
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	cin>>n>>k;
	for(int i=1,x;i<=n;i++){
		cin>>b[i];
		for(int j:{0,1,2,3}){
			cin>>x;
			a[i]+=x;
		}
		a[i]=a[i]*n+(n-i);
		s[b[i]].push_back(a[i]);
	}
	
	sort(s[0].begin(),s[0].end(),greater<ll>());
	for(int i=0;i<s[0].size();i++) s[0][i]+=1ll*n*(i+1);
	for(int i=0;i<s[0].size();i++) mx[i][0]=s[0][i];
	for(int i=2;i<=n;i++) lg[i]=lg[i>>1]+1;
	for(int k=1;k<=lg[n];k++){
		for(int i=0;i+(1<<k)-1<s[0].size();i++){
			mx[i][k]=max(mx[i][k-1],mx[i+(1<<k-1)][k-1]);
		}
	}
	sort(s[1].begin(),s[1].end(),greater<ll>());

	int l=-1,r=s[0].size();
	while(l+1<r){
		int mid=l+r>>1;
		if(Check(mid)) r=mid;
		else l=mid;
	}

	cout<<k-r<<endl;

	return 0;
}

Stage 9: Grand Prix of Jinan

队伍战犯前来报到。

E. Tree and Subgraph Problem

令一个连通块 \(T\) 的深度最小点为 \(p(T)\)

将贡献分为两类,\(p(\operatorname {sub}(S))\)\(p(\operatorname{sub}(T))\) 存在祖先-后代关系的以及没有的。

没有祖先-后代关系的可以直接在 LCA 处简单统计答案。

有祖先-后代关系的考虑 DSU on Tree,那么可以在 \(u\) 处计算 \(p(\operatorname{sub}(S))=u\)\(S\) 的方案数以及不包含 \(u\) 子树内的点的 \(T\) 的方案数,再扣掉没有祖先-后代关系的方案数即为所求。

时间复杂度 \(O(n\log n)\),不知道为什么 DSU on Tree 写了线段树合并。

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=4e5+9;
const int lgN=2e1;
const int mod=998244353;

inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int QPow(int x,int y){
	int res=1;
	while(y){
		if(y&1) MulAs(res,x);
		MulAs(x,x);
		y>>=1;
	}
	return res;
}
inline int Inv(int x){return QPow(x,mod-2);}

vector<int> e[N];
int fa[N],c[N],cnt[N],w[N],root[N],n,ans,fk,all;

struct Node{
	int lc,rc,siz,f,g;
}tr[N<<5];

int tot;
inline int Allc(){return ++tot;}
inline void Clear(){
	for(int x=1;x<=tot;x++) tr[x].lc=tr[x].rc=tr[x].f=tr[x].g=tr[x].siz=0;
	tot=0;
}
inline void PushUp(int x){
	tr[x].f=Add(tr[tr[x].lc].f,tr[tr[x].rc].f);
	tr[x].g=Add(tr[tr[x].lc].g,tr[tr[x].rc].g);
}
inline void Insert(int &x,int L,int R,int k){
	if(!x) x=Allc();
	if(L==R){
		tr[x].siz++;
		tr[x].f=w[tr[x].siz];
		tr[x].g=Sub(w[cnt[L]],w[cnt[L]-tr[x].siz]);
		return ;
	}
	int mid=L+R>>1;
	if(k<=mid) Insert(tr[x].lc,L,mid,k);
	else Insert(tr[x].rc,mid+1,R,k);
	PushUp(x);
}
inline void Merge(int &x,int y,int L,int R){
	if(!x||!y) return x|=y,void();
	if(L==R){
		tr[x].siz+=tr[y].siz;
		tr[x].f=w[tr[x].siz];
		tr[x].g=Sub(w[cnt[L]],w[cnt[L]-tr[x].siz]);
		return ;
	}
	int mid=L+R>>1;
	Merge(tr[x].lc,tr[y].lc,L,mid);
	Merge(tr[x].rc,tr[y].rc,mid+1,R);
	PushUp(x);
}
inline int Query(int x,int L,int R,int k){
	if(!x) return 0;
	if(L==R) return tr[x].siz;
	int mid=L+R>>1;
	if(k<=mid) return Query(tr[x].lc,L,mid,k);
	else return Query(tr[x].rc,mid+1,R,k);
}

inline void DFS(int x){
	int sum=0,tmp=0,ss=0;
	Insert(root[x],1,n,c[x]);
	for(int y:e[x]){
		if(y==fa[x]) continue ;
		fa[y]=x;
		DFS(y);
		SubAs(tmp,tr[root[y]].f);
		AddAs(fk,Mul(tr[root[y]].f,sum));
		AddAs(sum,tr[root[y]].f);
		Merge(root[x],root[y],1,n);
	}
	AddAs(tmp,tr[root[x]].f);
	AddAs(ans,Mul(tmp,Sub(all,tr[root[x]].g)));
}

inline void Solve(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>c[i],cnt[c[i]]++;
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}

	for(int i=1;i<=n;i++) w[i]=Sub(QPow(2,i),1);
	for(int i=1;i<=n;i++) AddAs(all,w[cnt[i]]);

	DFS(1);

	SubAs(ans,fk);
	MulAs(ans,2);

	cout<<ans<<endl;
	
	all=ans=fk=0;
	for(int i=1;i<=n;i++){
		c[i]=cnt[i]=w[i]=root[i]=fa[i]=0;
		e[i].clear();
	}
	Clear();
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

L. Activity Rehearsal

考虑把整个合并的过程模拟出来,发现树高是 \(O(\log n)\) 的,因此单点修改只会影响到 \(O(\log n)\) 次合并的结果。

直接树状数组维护区间和,交换硬改答案就是 \(O(n\log^2 n)\) 的。

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=4e5+9;

int a[N],id[N],n,q,tot;
int fa[N],lc[N],rc[N];

ll tr[N];
inline void Add(int x,ll k){while(x<=n) tr[x]+=k,x+=x&-x;}
inline ll Ask(int x){ll sum=0;while(x) sum+=tr[x],x&=x-1;return sum;}
inline ll Ask(int l,int r){return Ask(r)-Ask(l-1);}

inline void Build(){
	tot=n;
	queue<int> q;
	for(int i=1;i<=n;i++) q.push(i);
	while(q.size()>1){
		int x=q.front();
		q.pop();
		int y=q.front();
		q.pop();
		a[++tot]=min(a[x],a[y]);
		fa[x]=fa[y]=tot;
		lc[tot]=x,rc[tot]=y;
		id[tot]=tot-n;
		Add(id[tot],max(a[x],a[y]));
		q.push(tot);
	}
}
inline void F(int x,ll k){
	if(!x) return ;
	if(id[x]) Add(id[x],k*max(a[lc[x]],a[rc[x]]));
	F(fa[x],k);
}
inline void Upd(int x){
	if(!x) return ;
	if(lc[x]) a[x]=min(a[lc[x]],a[rc[x]]);
	Upd(fa[x]);
}
inline void M(int x,int k){
	F(x,-1);
	a[x]=k,Upd(x);
	F(x,1);
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	cin>>n>>q;
	for(int i=1;i<=n;i++) cin>>a[i];

	Build();
	while(q--){
		char op;
		int x,y;
		cin>>op>>x>>y;
		if(op=='C'){
			int k1=a[x],k2=a[y];
			M(x,k2),M(y,k1);
		}else{
			cout<<Ask(x,y)<<endl;
		}
	}

	return 0;
}

Extra Stage 3: Osijek

草拟吗什么傻逼场。

E. XOR Again?

首先考虑把其转成前缀异或和做,那么答案为 \(x\) 相当于挑选出来的右端点 \(r_i\) 均满足 \(s_{r_i}\subseteq x\)

因此直接把 \(s_i\) 扔进桶里做高维前缀和即可,时间复杂度 \(O(n+V\log V)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
const int N=1e6+9;
const int B=2e1;
const int S=(1<<B)+9;

int a[N],b[N],c[S],n;

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i],a[i]^=a[i-1];

	for(int i=1;i<=n;i++) c[a[i]=~a[i]&(1<<B)-1]++;
	for(int i=0;i<B;i++){
		for(int j=0;j<(1<<B);j++){
			if(~j>>i&1) c[j]+=c[j|(1<<i)];
		}
	}

	memset(b,0x3f,sizeof b);
	for(int i=0;i<(1<<B);i++){
		if((i&a[n])==i) b[c[i]]=min(b[c[i]],~i&(1<<B)-1);
	}

	for(int i=n;i>=1;i--) b[i]=min(b[i+1],b[i]);
	for(int i=1;i<=n;i++) cout<<b[i]<<' ';cout<<endl;

	return 0;
}

F. Binary Permutation

考虑从小到大填数,并维护谷的连续段个数 \(c\),那么添加一个谷就是加一个连续段,添加一个峰就是合并两个连续段,那么合并一个连续段就会添加 \(c(c-1)\) 的贡献。

时间复杂度 \(O(n)\),偶数的边界需要添加一些判断。

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=1e6+9;
const int mod=998244353;

inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int QPow(int x,int y){
	int res=1;
	while(y){
		if(y&1) MulAs(res,x);
		MulAs(x,x);
		y>>=1;
	}
	return res;
}
inline int Inv(int x){return QPow(x,mod-2);}

int a[N],n;

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];

	if(n&1){
		int ans=1,cnt=0;
		for(int i=1;i<=n;i++){
			if(a[i]) cnt++;
			else{
				MulAs(ans,Mul(cnt,cnt-1));
				cnt--;
			}
		}
		if(cnt!=1) ans=0;
		cout<<ans<<endl;
	}else{
		int ans=1,cnt=0;
		for(int i=1;i<=n;i++){
			if(a[i]) cnt++;
			else{
				ans=Add(Mul(ans,Mul(cnt,cnt-1)),Mul(ans,cnt));
				cnt--;
			}
		}
		if(cnt!=0) ans=0;
		cout<<ans<<endl;
	}

	return 0;
}

Stage 10: Grand Prix of Wrocław

A. Automatized Mineral Classification

首先可以通过 \(O(n)\) 次操作直接问出每种数第一次出现的位置。

将这些数删去,重复执行该操作即可得到若干组互不相同的数的下标,且后一组是前一组的子集,这样可以做到 \(O(n^2)\) 次查询,和暴力没有本质区别。

考虑根号分治优化筛数的查询次数。假设某一查询之后颜色种类数缩减到 \(\sqrt n\) 以下了,则可以通过暴力 \(O(n\sqrt n)\) 次查询出剩下的数的颜色关系。同时前面至多只会有 \(\sqrt n\) 组数被筛出来。

考虑分治优化相邻两组数 \(S_i\)\(S_{i+1}\) 的颜色关系判定。设颜色集合被分为了 \(C_0\)\(C_1\),则先插入 \(C_0\) 中所有数,从而可以根据问询集合中的数添加之后是否会新增颜色分成两个新的问询集合,分治做下去即可,单次查询次数是 \(O(n_i\log n_i)\) 的。

总查询次数 \(O(n\sqrt n)\)

#include<bits/stdc++.h>

using namespace std;

const int N=5e2+9;

int c[N],n;
vector<int> t[N];
inline int P(int x){
	int res;
	cout<<"+ "<<x<<endl;
	cin>>res;
	return res;
}
inline int D(){
	int res;
	cout<<'-'<<endl;
	cin>>res;
	return res;
}
inline void D(int t){while(t--) D();}

inline void Conquer(vector<int> z,vector<int> v){
	if(!v.size()) return ;
	if(z.size()==1){
		for(int x:v) c[x]=c[z.front()];
		return ;
	}
	vector<int> lv,rv;
	vector<int> lz,rz;
	for(int i=0;i<z.size()/2;i++) lz.push_back(z[i]);
	for(int i=z.size()/2;i<z.size();i++) rz.push_back(z[i]);
	int lst=0;
	for(int x:lz) lst=P(x);
	for(int x:v){
		int now=P(x);
		if(now>lst) rv.push_back(x);
		else lv.push_back(x);
		lst=now;
	}
	D(lz.size()+v.size());
	Conquer(lz,lv),Conquer(rz,rv);
}

signed main(){
	cin>>n;

	int B=sqrt(n);

	vector<vector<int>> row;
	vector<int> lft(n);
	iota(lft.begin(),lft.end(),1);
	while(true){
		row.push_back(vector<int>());
		vector<int> tmp;
		int lst=0;
		for(int x:lft){
			int now=P(x);
			if(now>lst) row.back().push_back(x);
			else tmp.push_back(x);
			lst=now;
		}
		D(lft.size());
		lft=tmp;
		if(row.back().size()<=B) break ;
	}

	for(int i=0;i<row[0].size();i++) c[row[0][i]]=i+1;
	for(int i=1;i<row.size();i++) Conquer(row[i-1],row[i]);

	for(int x:lft){
		int lst=P(x);
		for(int y:row.back()){
			int now=P(y);
			if(now==lst) c[x]=c[y];
			lst=now;
		}
		D(row.back().size()+1);
	}

	cout<<"! "<<row[0].size()<<endl;
	for(int i=1;i<=n;i++) t[c[i]].push_back(i);
	for(int i=1;i<=row[0].size();i++){
		cout<<t[i].size()<<' ';
		for(int x:t[i]) cout<<x<<' ';cout<<endl;
	}

	return 0;
}

F. Foxes

\(f_i/g_i\) 表示位置 \(i\) 作为最后一个/第一个数的最大 LIS 长度,\(p\) 表示指针位置。

考虑在值域线段树上维护出 \(f[1:p],g[p+1:n]\),那么答案就是左边的 \(f\) 加上右边的 \(g\) 的最大值,修改是简单的。

时间复杂度 \(O((n+q)\log n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
const int N=1e6+9;

struct Data{
	int f,g,ans;
	Data(){}
	Data(int _f,int _g,int _ans){f=_f,g=_g,ans=_ans;}
	Data(int _f,int _g){f=_f,g=_g,ans=f+g;}
	friend inline Data operator +(Data x,Data y){return Data(max(x.f,y.f),max(x.g,y.g),max(max(x.ans,y.ans),x.f+y.g));}
};
struct Node{
	int l,r;
	Data dat;
}tr[N<<2];

inline void PushUp(int x){tr[x].dat=tr[x<<1].dat+tr[x<<1|1].dat;}
inline void Build(int x,int l,int r){
	tr[x].l=l,tr[x].r=r;
	if(tr[x].l==tr[x].r) return ;
	int mid=tr[x].l+tr[x].r>>1;
	Build(x<<1,l,mid),Build(x<<1|1,mid+1,r);
	PushUp(x);
}
inline void Set(int x,int pos,Data k){
	if(tr[x].l==tr[x].r) return tr[x].dat=k,void();
	int mid=tr[x].l+tr[x].r>>1;
	if(pos<=mid) Set(x<<1,pos,k);
	else Set(x<<1|1,pos,k);
	PushUp(x);
}
inline Data Query(int x,int l,int r){
	if(l>r) return Data(0,0,0);
	if(l<=tr[x].l&&tr[x].r<=r) return tr[x].dat;
	int mid=tr[x].l+tr[x].r>>1;
	if(r<=mid) return Query(x<<1,l,r);
	else if(l>mid) return Query(x<<1|1,l,r);
	else return Query(x<<1,l,r)+Query(x<<1|1,l,r);
}

char op[N];
int a[N],ra[N],rp[N],n,q,tot;
inline void Discretize(){
	vector<array<int,2>> val;
	rp[0]=1;
	for(int i=1;i<=q;i++){
		rp[i]=rp[i-1];
		if(op[i]=='<') rp[i]--;
		else if(op[i]=='>') rp[i]++;
		val.push_back({ra[i],rp[i]});
	}
	val.push_back({0,0});
	for(int i=1;i<=n;i++) val.push_back({a[i],i});
	sort(val.begin(),val.end());
	val.erase(unique(val.begin(),val.end()),val.end());
	tot=val.size()-1;
	for(int i=1;i<=n;i++) a[i]=lower_bound(val.begin(),val.end(),array<int,2>({a[i],i}))-val.begin();
	for(int i=1;i<=q;i++) ra[i]=lower_bound(val.begin(),val.end(),array<int,2>({ra[i],rp[i]}))-val.begin();
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);

	cin>>n>>q;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=q;i++){
		cin>>op[i];
		if(op[i]=='!') cin>>ra[i];
	}

	Discretize();

	int p=1;
	Build(1,1,tot);
	for(int i=n;i>1;i--){
		Set(1,a[i],Data(0,Query(1,a[i],tot).g+1));
	}
	Set(1,a[1],Data(1,0));

	for(int o=1;o<=q;o++){
		if(op[o]=='<'){
			Set(1,a[p],Data(0,Query(1,a[p],tot).g+1));
			p--;
		}else if(op[o]=='>'){
			p++;
			Set(1,a[p],Data(Query(1,1,a[p]).f+1,0));
		}else{
			Set(1,a[p],Data(0,0));
			a[p]=ra[o];
			Set(1,a[p],Data(Query(1,1,a[p]).f+1,0));
			cout<<tr[1].dat.ans<<endl;
		}
	}

	return 0;
}

Stage 11: Grand Prix of Southeastern Europe

B. AND Reconstruction

首先套路地拆位,找到所有连续 \(\tt 1\) 段,那么长度至多是连续 \(\tt 1\) 段长度最小值,不然这一段就可能填成 \(\tt 0\)

然后如果出现了 \(\tt 000\),那么答案就是 \(1\),因为再高没法和 \(\tt 010\) 区分。

实现是简单的,时间复杂度 \(O(n\log V)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
const int N=2e5+9;

int a[N],n,m;

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	cin>>n>>m;
	for(int i=0;i<n;i++) cin>>a[i];

	int ans=n;
	for(int i=0;i<m;i++){
		int res=n;
		vector<int> v(n<<1);
		for(int j=0;j<n;j++) v[j]=v[j+n]=a[j]>>i&1;
		if(accumulate(v.begin(),v.end(),0)==0){
			ans=1;
			break ;
		}
		for(int j=0;j+2<n*2;j++){
			if(!v[j]&&!v[j+1]&&!v[j+2]){
				ans=1;
				break ;
			}
		}
		if(accumulate(v.begin(),v.end(),0)==v.size()) continue ;
		int p=find(v.begin(),v.end(),0)-v.begin();
		for(int j=p,k=0;j<p+n;j=k){
			while(k<n*2&&v[k]) k++;
			if(k==j) k++;
			else res=min(res,k-j);
		}
		ans=min(ans,res);
	}

	cout<<ans<<endl;

	return 0;
}

D. Two Options

考虑颜色相同的边,如果有超过 \(1\) 条,那么就是他们的端点交集,原因显然。

由于剩下的边颜色两两不同,那么颜色就不重要了,就是二选一问题。每个连通块如果是树贡献点数,基环树贡献 \(2\),否则贡献 \(0\)

答案就是所有连通块贡献的乘积,实现的比较菜,时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>

using namespace std;

using ll=long long;
const int N=1e6+9;
const int mod=1e9+7;

inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int QPow(int x,int y){
	int res=1;
	while(y){
		if(y&1) MulAs(res,x);
		MulAs(x,x);
		y>>=1;
	}
	return res;
}
inline int Inv(int x){return QPow(x,mod-2);}

vector<int> e[N],o[N];
int u[N],v[N],c[N],cnt[N],col[N],n,m;

int fa[N],vsiz[N],esiz[N];
inline int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
inline void Merge(int x,int y){
	x=Find(x),y=Find(y);
	if(x==y) return ;
	fa[y]=x;
	vsiz[x]+=vsiz[y];
	esiz[x]+=esiz[y];
}

signed main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++) cin>>u[i]>>v[i]>>c[i];

	for(int i=1;i<=m;i++){
		e[u[i]].push_back(i);
		e[v[i]].push_back(i);
		o[c[i]].push_back(i);
	}
	for(int i=1;i<=n;i++){
		if(o[i].size()<2) continue ;
		vector<int> node;
		for(int j:o[i]){
			cnt[u[j]]++;
			cnt[v[j]]++;
			node.push_back(u[j]);
			node.push_back(v[j]);
		}
		sort(node.begin(),node.end());
		node.erase(unique(node.begin(),node.end()),node.end());
		int p=0;
		for(int j:node){
			if(cnt[j]!=o[i].size()) continue ;
			if(!p) p=j;
			else p=-1;
		}
		if(!p){
			cout<<0<<endl;
			return 0;
		}
		if(~p) col[p]=i;
		for(int j:o[i]) cnt[u[j]]--,cnt[v[j]]--;
	}

	queue<int> q;
	for(int i=1;i<=n;i++) if(col[i]) q.push(i);
	while(q.size()){
		int x=q.front();
		q.pop();
		for(int i:e[x]){
			int y=x^u[i]^v[i];
			if(col[y]){
				if(col[x]!=c[i]&&col[y]!=c[i]){
					cout<<0<<endl;
					return 0;
				}
			}else{
				if(col[x]!=c[i]){
					col[y]=c[i];
					q.push(y);
				}
			}
		}
	}

	for(int i=1;i<=n;i++) fa[i]=i,vsiz[i]=!col[i];
	for(int i=1;i<=m;i++){
		if(col[u[i]]||col[v[i]]) continue ;
		Merge(u[i],v[i]);
		esiz[Find(u[i])]++;
	}

	int ans=1,cnt=0;
	for(int i=1;i<=n;i++){
		if(col[i]) continue ;
		if(fa[i]!=i) continue ;
		if(esiz[i]>vsiz[i]){
			cout<<0<<endl;
			return 0;
		}else if(esiz[i]==vsiz[i]) MulAs(ans,2);
		else{
			cnt++;
			MulAs(ans,vsiz[i]);
		}
	}
	while(cnt) MulAs(ans,cnt--);

	cout<<ans<<endl;

	return 0;
}

F. Language Barrier

呃啊。

首先从值域的角度贪心地考虑,棋子一定只会向终点方向移动。

钦定 \(l_1<l_n\),并且 \(\forall i,l_i\leftarrow \max(l_1,l_i),r_i \leftarrow \min(r_n,r_i)\)。那么按照左端点从左到右顺序转移就是对的。

建出点分树,则转移操作可以在点分树上快速完成,时间复杂度 \(O(n\log^2 n)\)

代码咕咕咕。

Stage 12: Grand Prix of Shanghai

打的现场赛,耶耶耶。

Stage 13: Grand Prix of Ōokayama

A. Depth of Interval

对于每个次小值可能和他作为区间最小次小值的只有单调栈左边/右边把它弹出来的点,也就是说 \(f\) 计算到第二层之后只有 \(O(n)\) 种本质不同的区间。而对于一个第二层的区间,其作用域是可以简单二分得出的。故可以在 \(O(n\log n)\) 的时间复杂度内解决。

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=3e5+9;
const int lgN=2e1;

int a[N],lp[N],rp[N],mnp[lgN][N],n;
inline int Cmp(int i,int j){return a[i]<a[j]?i:j;}
inline void InitMinP(){
	for(int i=1;i<=n;i++) mnp[0][i]=i;
	for(int k=1;k<=__lg(n);k++){
		for(int i=1;i+(1<<k)-1<=n;i++) mnp[k][i]=Cmp(mnp[k-1][i],mnp[k-1][i+(1<<k-1)]);
	}
}
inline int MinP(int l,int r){
	if(l>r) return 0;
	int k=__lg(r-l+1);
	return Cmp(mnp[k][l],mnp[k][r-(1<<k)+1]);
}

ll ans[N];

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	a[0]=a[n+1]=INT_MAX;

	vector<int> stk;
	for(int i=1;i<=n;i++){
		while(stk.size()&&a[stk.back()]>=a[i]) stk.pop_back();
		lp[i]=stk.size()?stk.back():0;
		stk.push_back(i);
	}
	stk.clear();
	for(int i=n;i>=1;i--){
		while(stk.size()&&a[stk.back()]>=a[i]) stk.pop_back();
		rp[i]=stk.size()?stk.back():n+1;
		stk.push_back(i);
	}

	InitMinP();
	map<array<int,2>,int> mp;
	vector<array<int,2>> ord;
	for(int i=1;i<=n;i++){
		if(lp[i]>=1) ord.push_back({lp[i],i});
		if(rp[i]<=n) ord.push_back({i,rp[i]});
	}
	sort(ord.begin(),ord.end(),[](auto x,auto y){return x[1]-x[0]<y[1]-y[0];});
	for(auto p:ord){
		int l=p[0],r=p[1];
		if(r-l-1<2) mp[{l,r}]=1;
		else{
			int x=MinP(l+1,r-1),y=Cmp(MinP(l+1,x-1),MinP(x+1,r-1));
			if(x>y) swap(x,y);
			mp[{l,r}]=mp[{x,y}]+1;
		}
		if(a[l]<a[r]){
			int L=lp[l],R=l;
			while(L+1<R){
				int mid=L+R>>1;
				if(a[MinP(mid,l-1)]>=a[r]) R=mid;
				else L=mid;
			}
			ans[mp[{l,r}]]+=1ll*(l-L)*(rp[r]-r);
		}else{
			int L=r,R=rp[r];
			while(L+1<R){
				int mid=L+R>>1;
				if(a[MinP(r+1,mid)]>=a[l]) L=mid;
				else R=mid;
			}
			ans[mp[{l,r}]]+=1ll*(l-lp[l])*(R-r);
		}
	}

	for(int i=1;i<=n;i++) cout<<ans[i]<<endl;

	return 0;
}

B. Increasing Swaps

称左旋为把序列的首项放到末尾,右旋为把序列末项放到开头,那么原先的操作相当于对于所有 \(T_i\leq t\) 的连续段执行左旋操作,所有操作的整体趋势是把连续段合并的。

考虑正难则反,假设原来的操作过程是 \(p\circ r_1r_2\ldots r_k=\epsilon\),那么:

\[\begin{align} p\circ r_1r_2\ldots r_k&=\epsilon \\ r_{k}^{-1}r_{k-1}^{-1}\ldots r_1^{-1}\circ p^{-1}&=\epsilon \\ p^{-1}\circ r_{k}^{-1}r_{k-1}^{-1}\ldots r_1^{-1}&=\epsilon \end{align} \]

因此我们成功把操作趋势变成了逐步分裂,对象是 \(q=p^{-1}\),具体操作是对每个连续段右旋。

称一个序列 \(q\) 的分裂区间序列 \(a\) 是指 \(q=a_1+a_2+\ldots a_k\),在满足每个 \(a_i\) 都是值域连续段且 \(a_i\) 之间相对有序的情况下 \(k\) 取到最大的序列 \(q\) 的划分。特别地,若 \(k=1,a_1=q\),那么成序列 \(q\) 是不可分裂的。

考虑如下贪心

  • 将现序列 \(q\) 不断右旋直到右旋出来的序列 \(q'\) 是可分裂的,设这一步右旋了 \(\Delta\) 次。

  • \(q'\) 分裂,对 \(a_1,a_2,.\ldots,a_k\) 递归执行该算法。设 \(f(q)\) 表示对序列 \(q\) 的答案,那么:

    \[f(q)=\left\{\begin{matrix}0& q~\text{is~sorted}\\\displaystyle \Delta+\max_if(a_i)&\text{otherwise}\end{matrix}\right. \]

贪心正确性证明

性质:对于一个序列 \(a\),以及其子序列 \(a'\)\(f(a')\leq f(a)\)

证明:考虑对于原先在 \(a\) 序列上的还原操作,直接忽略其他元素直接照搬到 \(a'\) 上也是可行的。

首先如果不进行分裂序列就是无序的,因此接下来探究不同的分裂对操作次数造成的影响。

对贪心分裂 \(a\) 和可能更优的分裂方式 \(b\) 的首段长度进行讨论:

  • \(|a_1|=|b_1|\):那么 \(a_1=b_1\),可以先把这两段撇掉,然后归约到其他情况。

  • \(|a_1|<|b_1|\)

    设原序列 \(q=t_1+t_2+t_3+t_4,~a_1=t_1,~b_1=t_4+t_1+t_2\)

    那么 \(f_a(q)=\max(f(t_1),f(t_2+t_3+t_4)),~f_b(q)=|t_4|+\max(f(t_4+t_1+t_2),f(t_3))\)

    由于对 \(t_2+t_3+t_4\) 旋转 \(|t_4|\) 次能够分裂,因此 \(f(t_2+t_3+t_4)\leq|t_4|+\max(f(t_4+t_2),f(t_3))\)

    那么:

    \[\begin{align} f_a(q)&=\max(f(t_1),f(t_2+t_3+t_4)) \\ &\leq\max(f(t_1),|t_4|+\max(f(t_4+t_2),f(t_3))) \\ &\leq|t_4|+\max(f(t_1),f(t_4+t_2),f(t_3)) \\ &\leq|t_4|+\max(f(t_4+t_1+t_2),f(t_3)) \\ &=f_b(q) \end{align} \]

  • \(|a_1|>|b_1|\)

    类似地,设原序列 \(q=t_4+t_1+t_2+t_3,~a_1=t_4+t_1+t_2,~b_1=t_1\)

    那么 \(f_a(q)=\max(f(t_4+t_1+t_2),f(t_3)),~f_b(q)=|t_1|+|t_2|+|t_3|+\max(f(t_1),f(t_2+t_3+t_4))\)

    由于对 \(t_4+t_1+t_2\) 旋转 \(|t_1|+|t_2|\) 次可以分裂,因此 \(f(t_4+t_1+t_2)\leq |t_1|+|t_2|+\max(f(t_1),f(t_2+t_4))\)

    那么:

    \[\begin{align} f_a(q)&=\max(f(t_4+t_1+t_2),f(t_3)) \\ &\leq\max(|t_1|+|t_2|+\max(f(t_1),f(t_2+t_4)),f(t_3)) \\ &\leq|t_1|+|t_2|+|t_3|+\max(f(t_1),f(t_2+t_4),f(t_3)) \\ &\leq|t_1|+|t_2|+|t_3|+\max(f(t_1),f(t_2+t_4+t_3)) \\ &=f_b(q) \end{align} \]

因此贪心必然不劣。

单层的分裂可以通过扫描值域做到线性,一共 \(O(n)\) 层,时间复杂度 \(O(n^2)\)

#include<bits/stdc++.h>

using namespace std;

const int N=5e3+9;

#define endl '\n'
int p[N],q[N],n;

inline int F(int l,int r){
	if(l==r) return 0;
	int O=0,T=n+1;
	for(int i=l;i<=r;i++) p[q[i]]=i;
	auto Prev=[&](int x){return x!=l?x-1:r;};
	auto Next=[&](int x){return x!=r?x+1:l;};
	auto W=[&](int x){return x<=p[r]?0:r-x+1;};
	for(int i=l,c=0;i<r;i++){
		c++;
		if(q[Prev(p[i])]<=i) c--;
		if(q[Next(p[i])]<=i) c--;
		O=max(O,W(p[i]));
		if(c<=1){
			if(O||q[l]<=i) T=min(T,O);
		}
	}
	if(T) rotate(q+l,q+r-T+1,q+r+1);
	int ans=0;
	for(int i=l,j=l-1,mx=0;i<=r;i++){
		mx=max(mx,q[i]);
		if(mx==i) ans=max(ans,F(j+1,i)),j=i;
	}
	return ans+T;
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	cin>>n;
	for(int i=1;i<=n;i++) cin>>p[i],q[p[i]]=i;

	cout<<F(1,n)<<endl;

	return 0;
}

C. Sum of Three Inversions

队友牛牛。

考虑把排列分成 \(A=\{\{1,2,3\},\{2,3,1\},\{3,1,2\}\},~B=\{\{3,2,1\},\{1,3,2\},\{2,1,3\}\}\) 两组,那么 \(A\) 组和 \(B\) 组的排列两两之间的贡献恰好为 \(1\)

因此考虑对 \(A\)\(B\) 分开 DP,最后再用组合数合起来,时间复杂度 \(O(n^5)\)

H. Akari Counting

队友牛牛。

考虑先把甜甜圈分成 \(8\) 个部分:

E A H
B O D
F C G

同时令行距分别为 \(h_1=A-1,h_2=B-A+1,h_3=H-B\),列宽为 \(w_1=C-1,w_2=D-C+1,w_3=W-C\),下文中不再使用原题面中的 \(A,B,C,D\)。同时用小写字母表示区域中灯的个数,例:\(a\) 表示区域 \(\tt A\) 的灯的个数。

考虑角块(\(\tt EFGH\))和棱块(\(\tt ABCD\))的合法条件:

  • 角块(以 \(\tt E\) 为例):\(e+a+h=h_1\)\(e+b+f=w_1\)
  • 棱块(以 \(\tt A\) 为例):\(e+a+h=h_1\)\(a=w_2\)

由此可知,使 \(\tt EFGH\) 均合法的充要条件为(\(e+a+h=h_1\)\(f+c+g=h_3\))或(\(e+b+f=w_1\)\(h+d+g=w_3\))。

\(X_0[i]\) 表示 \(e+a+h=h_1\)\(e+f=i\)\(\tt A\) 的方案数,注意这里预定了 \(e\)\(f\) 的位置,但是没有约束具体分配,因此 \(X_0[i]=\dbinom {w_2}{h_1-i}\dbinom {h_1}i(h_1-i)!\)。同时令 \(X_1[i]\) 表示 \(e+a+h\neq h_1,a=w_2\)\(e+f=i\)\(\tt A\) 的方案数,有 \(X_1[i]=\dbinom{h_1}{w_2}\dbinom{h_1-w_2}{i}w_2!\)。类似地用 \(f,c,g\) 定义 \(Y_0[i],Y_1[i]\)

\(P_0[i]\) 表示 \(e+a+h=h_1\wedge f+c+g=w_3\) 且角块共用了 \(i\) 个灯的方案数, \(P_1[i]\) 则反之,那么:

  • \(\displaystyle P_0[i]=\sum_{j+k=i} X_0[j]Y_0[k]\)
  • \(\displaystyle P_1[i]=\sum_{j+k=i} X_0[j]Y_1[k]+X_1[j]Y_0[k]+X_1[j]Y_1[k]\)

相当于 \(P_0\) 表示满足角块合法的第一个限制的方案数,类似地用列定义 \(Q_{0/1}[i]\)

那么答案就是:\(\displaystyle \sum_{i=0}^{\min(h_1+h_3,w_1+w_3)} i!(P_0[i]Q_0[i]+P_0[i]Q_1[i]+P_1[i]Q_0[i])\),因为在 \(P,Q\) 中我们没有指定角块的行和角块的列的具体对应关系。

用卷积可以做到 \(O(n\log n)\),瓶颈在于求解 \(P,Q\)

K. Two-Way Communication

简单的想法是,让 Alice 每 \(B\) 个 bit 分一组,扔给 Bob 看是不是比他牛,如果 Bob 比较牛,返回一个 \(1\),并且把包含这个块的后缀扔回给 Alice,否则返回 \(0\)。这时取 \(B=\sqrt {64}=8\) 可以取到 \(80\) 次询问。

考虑沿用 CSP-S1 2025 的思路,分块大小分别为 \(\{11,10,9,8,7,6,5,4,3,1\}\),可以做到 \(76\) 次。

代码交给 @DrAlfed 写了。

Stage 14: Grand Prix of Hong Kong

A. Bipartite Graph Matching Problem

考虑反悔贪心,从小到大扫描 \(r\),并尽量让右部点未匹配的集合下标尽量小。每次加入 \(r\) 时,如果可以找到增广路,那么直接增广,否则找到另一端下标最小的边数为偶数的交错路,用 \(r\) 把该端点换出来。区间的答案就是右部已匹配点的数量。由于两组最大匹配之间一定可以通过操作交错路相互转换,因此正确性有所保障。时间复杂度 \(O(n(n+m))\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=1e4+9;
const int mod=998244353;

inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int QPow(int x,int y){
	int res=1;
	while(y){
		if(y&1) MulAs(res,x);
		MulAs(x,x);
		y>>=1;
	}
	return res;
}
inline int Inv(int x){return QPow(x,mod-2);}

vector<int> e[N];
int to[N],fa[N],vis[N],n1,n2,m;
inline void DFS(int x,int op){
	vis[x]=1;
	for(int y:e[x]){
		if(vis[y]) continue ;
		if(op^(y==to[x])) continue ;
		fa[y]=x;
		DFS(y,!op);
	}
}

inline void Solve(){
	cin>>n1>>n2>>m;
	for(int i=1,u,v;i<=m;i++){
		cin>>u>>v,v+=n1;
		e[u].push_back(v);
		e[v].push_back(u);
	}

	int ans=0;
	for(int i=1;i<=n2;i++){
		fa[i+n1]=0,DFS(i+n1,0);
		
		int p=0;
		for(int j=1;j<=n1;j++) if(vis[j]&&!to[j]) p=j;
		if(!p){
			for(int j=n2;j>=1;j--) if(vis[j+n1]) p=j+n1;
			to[p]=0;
		}
		while(p){
			if(p<=n1){
				to[p]=fa[p];
				to[fa[p]]=p;
			}
			p=fa[p];
		}
		
		for(int j=i,cnt=0;j>=1;j--){
			cnt+=bool(to[j+n1]);
			AddAs(ans,Mul(Mul(Mul(cnt,i),j),(i^j)+1));
		}

		for(int j=1;j<=n1+n2;j++) vis[j]=0;
	}

	cout<<ans<<endl;

	for(int i=1;i<=n1+n2;i++) e[i].clear(),to[i]=0;
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

B. Travelling

不难发现答案整体结构是一条链,然后链上所有节点可以随便挂其他的子树。考虑树上背包,答案在 LCA 处转移,时间复杂度 \(O(n^2)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=4e3+9;
const ll inf=1e18;

ll f[N][2][N],o[N][N],g[N][N],h[N],es[N],ans[N];
vector<int> e[N];
int u[N],v[N],w[N],siz[N],n;
inline void DFS(int x,int fa){
	siz[x]=1,es[x]=0;
	for(int i:e[x]){
		int y=x^u[i]^v[i];
		if(y==fa) continue ;
		DFS(y,x);
		int tot=2*(siz[x]+siz[y]-1);
		ll v=2*siz[y],wth=2*(es[y]+w[i]);
		for(int j=0;j<=2*(siz[x]-1);j++){
			for(int k=0;k<=2*(siz[y]-1);k++){
				for(int c:{0}){
					h[j+k+c+1]=max(h[j+k+c+1],g[x][j]+f[y][c][k]+1ll*w[i]*(c+1));
					for(int d:{0}){
						h[j+k+c+1]=max(h[j+k+c+1],f[x][d][j]+f[y][c][k]+1ll*w[i]*(c+1));
					}
				}
			}
			h[j+v]=max(h[j+v],o[x][j]+wth);
		}
		for(int j=0;j<=tot;j++) o[x][j]=max(o[x][j],exchange(h[j],0));
		for(int c:{0}){
			for(int j=0;j<=2*(siz[x]-1);j++){
				for(int k=0;k<=2*(siz[y]-1);k++){
					h[j+k+c+1]=max(h[j+k+c+1],g[x][j]+f[y][c][k]+1ll*w[i]*(c+1));
				}
				h[j+v]=max(h[j+v],f[x][c][j]+wth);
			}
			for(int j=0;j<=tot;j++) f[x][c][j]=max(f[x][c][j],exchange(h[j],0));
		}
		for(int j=2*siz[x]-1;j<=tot;j++) g[x][j]=-inf;
		for(int j=tot;j>=v;j--) g[x][j]=max(g[x][j],g[x][j-v]+wth);
		siz[x]+=siz[y];
		es[x]+=es[y]+w[i];
	}
	for(int i=0;i<=2*(siz[x]-1);i++) ans[i]=max(ans[i],o[x][i]);
}
inline void Clear(){
	for(int i=1;i<=n;i++){
		for(int j=0;j<=2*(n-1);j++){
			g[i][j]=o[i][j]=f[i][0][j]=f[i][1][j]=0;
		}
		e[i].clear();
	}
	for(int j=0;j<=2*(n-1);j++) ans[j]=0;
}

inline void Solve(){
	cin>>n;
	for(int i=1;i<n;i++){
		cin>>u[i]>>v[i]>>w[i];
		e[u[i]].push_back(i);
		e[v[i]].push_back(i);
	}

	DFS(1,0);

	for(int i=0;i<=2*(n-1);i++) cout<<2ll*es[1]-ans[i]<<' ';cout<<endl;

	Clear();
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

F. Find the Circuit

对于题目给定的环,考虑随便钦定环上任意一个点作为起点,保留环的初始顺序,剩下的边直接拓扑排序。

那么在第二轮的时候,则可以轻松找到这个环的生成子图。剩下的图中,找到入度为 \(1\) 的点,把它前驱除了连到它以外的出边全部删掉。最终剩下的一定是原来的环。

时间复杂度 \(O(n+m)\)

G. Watering System

答案可以刻画成维护每个位置被喷水器喷到最小的 \(L\) 值,区间查最大值。

考虑插入一个喷水器的影响。不失一般性地,假设该喷水器向右。首先其不会影响到其向右第一个向右的喷水器以右的位置。对于向左的喷水器,被新喷水器替代一部分位置当且仅当这些位置在两个喷水器中点以左。换句话说,在第一个向左的喷水器之后,新喷水器能替代的喷水器离新喷水器的距离至少翻倍。因此每次至多修改 \(O(\log n)\) 个区间,用线段树维护就是 \(O(n\log^2 n)\) 的。

然而,事实上这是 \(O(n\log n)\) 的。但是我不太会证,大概是因为最坏情况下只能做差不多 \(\displaystyle \sum _{i=1}^n \operatorname{lowbit}(i-1)\) 次。

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
const int N=3e5+9;
const int inf=1e9+7;

struct Node{
	int l,r,dat,ptag,ntag,mnp,mnn,mxp,mxn;
}tr[N<<2];

inline void PushUp(int x){
	tr[x].dat=max(tr[x<<1].dat,tr[x<<1|1].dat);
	tr[x].mnp=min(tr[x<<1].mnp,tr[x<<1|1].mnp);
	tr[x].mxp=max(tr[x<<1].mxp,tr[x<<1|1].mxp);
	tr[x].mnn=min(tr[x<<1].mnn,tr[x<<1|1].mnn);
	tr[x].mxn=max(tr[x<<1].mxn,tr[x<<1|1].mxn);
}
inline void PushP(int x,int k){
	tr[x].ptag=k;
	tr[x].mnp=k;
	tr[x].mxp=k;
	tr[x].dat=k-tr[x].l;
	tr[x].mnn=k-2*tr[x].r;
	tr[x].mxn=k-2*tr[x].l;
	tr[x].ntag=INT_MIN;
}
inline void PushN(int x,int k){
	tr[x].ntag=k;
	tr[x].mnn=k;
	tr[x].mxn=k;
	tr[x].dat=k+tr[x].r;
	tr[x].mnp=k+tr[x].l*2;
	tr[x].mxp=k+tr[x].r*2;
	tr[x].ptag=INT_MIN;
}
inline void PushDown(int x){
	if(tr[x].ptag!=INT_MIN) PushP(x<<1,tr[x].ptag),PushP(x<<1|1,tr[x].ptag),tr[x].ptag=INT_MIN;
	if(tr[x].ntag!=INT_MIN) PushN(x<<1,tr[x].ntag),PushN(x<<1|1,tr[x].ntag),tr[x].ntag=INT_MIN;
}

inline void Build(int x,int l,int r){
	tr[x].l=l,tr[x].r=r,tr[x].ptag=tr[x].ntag=INT_MIN;
	if(tr[x].l==tr[x].r){
		tr[x].dat=inf;
		tr[x].mnp=tr[x].mxp=inf+tr[x].l;
		tr[x].mnn=tr[x].mxn=inf-tr[x].l;
		return ;
	}
	int mid=tr[x].l+tr[x].r>>1;
	Build(x<<1,l,mid),Build(x<<1|1,mid+1,r);
	PushUp(x);
}
inline void AssignP(int x,int l,int r,int k){
	if(k>=tr[x].mxp) return ;
	if(l<=tr[x].l&&tr[x].r<=r&&k<=tr[x].mnp) return PushP(x,k);
	PushDown(x);
	int mid=tr[x].l+tr[x].r>>1;
	if(l<=mid) AssignP(x<<1,l,r,k);
	if(r>mid) AssignP(x<<1|1,l,r,k);
	PushUp(x);
}
inline void AssignN(int x,int l,int r,int k){
	if(k>=tr[x].mxn) return ;
	if(l<=tr[x].l&&tr[x].r<=r&&k<=tr[x].mnn) return PushN(x,k);
	PushDown(x);
	int mid=tr[x].l+tr[x].r>>1;
	if(l<=mid) AssignN(x<<1,l,r,k);
	if(r>mid) AssignN(x<<1|1,l,r,k);
	PushUp(x);
}
inline int Query(int x,int l,int r){
	if(l<=tr[x].l&&tr[x].r<=r) return tr[x].dat;
	PushDown(x);
	int mid=tr[x].l+tr[x].r>>1,ans=0;
	if(l<=mid) ans=max(ans,Query(x<<1,l,r));
	if(r>mid) ans=max(ans,Query(x<<1|1,l,r));
	return ans;
}

int n,q;

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	cin>>n>>q;

	Build(1,1,n);
	while(q--){
		int op,x,y;
		cin>>op>>x>>y;
		if(op==1){
			if(!x) AssignP(1,1,y,y);
			else AssignN(1,y,n,-y);
		}else{
			int ans=Query(1,x,y);
			cout<<(ans<=inf/2?ans:-1)<<endl;
		}
	}

	return 0;
}

K. Cyclic Shift

考虑怎么比较两个位置 \(i\)\(j\) 哪个更优。取出 \([i,j),[j,j+(j-i))\),比较这两段的字典序,如果不能通过这两段判断,则钦定 \(i\) 更优,因为 \(j\) 会先遇到后面的大值。考虑倍增比较相邻两个值,那么总共进行 \(O(\log n)\) 次,每次检查的是 \(O(n)\) 的,总复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
const int N=5e5+9;

int a[N<<1],n;

inline bool Cmp(vector<int> a,vector<int> b){
	for(int i=1;i<a.size();i++) a[i]=max(a[i],a[i-1]);
	for(int i=1;i<b.size();i++) b[i]=max(b[i],b[i-1]);
	for(int i=0;i<a.size();i++){
		if(a[i]<b[i]) return 1;
		else if(b[i]<a[i]) return 0;
	}
	return 1;
}

inline void Solve(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i],a[i+n]=a[i];

	vector<int> p(n);
	iota(p.begin(),p.end(),1);

	while(p.size()>1){
		vector<int> q;
		for(int i=0;i<p.size();i+=2){
			if(i+1>=p.size()){
				q.push_back(p[i]);
			}else{
				int x=p[i],y=p[i+1],z=y+y-x;
				if(Cmp(vector<int>(a+x,a+y),vector<int>(a+y,a+z))) q.push_back(x);
				else q.push_back(y);
			}
		}
		p=q;
	}

	int x=p.front();
	for(int i=x,t=0;i<n+x;i++){
		t=max(t,a[i]);
		cout<<t<<' ';
	}
	cout<<endl;
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

Stage 15: Grand Prix of China

打的现场赛,正赛一坨,挑战赛王朝了。

Stage 16: Grand Prix of Warsaw

三个人在五个省份打比赛。

B. Bitwise Beach

本质不同的线段只有 \(O(\sqrt n)\) 种,相同长度的容易放在一起算,时间复杂度 \(O(n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=1e6+9;

int n;
char a[N],b[N];
vector<int> c,d;

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);
	
	cin>>n;
	for(int i=1;i<n;i++) cin>>a[i];
	for(int i=1;i<n;i++) cin>>b[i];

	a[n]=b[n]='1';
	for(int i=1,r=0;i<=n;i++){
		r++;
		if(a[i]=='1') c.push_back(r),r=0;
	}
	for(int i=1,r=0;i<=n;i++){
		r++;
		if(b[i]=='1') d.push_back(r),r=0;
	}

	set<int> s,t;
	for(int i:c){
		auto res=s.insert(i);
		if(!res.second) s.erase(res.first);
	}
	for(int i:d){
		auto res=t.insert(i);
		if(!res.second) t.erase(res.first);
	}

	ll res=0;
	for(int i:s){
		for(int j:t) res^=1ll*i*j;
	}

	cout<<res<<endl;

	return 0;
}

G. Good Permutations

首先把 \(-1\) 填完之后答案形态只能是把单位排列劈成若干段每段翻转。

\(x_i=i-l+1\),那么答案合法当且仅当:

  • \(\displaystyle \max_{i=l}^r a_i\leq r-l+1\)
  • \(i<j,a_i\neq -1,a_j\neq -1\),则:
    • \(i+a_i\leq j+a_j\)
    • \(\forall i+a_i<j+a_j,~\max(x_i,a_i)\lt \min(x_j,a_j)\)

以上所有条件均可以通过简单的预处理得到区间左端点上下界以及右端点上界,时间复杂度 \(O(n\log n+q)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
const int N=2e5+9;
const int lgN=2e1;

int a[N],mx[lgN][N],Rmx[lgN][N],Lmx[lgN][N],Lmn[lgN][N],s1[N],s2[N],s3[N],n,q;
inline void Init(){
	for(int i=1;i<=n;i++){
		mx[0][i]=a[i];
		Rmx[0][i]=s1[i];
		Lmx[0][i]=s2[i];
		Lmn[0][i]=s3[i];
	}
	for(int k=1;k<=__lg(n);k++){
		for(int i=1;i+(1<<k)-1<=n;i++){
			mx[k][i]=max(mx[k-1][i],mx[k-1][i+(1<<k-1)]);
			Rmx[k][i]=min(Rmx[k-1][i],Rmx[k-1][i+(1<<k-1)]);
			Lmx[k][i]=min(Lmx[k-1][i],Lmx[k-1][i+(1<<k-1)]);
			Lmn[k][i]=max(Lmn[k-1][i],Lmn[k-1][i+(1<<k-1)]);
		}
	}
}
inline int MaxA(int l,int r){
	int k=__lg(r-l+1);
	return max(mx[k][l],mx[k][r-(1<<k)+1]);
}
inline int Rmax(int l,int r){
	int k=__lg(r-l+1);
	return min(Rmx[k][l],Rmx[k][r-(1<<k)+1]);
}
inline int Lmax(int l,int r){
	int k=__lg(r-l+1);
	return min(Lmx[k][l],Lmx[k][r-(1<<k)+1]);
}
inline int Lmin(int l,int r){
	int k=__lg(r-l+1);
	return max(Lmn[k][l],Lmn[k][r-(1<<k)+1]);
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	cin>>n>>q;
	for(int i=1;i<=n;i++) cin>>a[i];

	vector<int> id;
	for(int i=1;i<=n;i++){
		s1[i]=s2[i]=n+1;
		if(~a[i]) id.push_back(i);
	}

	auto F=[&](int i){return i+a[i];};
	vector<int> stk,buf;
	for(int i:id){
		while(stk.size()&&F(stk.back())>F(i)){
			s1[stk.back()]=i;
			stk.pop_back();
		}
		stk.push_back(i);
	}

	stk.clear();
	int cur=0,lst=0,f=1;
	for(int i:id){
		if(F(i)!=cur){
			if(f){
				for(int j:buf){
					while(stk.size()&&a[stk.back()]>=a[j]){
						s1[stk.back()]=min(s1[stk.back()],j);
						stk.pop_back();
					}
				}
			}
			stk.insert(stk.end(),buf.rbegin(),buf.rend());
			if(F(i)<cur) stk.clear(),f=0;
			else{
				for(int j:buf) s2[j]=i-a[j]+1;
				if(buf.size()) lst=buf.back();
				f=1;
			}
			buf.clear();
		}
		s3[i]=lst-a[i]+1;
		cur=F(i);
		buf.push_back(i);
	}
	if(f){
		for(int j:buf){
			while(stk.size()&&a[stk.back()]>=a[j]){
				s1[stk.back()]=min(s1[stk.back()],j);
				stk.pop_back();
			}
		}
		stk.insert(stk.end(),buf.rbegin(),buf.rend());
	}

	Init();
	while(q--){
		int l,r;
		cin>>l>>r;
		if(MaxA(l,r)>r-l+1) cout<<"NO"<<endl;
		else if(r>=Rmax(l,r)) cout<<"NO"<<endl;
		else if(l>=Lmax(l,r)) cout<<"NO"<<endl;
		else if(l<=Lmin(l,r)) cout<<"NO"<<endl;
		else cout<<"YES"<<endl;
	}

	return 0;
}

I. Palindromes

先将 \(a\) 升序排序。对于一长段相邻两项满足 \(2a_{i-1}>a_i\) 的,其贡献相当于两个前缀 \(x,x+\Delta\) 的贡献,其中 \(\displaystyle \Delta=\gcd_i (a_i-a_{i-1}),x=a_i\bmod\Delta\)

考虑从大到小扫,并时刻维护当前的 \(\Delta\),以及与之对应的 \(x\),设新加的数是 \(y\)

  • \(2y\leq x\):则 \(y\)\(x,x+\Delta\) 不是一段,因此计算 \((x,x+\Delta),(y,x)\) 的贡献,更新 \(x'=y,\Delta'=0\)
  • \(x<2y\leq x+\Delta\),则 \(y,x\) 单成一段,与 \(x+\Delta\) 割裂,计算 \((\max(x,y),x+\Delta)\) 的贡献,更新 \(\Delta'=|x-y|,x'=x\bmod \Delta'\)。注意需要忽略 \(y=x\) 的情况。
  • 否则 \(y\)\(x,x+\Delta\) 在同一段,更新 \(\Delta'=\gcd(\Delta,|x-y|),x'=x\bmod \Delta'\)

时间复杂度 \(O(n\log V)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=5e5+9;

int n,mod;
ll a[N],lim;
inline int QPow(int x,ll y){
	int res=1;
	while(y){
		if(y&1) res=1ll*res*x%mod;
		x=1ll*x*x%mod;
		y>>=1;
	}
	return res;
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	cin>>lim>>n>>mod;
	for(int i=1;i<=n;i++) cin>>a[i];

	sort(a+1,a+n+1);
	n=unique(a+1,a+n+1)-a-1;
	int ans=1;
	ll x=2*lim,dlt=0;
	for(int i=n;i>=1;i--){
		ll y=a[i];
		if(2*y<=x){
			ans=1ll*ans*QPow(2,max((x+dlt+1)/2-x,0ll))%mod;
			ans=1ll*ans*QPow(2,(x+1)/2-y)%mod;
			x=y,dlt=0;
		}else if(2*y<=x+dlt){
			if(x==y) continue ;
			ans=1ll*ans*QPow(2,(x+dlt+1)/2-max(x,y))%mod;
			dlt=abs(x-y);
			x=x%dlt;
		}else{
			if(x==y) continue ;
			dlt=__gcd(dlt,abs(x-y));
			x=y%dlt;
		}
	}
	ans=1ll*ans*QPow(2,max((x+dlt+1)/2-x,0ll))%mod;
	ans=1ll*ans*QPow(2,(x+1)/2)%mod;

	cout<<ans<<endl;

	return 0;
}

J. Jewel Guards

相当于 \(10^6\) 次对所有 \(|T\cap S|\geq 2\)\(T\) 的权值都加 \(1\),求最后权值大于 \(m\) 的有多少。

直接把这个条件用 \(|T\cap S|=0/1\) 容斥,操作变为若干次子集加 \(x\),统计下来最后高维后缀和即可,时间复杂度 \(O(k(\sum c_i+2^k))\)

#include<bits/stdc++.h>

using namespace std;

using ll=long long;
const int N=2e6+9;
const int M=2e1+9;

ll f[N];
int c[N],o[N],n,k,m;
vector<array<int,2>> v[M];
vector<int> x;

signed main(){
	cin>>n>>k>>m;
	for(int i=1;i<=k;i++){
		cin>>c[i];
		v[i].resize(c[i]);
		for(auto &p:v[i]){
			cin>>p[0]>>p[1];
			x.push_back(p[0]);
			x.push_back(p[1]+1);
		}
	}

	x.push_back(-1),x.push_back(1),x.push_back(n+1);
	sort(x.begin(),x.end());
	x.erase(unique(x.begin(),x.end()),x.end());

	for(int i=1;i<=k;i++){
		for(auto &p:v[i]){
			int l=lower_bound(x.begin(),x.end(),p[0])-x.begin();
			int r=lower_bound(x.begin(),x.end(),p[1]+1)-x.begin();
			o[l]^=1<<i-1,o[r]^=1<<i-1;
		}
	}
	for(int i=1;i<x.size();i++) o[i]^=o[i-1];


	for(int i=1;i+1<x.size();i++){
		int w=x[i+1]-x[i];
		for(int j=0;j<k;j++){
			if(o[i]>>j&1) f[(~o[i]&(1<<k)-1)|(1<<j)]+=w;
		}
		f[~o[i]&(1<<k)-1]+=1ll*w*(1-signed(__builtin_popcount(o[i])));
	}

	for(int i=0;i<k;i++){
		for(int j=0;j<(1<<k);j++){
			if(~j>>i&1) f[j]+=f[j|(1<<i)];
		}
	}

	int cnt=0;
	for(int i=0;i<(1<<k);i++) cnt+=(n-f[i]>=m);

	cout<<cnt<<endl;

	return 0;
}

Stage 17: Grand Prix of St. Petersburg

B. Missing Number

何意味,直接二分节点数即可做到 \(\sim 1400\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
const int N=4e3+9;

int f[N][N],a[N],n,q;

inline int Solve(int l,int r){
	if(l==r) return -l;

	int mid=l+r>>1;
	vector<int> c(mid-l+2,0);
	for(int i=0;i<mid-l+2;i++) c[i]=q++;
	int F=Solve(l,mid),S=Solve(mid+1,r);

	for(int i=0;i<mid-l+2;i++){
		for(int j=0;j<=n;j++){
			if(j==0) f[c[i]][j]=(i==mid-l+1)?S:F;
			else{
				if(j<l||j>mid) f[c[i]][j]=c[i];
				else f[c[i]][j]=(i==mid-l+1)?c[i]:c[i+1];
			}
		}
	}

	return c[0];
}
inline int Work(){
	int x=0;
	for(int c=0;c<8;c++){
		for(int i=1;i<=n;i++){
			x=f[x][a[i]];
			if(x<0) return -x;
		}
	}
	return 0;
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	cin>>n,n++;
	
	Solve(1,n);

	cout<<q<<endl;
	for(int i=0;i<q;i++){
		for(int j=0;j<=n;j++) cout<<f[i][j]<<' ';
		cout<<endl;
	}

	return 0;
}

C. String Workshop

首先整个排序过程手玩一下发现就是冒泡排序,考虑把答案写成 \(\displaystyle \sum_{i=l}^r \bigg(\binom {t_i}2-\binom{s_2}{2}\bigg)\),其中 \(\displaystyle s_i=\sum_{j=l}^{i-1} [a_j \leq a_i],t_i=s_i+\sum_{j=i+1}^r [a_j<a_i]\)。整体来看,\(\sum t_i\) 是一个比较容易计算的值,因此考虑对每种颜色维护 \(\sum s_i^0,\sum s _i^1,\sum s_i^2\),合并信息可以做到 \(O(|\Sigma|)\)。修改则考虑维护一个长度为 \(|\Sigma|\) 的用于推平的 tag,记录排序完该段中每种颜色包含几个,容易做到 \(O(|\Sigma|)\) 分裂。因此总的可以做到 \(O((n+q)|\Sigma|\log n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=3e5+9;

struct Tag{
	int c[26];
	Tag(){memset(c,0,sizeof c);}
	inline int& operator [](int i){return c[i];}
	inline Tag P(int L){
		Tag x;
		for(int i=0;i<26;i++) x[i]=min(c[i],L),L-=x[i];
		return x;
	}
	inline Tag S(int L){
		Tag x;
		for(int i=25;~i;i--) x[i]=min(c[i],L),L-=x[i];
		return x;
	}
};
struct Data{
	ll x[26][3];
	Data(){memset(x,0,sizeof x);}
	Data(Tag c){
		memset(x,0,sizeof x);
		ll sum=0;
		for(int i=0;i<26;i++){
			x[i][2]=1ll*c[i]*(c[i]+1)*(2*c[i]+1)/6;
			x[i][1]=1ll*c[i]*(c[i]+1)/2;
			x[i][0]=c[i];
			x[i][2]+=2*sum*x[i][1]+sum*sum*x[i][0];
			x[i][1]+=sum*x[i][0];
			sum+=c[i];
		}
	}
	inline ll* operator [](int i){return x[i];}
	friend inline Data operator *(Data y,Data x){
		ll sum=0;
		for(int i=0;i<26;i++){
			sum+=y[i][0];
			x[i][2]+=y[i][2]+2*sum*x[i][1]+sum*sum*x[i][0];
			x[i][1]+=y[i][1]+sum*x[i][0];
			x[i][0]+=y[i][0];
		}
		return x;
	}
	inline void Assign(int k,int L){
		memset(x,0,sizeof x);
		x[k][0]=L;
		x[k][1]=1ll*L*(L+1)/2;
		x[k][2]=1ll*L*(L+1)*(2*L+1)/6;
	}
};
struct Node{
	int l,r,cov;
	Data dat;
	Tag tag;
}tr[N<<2];

int a[N],n,q;
inline void PushUp(int x){tr[x].dat=tr[x<<1].dat*tr[x<<1|1].dat;}
inline void Push(int x,Tag k){
	tr[x].cov=1;
	tr[x].dat=tr[x].tag=k;
}
inline void PushDown(int x){
	if(tr[x].cov){
		Push(x<<1,tr[x].tag.P(tr[x<<1].r-tr[x<<1].l+1));
		Push(x<<1|1,tr[x].tag.S(tr[x<<1|1].r-tr[x<<1|1].l+1));
		tr[x].cov=0;
	}
}
inline void Build(int x,int l,int r){
	tr[x].l=l,tr[x].r=r,tr[x].cov=0;
	if(tr[x].l==tr[x].r) return tr[x].dat.Assign(a[l],1),void();
	int mid=tr[x].l+tr[x].r>>1;
	Build(x<<1,l,mid),Build(x<<1|1,mid+1,r);
	PushUp(x);
}
inline void Assign(int x,int l,int r,Tag k){
	if(l>r) return ;
	if(l<=tr[x].l&&tr[x].r<=r) return Push(x,k);
	PushDown(x);
	int mid=tr[x].l+tr[x].r>>1;
	if(r<=mid) Assign(x<<1,l,r,k);
	else if(l>mid) Assign(x<<1|1,l,r,k);
	else{
		Assign(x<<1,l,mid,k.P(mid-l+1));
		Assign(x<<1|1,mid+1,r,k.S(r-mid));
	}
	PushUp(x);
}
inline Data Query(int x,int l,int r){
	if(l>r) return Data();
	if(l<=tr[x].l&&tr[x].r<=r) return tr[x].dat;
	PushDown(x);
	int mid=tr[x].l+tr[x].r>>1;
	if(r<=mid) return Query(x<<1,l,r);
	else if(l>mid) return Query(x<<1|1,l,r);
	else return Query(x<<1,l,r)*Query(x<<1|1,l,r);
}

ll co[N];

inline void Solve(){
	cin>>n>>q;
	for(int i=1;i<=n;i++){
		char c;
		cin>>c;
		a[i]=c-'A';
	}

	for(int i=1;i<=n;i++) co[i]=co[i-1]+1ll*i*(i-1);

	Build(1,1,n);
	while(q--){
		int op;
		cin>>op;
		if(op==1){
			int i;
			char c;
			cin>>i>>c;
			Tag x;
			x[c-'A']=1;
			Assign(1,i,i,x);
		}else if(op==2){
			int l,r;
			cin>>l>>r;

			ll ans=0,sum=0;
			Data res=Query(1,l,r);
			for(int i=0;i<26;i++){
				sum+=res[i][0];
				ans+=(co[sum]-co[sum-res[i][0]])-(res[i][2]-res[i][1]);
			}
			cout<<ans/2<<endl;
		}else if(op==3){
			int l,r;
			cin>>l>>r;

			ll ans=0,sum=0;
			Data res=Query(1,l,r);
			for(int i=0;i<26;i++){
				sum+=res[i][0];
				ans+=(co[sum]-co[sum-res[i][0]])-(res[i][2]-res[i][1]);
			}
			cout<<ans/2<<endl;

			Tag x;
			for(int i=0;i<26;i++) x[i]=res[i][0];
			Assign(1,l,r,x);
		}
	}
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

G. Traffic Lights

没道理过的比 C 多啊。

考虑建出点分树,维护所有红色/绿色节点到分治中心的距离,每层找到一个和询问节点不在一个子树里的最近的待定的红色/绿色节点,共计分别找到 \(O(\log n)\) 个红色/绿色节点。枚举取到的节点分别是哪两个,并判断合法性。单次时间复杂度 \(O(\log^2 n)\),维护成本同样每次 \(O(\log^2 n)\),总时间复杂度 \(O((n+q)\log^2 n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
const int N=2e5+9;
const int lgN=2e1;
const int inf=1e9+7;

char a[N];
vector<int> e[N];
int n,q;

template<class T> struct DelHeap{
	priority_queue<T> p,q;
	inline void Insert(T x){p.push(x);}
	inline void Erase(T x){q.push(x);}
	inline int Size(){return p.size()-q.size();}
	inline T Top(){
		while(q.size()&&p.top()==q.top()) assert(p.size()),p.pop(),q.pop();
		return p.top();
	}
	inline void Clear(){
		while(p.size()) p.pop();
		while(q.size()) q.pop();
	}
};
DelHeap<pair<int,int>> r[lgN][N],g[lgN][N],R[N],G[N];

int fa[N],dep[N],elr[N<<1],pos[N],ecnt;
inline void GetElr(int x){
	elr[++ecnt]=x;
	pos[x]=ecnt;
	for(int y:e[x]){
		if(y==fa[x]) continue ;
		fa[y]=x;
		dep[y]=dep[x]+1;
		GetElr(y);
		elr[++ecnt]=x;
	}
}
int mn[lgN][N<<1];
inline void InitLCA(){
	for(int i=1;i<=ecnt;i++) mn[0][i]=pos[elr[i]];
	for(int k=1;k<=__lg(ecnt);k++){
		for(int i=1;i+(1<<k)-1<=ecnt;i++){
			mn[k][i]=min(mn[k-1][i],mn[k-1][i+(1<<k-1)]);
		}
	}
}
inline int LCA(int x,int y){
	x=pos[x],y=pos[y];
	if(x>y) swap(x,y);
	int k=__lg(y-x+1);
	return elr[min(mn[k][x],mn[k][y-(1<<k)+1])];
}
inline int Dist(int x,int y){return dep[x]+dep[y]-2*dep[LCA(x,y)];}

int vis[N],siz[N];
inline void GetGrv(int x,int fa,int tot,int &grv){
	bool flag=1;
	siz[x]=1;
	for(int y:e[x]){
		if(y==fa) continue ;
		if(vis[y]) continue ;
		GetGrv(y,x,tot,grv);
		siz[x]+=siz[y];
		flag&=(siz[y]<=tot/2);
	}
	flag&=(tot-siz[x]<=tot/2);
	if(flag) grv=x;
}
int col[lgN][N],ud[N],up[N];
inline void GetNode(int x,int fa,vector<int> &v){
	v.push_back(x);
	for(int y:e[x]){
		if(y==fa) continue ;
		if(vis[y]) continue ;
		GetNode(y,x,v);
	}
}
inline void Color(int x,int fa,int k,int dep){
	col[dep][x]=k;
	for(int y:e[x]){
		if(y==fa) continue ;
		if(vis[y]) continue ;
		Color(y,x,k,dep);
	}
}
inline void Conquer(int x,int tot,int fa){
	GetGrv(x,-1,tot,x);
	GetGrv(x,-1,tot,x);

	vis[x]=1;
	up[x]=fa;
	ud[x]=ud[fa]+1;
	for(int y:e[x]){
		if(vis[y]) continue ;
		Color(y,x,y,ud[x]);
	}
	col[ud[x]][x]=x;

	vector<int> node;
	GetNode(x,-1,node);
	for(int u:node){
		if(a[u]=='R') r[ud[x]][col[ud[x]][u]].Insert({-Dist(u,x),u});
		if(a[u]=='G') g[ud[x]][col[ud[x]][u]].Insert({-Dist(u,x),u});
	}
	for(int y:e[x]){
		if(vis[y]) continue ;
		if(r[ud[x]][y].Size()) R[x].Insert(r[ud[x]][y].Top());
		if(g[ud[x]][y].Size()) G[x].Insert(g[ud[x]][y].Top());
	}
	if(r[ud[x]][x].Size()) R[x].Insert(r[ud[x]][x].Top());
	if(g[ud[x]][x].Size()) G[x].Insert(g[ud[x]][x].Top());

	node.clear();
	node.shrink_to_fit();

	for(int y:e[x]){
		if(vis[y]) continue ;
		Conquer(y,siz[y],x);
	}
}
inline void Modify(int u,char k){
	int x=u;
	while(x){
		int v=col[ud[x]][u];

		if(r[ud[x]][v].Size()) R[x].Erase(r[ud[x]][v].Top());
		if(g[ud[x]][v].Size()) G[x].Erase(g[ud[x]][v].Top());

		int L=Dist(u,x);
		if(a[u]=='R') r[ud[x]][col[ud[x]][u]].Erase({-L,u});
		if(a[u]=='G') g[ud[x]][col[ud[x]][u]].Erase({-L,u});
		if(k=='R') r[ud[x]][col[ud[x]][u]].Insert({-L,u});
		if(k=='G') g[ud[x]][col[ud[x]][u]].Insert({-L,u});

		if(r[ud[x]][v].Size()) R[x].Insert(r[ud[x]][v].Top());
		if(g[ud[x]][v].Size()) G[x].Insert(g[ud[x]][v].Top());

		x=up[x];
	}
	a[u]=k;
}
inline int Query(int u){
	int x=u;
	vector<pair<int,int>> Rs,Gs;
	while(x){
		int v=col[ud[x]][u];

		int L=Dist(u,x);
		vector<pair<int,int>> tR,tG;
		for(int i=0;i<2&&R[x].Size();i++) tR.push_back(R[x].Top()),R[x].Erase(R[x].Top());
		for(int i=0;i<2&&G[x].Size();i++) tG.push_back(G[x].Top()),G[x].Erase(G[x].Top());
		for(auto tmp:tR) R[x].Insert(tmp),Rs.push_back({tmp.second,L+(-tmp.first)});
		for(auto tmp:tG) G[x].Insert(tmp),Gs.push_back({tmp.second,L+(-tmp.first)});

		x=up[x];
	}

	int ans=inf;
	for(auto p:Rs){
		for(auto q:Gs){
			int x=p.first,y=q.first;
			if(LCA(x,u)!=u&&LCA(y,u)!=u) continue ;
			if(dep[LCA(x,y)]>dep[u]) continue ;
			ans=min(ans,p.second+q.second);
		}
	}

	return ans<inf?ans:-1;
}

inline void Solve(){
	cin>>n>>q;
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	for(int i=1;i<=n;i++) cin>>a[i];

	GetElr(1),InitLCA();
	Conquer(1,n,0);
	
	while(q--){
		int op;
		cin>>op;
		if(op==1){
			int v;
			char c;
			cin>>v>>c;
			Modify(v,c);
		}else if(op==2){
			int v;
			cin>>v;
			cout<<Query(v)<<endl;
		}
	}

	for(int i=1;i<=n;i++){
		for(int j=1;j<=ud[i];j++){
			col[j][i]=0;
			r[j][i].Clear();
			g[j][i].Clear();
		}
		R[i].Clear();
		G[i].Clear();
		e[i].clear();
		vis[i]=0;
	}
	ecnt=0;
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

I. Wooden Checker

队友的题,也许是好题。

维护每个连通块对应的区间,那么加入 \(u\rightarrow v\) 边时,合法当且仅当 \(L_v-1\)\(u\) 子树内或 \(R_v+1\)\(u\) 子树内。考虑另外维护每个连通块中 \(L,R\) 的到根链,每次嗯跳,看有没有经过 \(u\),由于每次跳边数减一,而一次合并总边数至多加一,因此这是均摊线性的。

Extra Stage 4: Chongqing

加起来整个队总共写了 4 个数据结构题,无敌了。

A. New Gomoku

签到题,模拟题意即可。

#include<bits/stdc++.h>

using namespace std;

const int N=1e4+9;
const int M=1e3+9;

const int dx[4]={1,0,1,1};
const int dy[4]={0,1,1,-1};
int a[N][N],x[N],y[N],n;

inline void Solve(){
	cin>>n;
	int cnt[3]={0,0,0};
	for(int i=1;i<=n;i++){
		cin>>x[i]>>y[i],x[i]+=10,y[i]+=10;
		a[x[i]][y[i]]=(i&1)+1;
		for(int p:{0,1,2,3}){
			for(int j=-4;j<=0;j++){
				bool flag=1;
				for(int k=0;k<5;k++){
					flag&=(a[x[i]+dx[p]*(j+k)][y[i]+dy[p]*(j+k)]==a[x[i]][y[i]]);
				}
				cnt[a[x[i]][y[i]]]+=flag;
			}
		}
		cout<<cnt[a[x[i]][y[i]]]<<' ';
	}
	cout<<endl;
	for(int i=1;i<=n;i++) a[x[i]][y[i]]=0;
}

signed main(){
	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

B. Abstract Portal

由于每次只有计数器变为 \(0\) 的时候才向前移动,因此向前移动的时候前面的计数器相当于在初始状态,设 \(f_n\) 表示走到 \(n\) 所需的步数,那么有转移 \(\displaystyle f_n=f_{n-1}+\sum_{i=1}^{k-1}[n-t_i-1\geq 0](f_{n-1}-f_{n-t_i-1}+1)\)。考虑先 DP 前 \(\max t\) 项,后面再矩阵快速幂,时间复杂度 \(O((\max t)^3\log n)\)

#include<bits/stdc++.h>

using namespace std;

using ll=long long;
const int B=1e2+1;
const int N=1e3+9;
const int mod=1e9+7;

inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int QPow(int x,int y){
	int res=1;
	while(y){
		if(y&1) MulAs(res,x);
		MulAs(x,x);
		y>>=1;
	}
	return res;
}
inline int Inv(int x){return QPow(x,mod-2);}

struct Matrix{
	vector<vector<int>> A;
	inline void Init(int n,int m){
		A.resize(n);
		for(auto &v:A) v.resize(m,0);
	}
	Matrix(){}
	Matrix(int n,int m){Init(n,m);}
	inline vector<int>& operator [](int x){return A[x];}
	friend inline Matrix operator *(Matrix A,Matrix B){
		int n=A.A.size(),m=A[0].size(),k=B[0].size();
		Matrix C(n,k);
		for(int i=0;i<n;i++){
			for(int p=0;p<m;p++){
				for(int j=0;j<k;j++){
					AddAs(C[i][j],Mul(A[i][p],B[p][j]));
				}
			}
		}
		return C;
	}
};
inline Matrix QPow(Matrix A,int k){
	int n=A[0].size();
	Matrix B(n,n);
	for(int i=0;i<n;i++) B[i][i]=1;
	while(k){
		if(k&1) B=A*B;
		A=A*A;
		k>>=1;
	}
	return B;
}

int f[N],t[N],n,k;

signed main(){
	cin>>n>>k;
	for(int i=1;i<k;i++) cin>>t[i];

	f[0]=0;
	for(int i=1;i<=min(n,B);i++){
		f[i]=Add(f[i-1],1);
		for(int j=1;j<k;j++){
			if(i-t[j]-1<0) continue ;
			AddAs(f[i],Add(Sub(f[i-1],f[i-t[j]-1]),1));
		}
	}

	if(n<=B){
		cout<<f[n]<<endl;
		return 0;
	}

	Matrix A(B+1,B+1),X(B+1,1);
	AddAs(A[0][0],1),AddAs(A[0][B],1);
	for(int j=1;j<k;j++) AddAs(A[0][0],1),SubAs(A[0][t[j]],1),AddAs(A[0][B],1);
	for(int j=1;j<B;j++) AddAs(A[j][j-1],1);
	AddAs(A[B][B],1);

	for(int j=0;j<B;j++) X[j][0]=f[B-j];
	X[B][0]=1;

	Matrix Y=QPow(A,n-B)*X;
    
	cout<<Y[0][0]<<endl;

	return 0;
}

C. GPA Optimization Plan

凸凸凸凸包包包包啊啊啊啊。

经典地,分数规划,设答案为 \(k\),则有 \(\displaystyle \sum_{i=1}^n s_ig_ix_i-k\sum_{i=1}^n s_ix_i\geq 0\),其中 \(x_i\)\(0/1\) 变量,代表 \(i\) 是否计入答案。考虑最小化 \(\displaystyle \sum_{i=1}^n(1-x_i)(s_ig_i-ks_i)\),由于 \(c_i\) 相同的至多只有一个 \(x_i=0\),因此考虑对每个 \(c_i\) 维护凸包,点 \(i\) 坐标为 \((s_i,s_ig_i)\),则被 \(y=kx+b\) 切到的点就是要删的点(为了方便可以加入点 \((0,0)\))。动态维护斜率并在对斜率建的线段树上记录权值即可做到 \(O(n(\log n+\log V))\),和 \(m\) 无关,其中 \(V=\dfrac {n^2}{\epsilon}\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
using ldb=long double;
const ldb eps=1e-8;
const ldb lim=1e10;
const int N=1e5+9;
const int M=9;

struct Node{
	int lc,rc;
	ll s,g;
}tr[N<<7];

int cnt,root;
inline int Allc(){return ++cnt;}
inline void PushUp(int x){
	tr[x].s=tr[tr[x].lc].s+tr[tr[x].rc].s;
	tr[x].g=tr[tr[x].lc].g+tr[tr[x].rc].g;
}
inline void Modify(int &x,ldb L,ldb R,ldb pos,ll s,ll g){
	if(!x) x=Allc();
	if(abs(R-L)<eps){
		tr[x].s+=s;
		tr[x].g+=g;
		return ;
	}
	ldb mid=(L+R)/2;
	if(pos<mid) Modify(tr[x].lc,L,mid,pos,s,g);
	else Modify(tr[x].rc,mid,R,pos,s,g);
	PushUp(x);
}
inline void Modify(ldb L,ldb R,ll s,ll g){
	Modify(root,0,lim,L,s,g);
	Modify(root,0,lim,R,-s,-g);
}
inline ldb Try(ll x,ll y){return y?ldb(x)/y:lim;}
inline ldb Find(int x,ldb L,ldb R,ll s,ll g){
	if(!x||abs(R-L)<eps) return Try(g,s); 
	ldb mid=(L+R)/2;
	ll _s=s+tr[tr[x].lc].s;
	ll _g=g+tr[tr[x].lc].g;
	if(Try(_g,_s)>=mid) return Find(tr[x].rc,mid,R,_s,_g);
	else return Find(tr[x].lc,L,mid,s,g);
}
inline void ClearTree(){
	memset(tr,0,(sizeof Node())*(cnt+1));
	cnt=root=0;
}

ll s[N],g[N];
int t[N],n,m;

struct Convex{
	multiset<array<ll,2>> s;
	using Iter=set<array<ll,2>>::iterator;
	inline void Init(){s.insert({0,0});}
	inline ldb Slope(Iter it){
		if(it==s.begin()) return 0;
		else if(it==s.end()) return lim;
		else{
			auto jt=prev(it);
			if((*jt)[0]==(*it)[0]) return lim;
			else return ldb((*it)[1]-(*jt)[1])/((*it)[0]-(*jt)[0]);
		}
	}
	map<ldb,ll> S,G;
	inline void Update(Iter it,ll w){
		if(it==s.end()) return ;
		S[Slope(it)]+=(*it)[0]*w,G[Slope(it)]+=(*it)[1]*w;
		S[Slope(next(it))]+=(*it)[0]*-w,G[Slope(next(it))]+=(*it)[1]*-w;
	}
	inline void Buff(){
		for(auto p:S){
			Modify(root,0,lim,p.first,S[p.first],G[p.first]);
		}
		S.clear(),G.clear();
	}
	inline bool Check(Iter it){
		if(it==s.begin()||it==s.end()) return 1;
		if(next(it)==s.end()) return 1;
		auto jt=prev(it),kt=next(it);
		return ((*it)[1]-(*jt)[1])*((*kt)[0]-(*jt)[0])<((*kt)[1]-(*jt)[1])*((*it)[0]-(*jt)[0]);
	}
	inline Iter Insert(ll x,ll y){
		auto kt=s.upper_bound({x,y}),it=prev(kt);
		Update(it,1),Update(kt,1);
		auto jt=s.insert(next(it),{x,y});
		Update(it,-1),Update(jt,-1),Update(kt,-1);
		Buff();
		return jt;
	}
	inline void Erase(Iter jt){
		auto it=prev(jt),kt=next(jt);
		Update(it,1),Update(jt,1),Update(kt,1);
		s.erase(jt);
		Update(it,-1),Update(kt,-1);
		Buff();
	}
	inline void Join(ll x,ll y){
		auto it=Insert(x,y);
		if(!Check(it)) return Erase(it);
		while(!Check(prev(it))) Erase(prev(it));
		while(!Check(next(it))) Erase(next(it));
	}
	inline void Clear(){s.clear();}
}c[M];

inline void Solve(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>s[i]>>g[i]>>t[i],g[i]*=s[i];

	ll S=0,G=0;
	for(int i=1;i<=m;i++) c[i].Init();
	for(int i=1;i<=n;i++){
		S+=s[i],G+=g[i];
		c[t[i]].Join(s[i],g[i]);
		if(i>=2*m) cout<<Find(root,0,lim,S,G)<<endl;
	}

	for(int i=1;i<=m;i++) c[i].Clear();
	ClearTree();
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);
	cout<<fixed<<setprecision(10);
	cerr<<fixed<<setprecision(10);

	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

F. Turtle Soup

角盒角盒角盒角盒角盒。

先判 \(p\) 是否认识 \(m\)。如果是,因为 \(p\) 不可能是 \(fa_m\),因此 \(p\) 只能在 \(m\) 子树内,设 \(f_u\) 表示搜完 \(u\) 子树内最多需要的步数,有 \(\displaystyle f_u=\max_{i=0} (f_{son_{u,i}}+2i+1)\),其中 \(son_{u,i}\) 表示 \(u\) 儿子中 \(f\)\(i\) 大的编号,叶子的 \(f\) 值显然是 \(0\)。同时由于 \(m\) 一定不认识 \(p\),因此这部分对答案的贡献是 \(\max_{i=0} [son_{m,i} ~ \mathrm{is ~ not ~ leaf}](f_{son_{m,i}}+2i+1)\)

如果 \(p\) 不认识 \(m\),那么向上找到 \(p\)\(m\) 的最近公共祖先 \(q\),此时 \(p\) 一定认识 \(q\),那么再从 \(q\) 开始向下找 \(p\)。设 \(m\)\(q\)\(k\) 子树方向,则对应的贡献是 \(\displaystyle \operatorname{dist}(m,q)+\max_{i=0} [f_{son'_{u,i}}+2i+1]\),其中 \(son'\)\(son\) 除去 \(k\) 的结果,除去这一操作可以简单地通过线段树或者维护前后缀信息完成。

用两次 DFS(即非显式的换根 DP)即可计算上述所有信息,时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>

using namespace std;

const int N=2e5+9;
const int inf=1e9+7;

struct Data{
	int val,siz;
	Data(){}
	Data(int _val,int _siz){val=_val,siz=_siz;}
	inline void Assign(int v,int s){val=s?2*(s-1)+v:-inf,siz=s;}
	friend inline Data operator +(Data x,Data y){
		return Data(max(x.val+2*y.siz,y.val),x.siz+y.siz);
	}
};
struct Node{
	int l,r;
	Data dat;
}tr[N<<3];

inline void PushUp(int x){tr[x].dat=tr[x<<1].dat+tr[x<<1|1].dat;}
inline void Build(int x,int l,int r){
	tr[x].l=l,tr[x].r=r;
	if(tr[x].l==tr[x].r) return tr[x].dat=Data(-inf,0),void();
	int mid=tr[x].l+tr[x].r>>1;
	Build(x<<1,l,mid),Build(x<<1|1,mid+1,r);
	PushUp(x);
}
inline void Modify(int x,int pos,int k){
	if(tr[x].l==tr[x].r) return tr[x].dat.Assign(pos,tr[x].dat.siz+k);
	int mid=tr[x].l+tr[x].r>>1;
	if(pos<=mid) Modify(x<<1,pos,k);
	else Modify(x<<1|1,pos,k);
	PushUp(x);
}

vector<int> e[N];
int dep[N],f[N],ans[N],fa[N],n;
inline void DFS1(int x){
	vector<int> tmp,tmp2;
	for(int y:e[x]){
		fa[y]=x;
		dep[y]=dep[x]+1;
		DFS1(y);
		tmp.push_back(f[y]);
		if(e[y].size()) tmp2.push_back(f[y]);
	}
	sort(tmp.begin(),tmp.end(),greater<int>());
	for(int i=0;i<tmp.size();i++) f[x]=max(f[x],tmp[i]+2*i+1);
	sort(tmp2.begin(),tmp2.end(),greater<int>());
	for(int i=0;i<tmp2.size();i++) ans[x]=max(ans[x],tmp2[i]+2*i+1);
}
int t[N];
inline void DFS2(int x){
	ans[x]=max(ans[x],t[x]);
	for(int y:e[x]) Modify(1,f[y]+1,1);
	for(int y:e[x]){
		Modify(1,f[y]+1,-1);
		t[y]=max(t[y],t[x]+1);
		if(tr[1].dat.siz) t[y]=max(t[y],tr[1].dat.val+1);
		Modify(1,f[y]+1,1);
	}
	for(int y:e[x]) Modify(1,f[y]+1,-1);
	for(int y:e[x]) DFS2(y);
}

signed main(){
	cin>>n;
	for(int i=2,f;i<=n;i++) cin>>f,e[f].push_back(i);

	for(int i=1;i<=n;i++) t[i]=-inf;
	Build(1,0,2*n+1);
	dep[1]=1,DFS1(1);
	DFS2(1);
	for(int i=1;i<=n;i++) if(dep[i]+e[i].size()==n) ans[i]=0;

	for(int i=1;i<=n;i++) cout<<ans[i]<<' ';cout<<endl;

	return 0;
}

J. Tetris

胡了一下,队友写的。

首先两个相邻的相同颜色的位置会对合法的 \(x\)\(\mathbb{Z}_n\) 的环上做出一段限制,要求 \(x\) 在某个环上的区间内。把一个限制看成一个加标记,那么合法位置标记数量一定是全局最大值,因此直接线段树维护最大值以及所有最大值的权值异或和即可,时间复杂度 \(O(n\log n)\)

Stage 18: Grand Prix of Hongō

C. Convex Crusher

容易想到基本形式应该是一条直线再加上一条跨过直线两边但是不与直线上任意线段相交的线段。

  o
- + - o - o - o - o - o - o -
  o

有角盒六个点三条三点共线,特判即可。

          |
          o
    \     |
- o - o - o -
        \ |
          o
          | \
              o
                \
#include<bits/stdc++.h>

using namespace std;

using ll=long long;
const int N=5e2+9;

struct Point{
	int x,y;
	Point(){}
	Point(int _x,int _y){x=_x,y=_y;}
	friend inline Point operator +(Point p,Point q){return Point(p.x+q.x,p.y+q.y);}
	friend inline Point operator -(Point p,Point q){return Point(p.x-q.x,p.y-q.y);}
};

inline ll Cross(Point p,Point q){return 1ll*p.x*q.y-1ll*p.y*q.x;}

Point p[N];
int f[N][N],cvx[N],n;
inline bool Cmp(int i,int j){
	if(p[i].x!=p[j].x) return p[i].x<p[j].x;
	else return p[i].y<p[j].y;
}
inline bool OneLine(int i,int j,int k){
	if(i==j||j==k||k==i) return 0;
	if(p[i].x!=p[j].x) return 1^((p[i].x<p[j].x)^(p[j].x<p[k].x));
	else return 1^((p[i].y<p[j].y)^(p[j].y<p[k].y));
}

signed main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>p[i].x>>p[i].y/* ,cout<<'('<<p[i].x<<','<<p[i].y<<')'<<endl */;

	vector<int> ord(n,0);
	iota(ord.begin(),ord.end(),1);
	sort(ord.begin(),ord.end(),Cmp);

	vector<int> stk;
	for(int i:ord){
		while(stk.size()>1&&Cross(p[stk.end()[-1]]-p[stk.end()[-2]],p[i]-p[stk.end()[-2]])>=0) stk.pop_back();
		stk.push_back(i);
	}
	for(int x:stk) cvx[x]=1;
	stk.clear();
	for(int i:ord){
		while(stk.size()>1&&Cross(p[stk.end()[-1]]-p[stk.end()[-2]],p[i]-p[stk.end()[-2]])<=0) stk.pop_back();
		stk.push_back(i);
	}
	for(int x:stk) cvx[x]=1;

	int ans=0;
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++){
			int cnt=0,a=0,b=0;
			for(int k=1;k<=n;k++){
				if(Cross(p[k]-p[i],p[j]-p[i])) continue ;
				if(OneLine(i,j,k)) f[i][j]=1;
				if(OneLine(j,i,k)) f[j][i]=1;
				cnt++;
				if(!a||Cmp(k,a)) a=k;
				if(!b||Cmp(b,k)) b=k;
			}
			
			if(cvx[a]&&cvx[b]) ans=max(ans,min(cnt+1,n));
			else ans=max(ans,cnt+2);
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			for(int k=1;k<=n;k++){
				if(f[i][j]&&f[j][k]&&f[k][i]) ans=max(ans,6);
			}
		}
	}

	cout<<ans<<endl;

	return 0;
}

G. Greatest Bracket Sequence

首先区间和是 \(0\) 肯定输出 \(-1\),找到前缀和最小值位置开始循环即可。

手玩一下剩下的情况至多循环两次,即前缀加后缀或者就是一个子串。

若答案由一个后缀和一个前缀拼接而成,设 \(s_i\) 表示前缀和,令 \(p\)\(s[l:r]\) 最小值位置,那么后缀和最大值即为 \(s_r-s_p\),前缀和最小值即为 \(s_p-s_{l-1}\)。由于 \(s\) 值域变化连续,因此答案串的深度最大值即为 \(d=\min(s_r-s_p,-(s_p-s_{l-1}))\),二分找到作为前缀/后缀最小值且最深的 \(s_{l-1}-d\)\(s_r-d\) 即可。

若答案本身在原串中就是一个连续子串,设为 \(s[l',r']\),那么除去 \(s_{l'-1}=s_{r'}=s_p\) 的情况,要么 \(l'\) 向左无法找到一个 \(s_i=s_{l'-1}\)\(s[i:l'-1]\) 最小值不为 \(s_i\)\(i\),要么 \(r'\) 向右无法找到一个 \(s_i=s_{r'}\)\(s[i:r]\) 最小值不为 \(s_i\)\(i\)。这样的 \(s[l',r']\) 至多只有 \(2n\) 个,找出来后再二维数点即可。

时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
const int N=2e5+9;
const int lgN=2e1;

char c[N];
int s[N],mn[lgN][N],ql[N],qr[N],ans[N],n,q;
inline void Init(){
	for(int i=0;i<=n;i++) mn[0][i]=s[i];
	for(int k=1;k<=__lg(n);k++){
		for(int i=0;i+(1<<k)-1<=n;i++){
			mn[k][i]=min(mn[k-1][i],mn[k-1][i+(1<<k-1)]);
		}
	}
}
inline int MinS(int l,int r){
	int k=__lg(r-l+1);
	return min(mn[k][l],mn[k][r-(1<<k)+1]);
}
inline int FindL(int p,int llim,int x){
	int l=llim-1,r=p+1;
	while(l+1<r){
		int mid=l+r>>1;
		if(MinS(mid,p)>=x) r=mid;
		else l=mid;
	}
	int L=l,R=p+1;
	while(L+1<R){
		int mid=L+R>>1;
		if(MinS(r,mid)>x) L=mid;
		else R=mid;
	}
	return R;
}
inline int FindR(int p,int rlim,int x){
	int l=p-1,r=rlim+1;
	while(l+1<r){
		int mid=l+r>>1;
		if(MinS(p,mid)>=x) l=mid;
		else r=mid;
	}
	int L=p-1,R=r;
	while(L+1<R){
		int mid=L+R>>1;
		if(MinS(mid,l)>x) R=mid;
		else L=mid;
	}
	return L;
}

int tr[N];
vector<int> rp[N],qry[N];
inline void Insert(int x,int k){x++;while(x<=n+1) tr[x]=max(tr[x],k),x+=x&-x;}
inline int Query(int x){int mx=0;x++;while(x) mx=max(mx,tr[x]),x&=x-1;return mx;}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	cin>>n>>q;
	for(int i=1;i<=n;i++) cin>>c[i];
	for(int i=1;i<=q;i++) cin>>ql[i]>>qr[i];

	for(int i=1;i<=n;i++) s[i]=s[i-1]+(c[i]=='('?1:-1);

	Init();
	for(int i=1;i<=q;i++){
		if(s[qr[i]]-s[ql[i]-1]==0){
			ans[i]=-1;
			continue ;
		}
		qry[ql[i]-1].push_back(i);
		int x=MinS(ql[i]-1,qr[i]);
		ans[i]=max(ans[i],FindR(ql[i]-1,qr[i],x)-FindL(qr[i],ql[i]-1,x));
		int p=s[qr[i]]-x,q=x-s[ql[i]-1];
		q=-(p=min(p,-q));
		ans[i]=max(ans[i],(qr[i]-FindL(qr[i],ql[i]-1,s[qr[i]]-p))+(FindR(ql[i]-1,qr[i],s[ql[i]-1]+q)-ql[i]+1));
	}
	for(int i=0;i<=n;i++){
		rp[FindL(i,0,s[i])].push_back(i);
		rp[i].push_back(FindR(i,n,s[i]));
	}
	for(int i=n;i>=0;i--){
		for(int j:rp[i]) Insert(j,j-i);
		for(int j:qry[i]) ans[j]=max(ans[j],Query(qr[j]));
	}

	for(int i=1;i<=q;i++) cout<<ans[i]<<endl;

	return 0;
}

N. Numerical Error

有结论,\(n=22\) 时必然有解。

设密度函数 \(\rho(n)=\dfrac{\binom n{\lfloor\frac n2\rfloor}\epsilon}{H(n)}\),有 \(\rho(22)\approx 1.91131859613>1\),也就是说,根据鸽巢原理,\(n=22\) 时至少有一对解间距小于 \(\epsilon\),且 \(|X|=|Y|=\frac 12 n\)

因此只要 \(n\leftarrow \min(n,22)\),折半搜索即可做到 \(O(3^{\frac 12\log n})\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ldb=long double;
const int D=50;
const int N=1e3+9;
const ldb eps=(1e-5);

int a[N],b[N],n,m;
inline void DFS(int i,ldb cur,int s,vector<pair<ldb,int>> &v){
	if(i==m) return v.push_back({cur,s});
	ldb tmp=ldb(1)/b[i];
	DFS(i+1,cur,s,v);
	DFS(i+1,cur-tmp,s|(1<<2*i),v);
	DFS(i+1,cur+tmp,s|(1<<2*i+1),v);
}
inline int Bit(int c){
	int res=0;
	while(c){
		if(c&1) res--;
		else if(c&2) res++;
		c>>=2;
	}
	return res;
}

int l[D<<1],r[D<<1];
vector<int> X,Y;
inline void Yes(){
	cout<<"Yes"<<endl;
	cout<<X.size()<<endl;
	for(int x:X) cout<<x<<' ';cout<<endl;
	for(int x:Y) cout<<x<<' ';cout<<endl;

	ldb A=0,B=0;
	for(int x:X) A+=ldb(1)/a[x];
	for(int x:Y) B+=ldb(1)/a[x];

	assert(X.size()==Y.size());
	assert(abs(A-B)<=eps);
}
inline void No(){
	cout<<"No"<<endl;
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];

	n=min(n,26);
	vector<pair<ldb,int>> L,R;

	int h=n/2;
	for(int i=1;i<=h;i++) b[i-1]=a[i];
	m=h,DFS(0,0,0,L);
	for(int i=h+1;i<=n;i++) b[i-h-1]=a[i];
	m=n-h,DFS(0,0,0,R);
	for(auto &p:R) p.first=-p.first;

	sort(L.begin(),L.end()),sort(R.begin(),R.end());

	int p=0,q=0;
	auto MoveL=[&](){
		int c=Bit(L[p].second);
		if(!c&&L[p].second&&abs(L[p].first)<=eps){
			int s=L[p].second;
			for(int i=0;i<h;i++){
				if(s>>2*i&1) X.push_back(i+1);
				else if(s>>2*i&2) Y.push_back(i+1);
			}
			return 1;
		}
		if(r[-c+D]&&L[p].first-R[r[-c+D]].first<=eps){
			int s=L[p].second,t=R[r[-c+D]].second;
			for(int i=0;i<h;i++){
				if(s>>2*i&1) X.push_back(i+1);
				else if(s>>2*i&2) Y.push_back(i+1);
			}
			for(int i=0;i<n-h;i++){
				if(t>>2*i&1) X.push_back(i+h+1);
				else if(t>>2*i&2) Y.push_back(i+h+1);
			}
			return 1;
		}
		if(L[p].second) l[c+D]=p;
		p++;
		return 0;
	};
	auto MoveR=[&](){
		int c=Bit(R[q].second);
		if(!c&&R[q].second&&abs(R[q].first)<=eps){
			int t=R[q].second;
			for(int i=0;i<n-h;i++){
				if(t>>2*i&1) X.push_back(i+h+1);
				else if(t>>2*i&2) Y.push_back(i+h+1);
			}
			return 1;
		}
		if(l[-c+D]&&R[q].first-L[l[-c+D]].first<=eps){
			int s=L[l[-c+D]].second,t=R[q].second;
			for(int i=0;i<h;i++){
				if(s>>2*i&1) X.push_back(i+1);
				else if(s>>2*i&2) Y.push_back(i+1);
			}
			for(int i=0;i<n-h;i++){
				if(t>>2*i&1) X.push_back(i+h+1);
				else if(t>>2*i&2) Y.push_back(i+h+1);
			}
			return 1;
		}
		if(R[q].second) r[c+D]=q;
		q++;
		return 0;
	};
	while(p<L.size()&&q<R.size()){
		if(L[p].first<R[q].first){
			if(MoveL()) return Yes(),0;
		}else{
			if(MoveR()) return Yes(),0;
		}
	}
	while(p<L.size()) if(MoveL()) return Yes(),0;
	while(q<R.size()) if(MoveR()) return Yes(),0;
	
	No();

	return 0;
}

Stage 19: Grand Prix of Belarus

出于某些原因,后面自己又以补题的形式单刷了一遍。

B. Building a Reactor

考虑记录轮廓线上每个格子的状态,即障碍或已匹配的燃料棒/未匹配的燃料棒/冷却池,转移需要一点讨论,时间复杂度 \(O(nm3^n)\)

#include<bits/stdc++.h>

using namespace std;

const int N=12+9;
const int M=30+9;
const int S=531441+9;

char a[N][M],b[N][M];
short f[M][N][S];
int p[N],n,m;
inline void Init(){
	p[0]=1;
	for(int i=1;i<=n;i++) p[i]=p[i-1]*3;
}
inline int Get(int x,int i){return x/p[i]%3;}
inline void ChMax(short &x,short y){if(y>x) x=y;}
inline string ToString(int s){
	string x(n,'0');
	for(int i=0;i<n;i++) x[i]+=Get(s,i);
	return x;
}

signed main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++) cin>>a[i][j];
	}

	Init();
	memset(f,-0x3f,sizeof f);
	f[0][n][0]=0;
	for(int i=1;i<=m;i++){
		for(int j=1;j<=n;j++){
			short *g=j>1?f[i][j-1]:f[i-1][n],*h=f[i][j];
			for(int s=0;s<p[n];s++){
				if(g[s]<0) continue ;
				int _s=s*3%p[n],x=Get(s,n-1),y=Get(s,0);
				if(a[j][i]=='#'){
					if(x!=1) ChMax(h[_s],g[s]);
				}else{
					ChMax(h[j==1||y!=1?_s+2:_s-1],g[s]);
					if(x!=1){
						if(x!=2&&(y!=2||j==1)) ChMax(h[_s+1],g[s]+1);
						else ChMax(h[_s],g[s]+1);
					}
				}
			}
		}
	}

	int cur=0;
	short ans=-1;
	for(int s=0;s<p[n];s++){
		bool flag=0;
		for(int i=0;i<n;i++) flag|=(Get(s,i)==1);
		if(!flag){
			if(f[m][n][s]>ans){
				ans=f[m][n][s];
				cur=s;
			}
		}
	}

	for(int i=m;i>=1;i--){
		for(int j=n;j>=1;j--){
			bool flag=0;
			short *g=j>1?f[i][j-1]:f[i-1][n],*h=f[i][j];
			auto Try=[&](int s,int t,int x,char c){
				if(!flag&&s==cur&&x==ans){
					cur=t;
					ans=g[t];
					b[j][i]=c;
					flag=1;
				}
			};
			for(int s=0;s<p[n]&&!flag;s++){
				if(g[s]<0) continue ;
				int _s=s*3%p[n],x=Get(s,n-1),y=Get(s,0);
				if(a[j][i]=='#'){
					if(x!=1) Try(_s,s,g[s],'#');
				}else{
					Try(j==1||y!=1?_s+2:_s-1,s,g[s],'1');
					if(x!=1){
						if(x!=2&&(y!=2||j==1)) Try(_s+1,s,g[s]+1,'0');
						else Try(_s,s,g[s]+1,'0');
					}
				}
			}
		}
	}

	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++) cout<<b[i][j];cout<<endl;
	}
	
	return 0;
}

C. Cell Flip Game

考虑如下过程,从下至上扫描,假设 \((i,j)\)\(1\),则对 \((i-1,j)\) 操作。

那么除了第一行以外的全部都可以复原,而现在能影响到第一行的就是最后一行的操作,因为一开始并没有对最后一行执行任何操作。

考虑计算最后一行每种操作对第一行的最终影响,然后高斯消元/线性基,时间复杂度 \(O(nm^2+m^3)\),精细实现可能能做到 \(O(\frac {nm^2+m^3}w)\)

#include<bits/stdc++.h>

using namespace std;

const int N=5e2+9;

bitset<N> s[N],b[N],q[N],res;
int a[N][N],tmp[N][N],ans[N][N],n,m;
inline void Flip(int a[N][N],int ans[N][N],int i,int j){
	for(int x:{-1,0,1}){
		for(int y:{-1,0,1}){
			if(x&&y) continue ;
			if(x+i<1||x+i>n||y+j<1||y+j>m) continue ;
			a[x+i][y+j]^=1;
		}
	}
	ans[i][j]^=1;
}
inline bitset<N> Calc(int a[N][N],int ans[N][N]){
	for(int i=n;i>1;i--){
		for(int j=1;j<=m;j++){
			if(a[i][j]) Flip(a,ans,i-1,j);
		}
	}
	bitset<N> x;
	x.reset();
	for(int i=1;i<=m;i++) x[i]=a[1][i];
	return x;
}
inline void Insert(bitset<N> x,int i){
	bitset<N> y;
	y.reset();
	y[i]=1;
	for(int i=m;i>=1;i--){
		if(!x[i]) continue ;
		if(!b[i].any()){
			b[i]=x;
			q[i]=y;
			break ;
		}else x^=b[i],y^=q[i];
	}
}
inline void GetMin(bitset<N> &x,bitset<N> &p){
	p.reset();
	for(int i=m;i>=1;i--) if(x[i]&&b[i].any()) x^=b[i],p^=q[i];
}

signed main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			char c;
			cin>>c;
			a[i][j]=c-'0';
		}
	}

	memcpy(tmp,a,sizeof tmp);
	res=Calc(tmp,ans);
	for(int i=1;i<=m;i++){
		memset(tmp,0,sizeof tmp);
		Flip(tmp,ans,n,i);
		Insert(s[i]=Calc(tmp,ans),i);
	}

	bitset<N> p;
	GetMin(res,p);
	if(res.any()){
		cout<<"NO"<<endl;
		return 0;
	}else cout<<"YES"<<endl;

	memset(ans,0,sizeof ans);
	for(int i=1;i<=m;i++) if(p[i]) Flip(a,ans,n,i);
	res=Calc(a,ans);

	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++) cout<<ans[i][j];
		cout<<endl;
	}

	return 0;
}

D. Detecting the Missing Ship

首先独立出行和列。随机撒点看起来没什么意义,考虑从左到右扫。假设船是横着的,则判掉列 \(i\) 后,\([i-k,i+k]\) 中就没船了,则船下一步必定不碰到 \((i-k,i+k)\)。因此,逐个查询点 \((pk-1,pk-1),p\in \mathbb{N_+}\) 即可。

#include<bits/stdc++.h>

using namespace std;

signed main(){
	int n,k;
	cin>>n>>k;

	for(int i=k,c=0;i<=n&&!c;i+=k-1){
		cout<<i<<' '<<i<<endl;
		cin>>c;
	}

	return 0;
}

F. Forgot to Refuel

先证明在长度为 \(n\) 的环上分配加油站的期望等于在长度为 \(k\) 的序列上分配的期望。

考虑记录两两加油站之间的间隔,并认为 \(n\rightarrow 1\) 所在的段位序列首项,那么就根据这个分布得到了一个长度为 \(k\) 的序列 \(a\)

根据定义可以得到,有 \(a_1\) 个环上的分布可以通过这种方式得到 \(a\)

那么对序列做循环位移,显然最大值不变,那么循环位移的次数为 \(k\),而环上对应的分布总和为 \(\sum a_i=n\)

如果存在循环节也有类似结论,因此最大值相同时,两者方案数之比衡为 \(\dfrac nk\)。而期望则上下抵消,因此两者期望等价。

而对长度为 \(k\),和为 \(n\),最大值为 \(x\) 的序列计数有经典容斥:\(\displaystyle \sum_{c=1}^{\lfloor \frac nx\rfloor} \binom{k}{c}\binom{n-c(x-1)-1}{k-1}(-1)^{c+1}\)

对所有 \(x\) 计算的时间复杂度为 \(O(n\ln n)\)

#include<bits/stdc++.h>

using namespace std;

using ll=long long;
const int N=1e6+9;
const int mod=998244353;

inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int QPow(int x,int y){
	int res=1;
	while(y){
		if(y&1) MulAs(res,x);
		MulAs(x,x);
		y>>=1;
	}
	return res;
}
inline int Inv(int x){return QPow(x,mod-2);}

int fac[N],ifac[N];
inline void Init(int lim){
	fac[0]=1;
	for(int i=1;i<=lim;i++) fac[i]=Mul(fac[i-1],i);
	ifac[lim]=Inv(fac[lim]);
	for(int i=lim-1;~i;i--) ifac[i]=Mul(ifac[i+1],i+1);
}
inline int C(int n,int m){
	if(m<0||m>n) return 0;
	else return Mul(fac[n],Mul(ifac[m],ifac[n-m]));
}

int n,k;

signed main(){
	cin>>n>>k;

	Init(n);
	int ans=0;
	for(int c=1;c<=k;c++){
		int sum=0;
		for(int i=1;i*c<=n;i++){
			int lft=n-k-(i-1)*c;
			AddAs(sum,C(lft+k-1,k-1));
		}
		MulAs(sum,C(k,c));
		if(c&1) AddAs(ans,sum);
		else SubAs(ans,sum);
	}

	MulAs(ans,Inv(C(n-1,k-1)));

	cout<<ans<<endl;

	return 0;
}

G. Guaranteed Medal

喜欢模拟,喜欢洗数据。

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
const int N=1e5+9;

struct Submission{
	string bel,t,ver;
	char p;
}s[N];
struct Score{
	int cnt,sum,lst;
	Score(){}
	Score(int _cnt,int _sum,int _lst){cnt=_cnt,sum=_sum,lst=_lst;}
	friend inline bool operator >(Score x,Score y){
		if(x.cnt!=y.cnt) return x.cnt>y.cnt;
		else if(x.sum!=y.sum) return x.sum<y.sum;
		else return x.lst<y.lst;
	}
}cur[N];

int t[N][26],pn[N][26],ord[N],n,m,k;
map<string,int> id;
inline int ToVal(string t){
	return (t[0]-'0')*60+(t[2]-'0')*10+(t[3]-'0');
}

inline void Solve(){
	cin>>k>>m;
	for(int i=1;i<=m;i++){
		cin>>s[i].bel>>s[i].p>>s[i].t>>s[i].ver;
		if(!id[s[i].bel]) id[s[i].bel]=++n;
		int x=id[s[i].bel];
		if(!t[x][s[i].p-'A']){
			if(s[i].ver=="OK"){
				t[x][s[i].p-'A']=i;
				cur[x].cnt++;
				cur[x].sum+=ToVal(s[i].t);
				cur[x].sum+=pn[x][s[i].p-'A']*20;
				cur[x].lst=i;
			}else pn[x][s[i].p-'A']++;
		}
	}

	vector<int> ans;
	iota(ord+1,ord+n+1,1);
	sort(ord+1,ord+n+1,[](int i,int j){return cur[i]>cur[j];});
	for(int i=1;i<=12;i++){
		int x=ord[i],l=0,r=m+1;
		auto Calc=[&](int c){
			Score res(0,0,0);
			for(int j=0;j<26;j++){
				if(!t[x][j]) continue ;
				if(c>=t[x][j]){
					res.cnt++;
					res.sum+=ToVal(s[t[x][j]].t);
					res.sum+=pn[x][j]*20;
					res.lst=max(res.lst,t[x][j]);
				}
			}
			return res;
		};
		while(l+1<r){
			int mid=l+r>>1;
			if(Calc(mid)>cur[ord[13]]) r=mid;
			else l=mid;
		}
		ans.push_back(r);
	}
	
	sort(ans.begin(),ans.end());
	for(int x:ans) cout<<s[x].bel<<' '<<s[x].t<<endl;

	for(int i=1;i<=n;i++){
		cur[i]=Score(0,0,0);
		for(int j=0;j<26;j++) t[i][j]=pn[i][j]=0;
	}
	id.clear(),n=0;
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

H. Helpful Bits

由于每条边平均只能传递 12 个 bit,因此如果每条边真的是平均分配信息的话,只能表示出 \(2^{12}=4096\) 个数。

考虑找出一个固定的实数点集 \(S\),使得 \(\forall x\in[1,10^6],\exists y\in S,\dfrac {|x-y|}x\leq \epsilon\)。然后把 \(x\) 表示成 \(\displaystyle \sum_{z\in S} [z\leq y]\) 进行传递,则接收方得到近似值 \(y\),那么根据绝对值不等式可以得到近似答案与实际答案的相对误差不大于 \(\epsilon\)

事实证明,通过简单的贪心算法,可以做到 \(|S|=4092\)

#include<bits/stdc++.h>

using namespace std;

const int N=1e6+9;
const double e=1e-3-1e-8;
vector<double> t({0});
inline void Init(){
	for(int i=1;i<=1e6;i++){
		double k=i*e;
		if(abs(i-t.back())>k) t.push_back(i+k);
	}
	t.push_back(1e6);
}

namespace Run1{
	inline int Find(int x){
		int i=lower_bound(t.begin(),t.end(),x)-t.begin();
		int j=upper_bound(t.begin(),t.end(),x)-t.begin()-1;
		return abs(t[i]-x)<abs(t[j]-x)?i:j;
	}
	inline void Solve(){
		int m;
		cin>>m;
		cout<<m*12<<endl;
		while(m--){
			int x;
			cin>>x;
			x=Find(x);
			for(int i=0;i<12;i++) cout<<(x>>i&1);
		}
		cout<<endl;
	}
}
namespace Run2{
	double dis[N];
	vector<int> e[N],tmp;
	int vis[N],eu[N],ev[N],ew[N],n,m,q,b;
	inline void Dij(int s){
		priority_queue<pair<double,int>> q;
		for(int x:tmp) dis[x]=1e18,vis[x]=0;
		tmp.clear();
		q.push({dis[s]=0,s});
		while(q.size()){
			int x=q.top().second;
			q.pop();
			if(vis[x]) continue ;
			vis[x]=1;
			tmp.push_back(x);
			for(int i:e[x]){
				int y=x^eu[i]^ev[i];
				double d=dis[x]+t[ew[i]];
				if(d<dis[y]) q.push({-(dis[y]=d),y});
			}
		}
	}
	inline void Solve(){
		cin>>n>>m>>q>>b;
		for(int i=1;i<=m;i++){
			char c;
			for(int j=0;j<12;j++){
				cin>>c;
				ew[i]|=c-'0'<<j;
			}
		}
		for(int i=1;i<=m;i++){
			cin>>eu[i]>>ev[i];
			e[eu[i]].push_back(i);
			e[ev[i]].push_back(i);
		}

		for(int i=1;i<=n;i++) dis[i]=1e18,vis[i]=0;
		while(q--){
			int s,t;
			cin>>s>>t;
			Dij(s);
			if(dis[t]==1e18) cout<<-1<<endl;
			else cout<<dis[t]<<endl;
		}

		for(int i=1;i<=m;i++) ew[i]=0;
		for(int i=1;i<=n;i++) e[i].clear();
		tmp.clear();
	}
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);
	cout<<fixed<<setprecision(10);

	Init();

	int T;
	cin>>T;
	if(T<0) while(T++) Run1::Solve();
	else while(T--) Run2::Solve();

	return 0;
}

J. Jolly Wheel

模拟,区间加,map,\(O(n\log n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=3e5+9;

inline void Solve(){
	int n,c=0;
	ll p=0,k;
	map<ll,ll> mp;

	cin>>n>>k,k<<=1;
	while(n--){
		ll a;
		cin>>a,a<<=1;
		mp[0]+=a/k,mp[k]-=a/k;
		if(a%k){
			ll l=c&1?(p-a%k+k)%k:(p+1)%k,r=c&1?(p-1+k)%k:(p+a%k)%k;
			if(l<=r) mp[l]++,mp[r+1]--;
			else mp[l]++,mp[k]--,mp[0]++,mp[r+1]--;
		}
		ll q=c&1?(p-a%k+k)%k:(p+a%k)%k;
		p=q,c++;
	}
	mp[0]++,mp[1]--;

	ll cur=0,mx=0,mn=2e18;
	for(auto p:mp){
		if(p.first>=k) break ;
		cur+=p.second;
		mx=max(mx,cur);
		mn=min(mn,cur);
	}

	cout<<mn<<' '<<mx<<endl;
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

K. Kitchen

模拟,每轮找到最早被吃掉的饵料,计算之后遣散目标是这个饵料的蟑螂即可,时间复杂度 \(O(nm^2)\)

#include<bits/stdc++.h>

using namespace std;

using ll=long long;
const int N=1e2+9;
const int M=1e3+9;
const ll inf=1e18;

ll ban[N],mnT[M];
int cx[N],cy[N],bx[M],by[M],vis[M],cnt[N],ord[M],n,m,a,t,w,h;
vector<int> cs[M];

inline int Cost(int i,int j){return abs(cx[i]-bx[j])+abs(cy[i]-by[j]);}
inline void Move(int i,int j,int k){
	if(k<=abs(cx[i]-bx[j])) cx[i]=bx[j]<cx[i]?cx[i]-k:cx[i]+k;
	else{
		k-=abs(cx[i]-bx[j]),cx[i]=bx[j];
		cy[i]=by[j]<cy[i]?cy[i]-k:cy[i]+k;
	}
}
inline void Join(int x,ll T){
	int p=0;ll S=inf;
	for(int i=1;i<=m;i++){
		if(vis[ord[i]]) continue ;
		if(Cost(x,ord[i])<S) S=Cost(x,ord[i]),p=ord[i];
	}
	if(S+T>ban[x]) return ;
	mnT[p]=min(mnT[p],S+T);
	cs[p].push_back(x);
}

signed main(){
	cin>>n>>m>>a>>t>>w>>h;
	for(int i=1;i<=n;i++) cin>>cx[i]>>cy[i];
	for(int i=1;i<=m;i++) cin>>bx[i]>>by[i];

	iota(ord+1,ord+m+1,1);
	sort(ord+1,ord+m+1,[](int i,int j){
		if(bx[i]!=bx[j]) return bx[i]<bx[j];
		else return by[i]<by[j];
	});

	ll T=0;
	for(int i=1;i<=m;i++) mnT[i]=inf;
	for(int i=1;i<=n;i++) ban[i]=inf,Join(i,T);
	for(int i=1;i<=m;i++){
		int p=0;ll S=inf;
		for(int j=1;j<=m;j++){
			if(vis[ord[j]]) continue ;
			if(mnT[ord[j]]<S) S=mnT[ord[j]],p=ord[j];
		}
		
		if(S==inf) break ;

		for(int j=1;j<=m;j++) for(int k:cs[j]) Move(k,j,S-T);
		T=S;

		vis[p]=1;
		int x=n+1;
		for(int j:cs[p]) if(Cost(j,p)==0) x=min(x,j);
		if(++cnt[x]==a) ban[x]=T+t;
		for(int j:cs[p]) Join(j,T);
		cs[p].clear();
	}

	for(int i=1;i<=n;i++) cout<<(ban[i]<inf?ban[i]:-1)<<endl;

	return 0;
}

L. Layered Caesar Salad

注意到 \(n=25\),因此必然有一种开头字符没有出现过,把所有串的开头位移到这个字符上即可,这样加密串的开头字符一定是众数。

如果所有串开头都一样,那么特判并令所有串的加密串是位移恰好一位的结果即可。

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'

inline string Shift(string s,int k){
	for(char &c:s) c=(c-'a'+k)%26;
	for(int i=1;i<s.size();i++) s[i]=(s[i]+s[i-1])%26;
	for(char &c:s) c+='a';
	return s;
}

namespace Encode{
	inline void Solve(){
		int n,k;
		cin>>n>>k;
		vector<string> str(n);
		for(auto &s:str) cin>>s;

		vector<int> cnt(26,0);
		for(auto &s:str) cnt[s[0]-'a']++;
		int p=find(cnt.begin(),cnt.end(),0)-cnt.begin();
		if(*max_element(cnt.begin(),cnt.end())==n){
			p=max_element(cnt.begin(),cnt.end())-cnt.begin()+1;
			p%=26;
		}

		for(auto &s:str){
			for(int i=1;i<26;i++){
				string t=Shift(s,i);
				if(t[0]==p+'a'){
					cout<<t<<endl;
					break ;
				}
			}
		}
	}
}
namespace Decode{
	inline void Solve(){
		int n,k;
		cin>>n>>k;
		vector<string> str(n<<1);
		for(auto &s:str) cin>>s;

		vector<int> cnt(26,0);
		for(auto &s:str) cnt[s[0]-'a']++;
		int p=max_element(cnt.begin(),cnt.end())-cnt.begin();
		if(count(cnt.begin(),cnt.end(),*max_element(cnt.begin(),cnt.end()))>1){
			if(!cnt[0]||!cnt[25]) p++;
		}

		for(auto &s:str){
			if(s[0]!=p+'a') cout<<s<<endl;
		}
	}
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	int T;
	string op;
	cin>>op>>T;

	if(op=="ENCODE") while(T--) Encode::Solve();
	else while(T--) Decode::Solve();

	return 0;
}

M. May We Answer Your Questions Right?

因为 \(\displaystyle \sum_{i=0}^k a_i2^i\) 很大,考虑计算 \(\displaystyle \left\lfloor\sum_{i=0}^k a_i2^{i-k} \right\rfloor\)\(\displaystyle \left\lfloor-\sum_{i=0}^k a_i2^{i-k} \right\rfloor\)的值,并通过这两个值判断正负性。

维护函数 \(\displaystyle f_i(x)=\Big\lfloor\frac x2\Big\rfloor+a_i\),那么 \(\displaystyle \left\lfloor\sum_{i=0}^k a_i2^{i-k} \right\rfloor=(f_0\circ f_1\circ\ldots\circ f_k)(0)\),不难发现复合出来的函数都是 \(\left\lfloor \dfrac {x+a}{b} \right\rfloor+c\) 式的。

\(f_1(x)=\left\lfloor \dfrac {x+a_1}{b_1} \right\rfloor+c_1,f_2(x)=\left\lfloor \dfrac {x+a_2}{b_2} \right\rfloor+c_2\),则 \((f_1\circ f_2)(x)=\left\lfloor \dfrac {x+a_1+b_1(c_1+a_2)}{b_1b_2} \right\rfloor+c_2\),然而 \(b\) 通过若干次复合增长十分迅速,不能简单地直接维护 \(a,b,c\)

为了方便,若 \(a\geq b\)\(a<0\),则先把 $\left\lfloor \dfrac ab \right\rfloor $ 提出去,即令 \(0\leq a<b\)

考虑令 \(b\) 对某个固定值 \(B\) check min,同时使答案不变,则有以下几种 check min 的方式:

  • \(f(x)\in[-1,0]\),令 \(b\leftarrow B\) ,此时 \(x\) 合法的定义域为 \((-a-B,B-a)\)
  • \(f(x)\in[0,0]\),令 \(a\leftarrow \left\lfloor \dfrac B2 \right\rfloor,b\leftarrow B\) ,此时 \(x\) 合法的定义域为 \((-\dfrac B2,\dfrac B2)\)
  • \(f(x)\in[0,1]\),令 \(a\leftarrow B-(b-a),b\leftarrow B\) ,此时 \(x\) 合法的定义域为 \((a-B,a+B)\)

根据等比数列求和公式,\(\displaystyle \left|\left\lfloor\sum_{i=0}^k a_i2^{i-k} \right\rfloor\right|\) 有上界 \(L\simeq 2\times10^9\),因此取 \(B=2L+1=4\times 10^9+1\) 即可。

至此已经可以用线段树维护 \(\left\lfloor \dfrac {x+a}{b} \right\rfloor+c\) 式函数的复合了,时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>

using namespace std;

using ll=long long;
using bint=__int128;
const int N=5e5+9;
const ll lim=2e9;

template<class T> inline T FlrDiv(T x,T y){return x/y-(x%y<0);}
struct Data{
	ll a,b,c;
	Data(){}
	Data(ll _a,ll _b,ll _c){a=_a,b=_b,c=_c;}
	inline ll operator ()(ll x){return FlrDiv(x+a,b)+c;}
	friend inline Data operator *(Data f,Data g){
		Data h(0,0,g.c);
		bint a=f.a+bint(f.b)*(f.c+g.a);
		bint b=bint(f.b)*g.b;
		ll tmp=FlrDiv(a,b);
		h.c+=tmp;
		a-=tmp*b;
		if(b>2*lim+1){
			if(-lim+a<0) h.a=a;
			else if(lim+a>=b) h.a=2*lim+1-(b-a);
			else h.a=lim;
			h.b=2*lim+1;
		}else h.a=a,h.b=b;
		return h;
	}
};

struct SgT{
	Data f[N<<2];
	inline void PushUp(int x){f[x]=f[x<<1]*f[x<<1|1];}
	inline void Build(int x,int L,int R){
		if(L==R) return f[x]=Data(0,1,0),void();
		int mid=L+R>>1;
		Build(x<<1,L,mid),Build(x<<1|1,mid+1,R);
		PushUp(x);
	}
	inline void Set(int x,int L,int R,int pos,Data k){
		if(L==R) return f[x]=k,void();
		int mid=L+R>>1;
		if(pos<=mid) Set(x<<1,L,mid,pos,k);
		else Set(x<<1|1,mid+1,R,pos,k);
		PushUp(x);
	}
	inline Data Query(int x,int L,int R,int l,int r){
		if(l<=L&&R<=r) return f[x];
		int mid=L+R>>1;
		if(r<=mid) return Query(x<<1,L,mid,l,r);
		else if(l>mid) return Query(x<<1|1,mid+1,R,l,r);
		else return Query(x<<1,L,mid,l,r)*Query(x<<1|1,mid+1,R,l,r);
	}
}pT,nT;

int n,q;

inline void Solve(){
	cin>>n>>q;
	pT.Build(1,1,n);
	nT.Build(1,1,n);
	
	for(int i=1,x;i<=n;i++){
		cin>>x;
		pT.Set(1,1,n,i,Data(0,2,x));
		nT.Set(1,1,n,i,Data(0,2,-x));
	}

	while(q--){
		int op;
		cin>>op;
		if(op==1){
			int x,y;
			cin>>x>>y;
			pT.Set(1,1,n,x,Data(0,2,y));
			nT.Set(1,1,n,x,Data(0,2,-y));
		}else{
			int l,r;
			cin>>l>>r;
			if(pT.Query(1,1,n,l,r)(0)<0) cout<<-1<<endl;
			else if(nT.Query(1,1,n,l,r)(0)<0) cout<<1<<endl;
			else cout<<0<<endl;
		}
	}
}

signed main(){
	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

Stage 20: Grand Prix of India

B. Palindrome and Permutation

首先如果两个对称的位置一样,那么我们就不用也不能管他。否则考虑在两种颜色之间连一条边,代表这个位置一定要用掉两种颜色之一。如果建出来最后某个连通块是基环树,那么连通块内所有点都要选,如果是树就可以空掉一个,否则就无解。

称最后选了的点为定点,否则为浮点。考虑分别计算最后恰有 \(k\) 个浮点的方案数。设 \(f_{i,j}\) 表示当前考虑到第 \(i\) 个连通块,后缀恰有 \(j\) 个浮点的方案数。那么每次共有 \(3\) 种转移,分别是后缀浮点增加一个,后缀浮点不变,后缀浮点减少若干再增加一个。如果该连通块没有浮点则共 \(2\) 种,即后缀浮点不变或后缀浮点减少若干。使用前缀和优化 DP 可以做到 \(O(n^2)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=5e3+9;
const int mod=998244353;

inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int QPow(int x,int y){
	int res=1;
	while(y){
		if(y&1) MulAs(res,x);
		MulAs(x,x);
		y>>=1;
	}
	return res;
}
inline int Inv(int x){return QPow(x,mod-2);}

int fac[N],ifac[N];
inline void Init(int lim){
	fac[0]=1;
	for(int i=1;i<=lim;i++) fac[i]=Mul(fac[i-1],i);
	ifac[lim]=Inv(fac[lim]);
	for(int i=lim-1;~i;i--) ifac[i]=Mul(ifac[i+1],i+1);
}
inline int C(int n,int m){
	if(m<0||m>n) return 0;
	else return Mul(fac[n],Mul(ifac[m],ifac[n-m]));
}

int f[N],fa[N],vsiz[N],esiz[N],a[N],n;
inline int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
inline void Merge(int x,int y){
	x=Find(x),y=Find(y);
	if(x==y) return ;
	fa[y]=x;
	vsiz[x]+=vsiz[y];
	esiz[x]+=esiz[y];
}

inline void Solve(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	
	Init(n);
	int c=0;
	for(int i=1;i<=n;i++) fa[i]=i,vsiz[i]=1,esiz[i]=0;
	for(int i=1;i<=n-i+1;i++){
		if(a[i]==a[n-i+1]) continue ;
		Merge(a[i],a[n-i+1]);
		esiz[Find(a[i])]++;
		c++;
	}

	if(!c) return cout<<0<<endl,void();
	
	f[0]=1;
	int p=0,q=0;
	for(int i=1;i<=n;i++){
		if(fa[i]!=i) continue ;
		if(esiz[i]>vsiz[i]){
			cout<<Mul(fac[n],n+1)<<endl;
			for(int j=0;j<=n;j++) f[j]=0;
			return ;
		}
		if(esiz[i]){
			if(esiz[i]<vsiz[i]){
				for(int j=q;j>=0;j--){
					AddAs(f[j+1],Mul(vsiz[i],f[j]));
				}
			}
			p+=esiz[i];
			q+=vsiz[i]-esiz[i];
		}
	}
	for(int j=0;j<=q;j++){
		f[j]=Sub(Mul(f[j],Mul(fac[p+q-j],fac[j])),Mul(f[j+1],Mul(fac[p+q-(j+1)],fac[j+1])));
	}

	int ans=0;
	for(int i=1;i<=n;i++){
		for(int j=0;j<=q;j++){
			AddAs(ans,Mul(Mul(Mul(C(i-1,p+q-j-1),C(n-i,j)),Mul(f[j],fac[n-p-q])),i));
		}
	}

	cout<<ans<<endl;

	for(int j=0;j<=n;j++) f[j]=0;
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

E. Optimal Splitting

可以证明的是,对于答案的任意位置,第一次取到的值一定不小于第二次取到的值,否则可以直接取到第一次取到的值,然后后面全部归到同一个子序列内,这样一定更优。

因此考虑逐位确定答案 \(T\),并记录所有可能抵达的状态。设二元组 \((i,j)\) 表示当前两个子序列长度分别为 \(i,j\),为了方便,规定 \(i>j\)。则答案第 \(i+1\) 位能取到的最小值即为所有可达的 \((i,j)\)\(S_{i+j+1}\) 的最小值,并且 \(S_{i+j+1}=T_{i+1}\)\((i,j)\) 可以转移到 \((i+1,j)\)。确定最小值后,若 \(S_{i+j+1}< T_{j+1}\)\((i,j)\) 可以转移到 \((i,j+1)\)。算法在第一次出现 \(i+j=n\) 时停止。时间复杂度 \(O(n^2)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
const int N=5e3+9;

int n,m;
char s[N],t[N];

inline void Solve(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>s[i];

	m=0;
	vector<int> sta;
	sta.push_back(0);
	while(true){
		vector<int> tta(n+1,0);
		
		char c='z';
		for(int p:sta) c=min(c,s[m+p+1]);

		t[++m]=c;
		for(int p:sta){
			if(s[m+p]!=c) continue ;
			tta[p]=1;
			if(m+p==n){
				for(int i=1;i<=m;i++) cout<<t[i];cout<<endl;
				return ;
			}
			for(int j=1;p+j<=m&&s[m+p+j]<=t[p+j];j++){
				tta[p+j]=1;
				if(m+p+j==n){
					for(int i=1;i<=m;i++) cout<<t[i];cout<<endl;
					return ;
				}
			}
		}
		
		sta.clear();
		for(int i=0;i<=n;i++) if(tta[i]) sta.push_back(i);
	}
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	int T;
	cin>>T;
	while(T--) Solve();	

	return 0;
}

G. Sequence Domination

类比单调不升序列的结论。序列 \(V\) 一定可以拆分成若干 \(\ldots,16,8,4,2,1,1\) 前缀的对应相加的和。因此只考虑 \(\ldots,4,2,1,1,0,0,\ldots\) 类序列是否满足条件是充分的。

考虑对 \(D_i=A_i-B_i\) DP,那么对任意的 \(p\)\(D\) 都必须满足 \(\displaystyle \sum_{i=1}^p D_i2^{\max(p-i-1,0)}\geq 0\)。设 \(f_{i,j}\) 表示当前考虑到 \(D_i\)\(\displaystyle \sum_{t=1}^{i-1} D_t2^{i-t-1}=j\),则有转移 \(f_{i,2j+k}\leftarrow (N-|k|)f_{i,j}\),其中 \(k\in(-n,n)\)\(j+k\geq 0\)。使用差分优化 DP 即可做到 \(O(n^2)\)

#include<bits/stdc++.h>

using namespace std;

using ll=long long;
const int N=5e3+9;
const int mod=998244353;

inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ull*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ull*x*y%mod;}
inline int QPow(int x,int y){
	int res=1;
	while(y){
		if(y&1) MulAs(res,x);
		MulAs(x,x);
		y>>=1;
	}
	return res;
}
inline int Inv(int x){return QPow(x,mod-2);}

int f[N][N],g[N<<2],n,m;
inline void Solve(){
	cin>>n>>m;
	f[0][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=0;j<=m;j++){
			int k=max(m-j-1,0);
			AddAs(g[(j<<1)-m+1+N+k],Mul(f[i-1][j],k+1));
			SubAs(g[(j<<1)-m+1+N+k+1],Mul(f[i-1][j],k));
			SubAs(g[(j<<1)+1+N],Mul(2,f[i-1][j]));
			AddAs(g[(j<<1)+m+1+N],f[i-1][j]);
		}
		for(int j=-m;j<=3*m+2;j++) AddAs(g[j+N],g[j+N-1]);
		for(int j=-m;j<=3*m+2;j++) AddAs(g[j+N],g[j+N-1]);
		for(int j=0;j<=3*m+2;j++) AddAs(f[i][min(j,m)],g[j+N]);
		for(int j=-m;j<=3*m+2;j++) g[j+N]=0;
	}
	
	int ans=0;
	for(int j=0;j<=m;j++) AddAs(ans,f[n][j]);
	cout<<ans<<endl;

	for(int i=0;i<=n;i++){
		for(int j=0;j<=m;j++) f[i][j]=0;
	}
}

signed main(){
	int T;
	cin>>T;
	while(T--) Solve();
	
	return 0;
}

I. Point Mirror

由于操作是可逆的,因此考虑把给定状态规约到一些有特殊性质的状态。当不同的坐标有 \(3\) 个及以上时,则可以令最右侧的点向左折,同时最大值与最小值的差变小,最后折到只有两种不同的坐标,且坐标间距为原来各坐标差的 \(\gcd\)。同时,由于操作不改变奇偶性,因此按按照 \(\gcd\) 分段之后,两个位置点的多少可以根据初始状态推定,即奇点只会跳到奇点,偶点只会跳到偶点。因此归约到的状态基本是可以确定的(即使有不少状态是同构的,我们也能很方便判断他们)。判断两个原始点集规约到的状态是否同构即可。

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
const int N=2e5+9;

int a[N],b[N],n;
inline void Solve(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i]>>b[i];

	sort(a+1,a+n+1),sort(b+1,b+n+1);
	if(a[1]==a[n]&&b[1]==b[n]){
		cout<<(a[1]==b[1]?"YES":"NO")<<endl;
		return ;
	}

	int ag=0,bg=0;
	for(int i=1;i<n;i++){
		ag=__gcd(ag,a[i+1]-a[i]);
		bg=__gcd(bg,b[i+1]-b[i]);
	}
	if(ag!=bg||(a[1]%ag+ag)%ag!=(b[1]%bg+bg)%bg){
		cout<<"NO"<<endl;
		return ;
	}
	int d=(a[1]%ag+ag)%ag,ca[2]={0,0},cb[2]={0,0};
	for(int i=1;i<=n;i++){
		a[i]-=d,b[i]-=d;
		ca[a[i]/ag&1]++;
		cb[b[i]/bg&1]++;
	}

	if(ca[0]==cb[0]&&ca[1]==cb[1]) cout<<"YES"<<endl;
	else cout<<"NO"<<endl;
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

Stage 21: Grand Prix of Kamigō

A. 01 Matrix

首先翻转 \((1,1)\)\((n,m)\) 处的位,把限制都变成偶数。

考虑限制 \((x_0,y_0)\) 的作用,事实上是令 \(x=x_0\)\(y=y_0\) 两条直线相交划分 \((1,1)\)\((n,m)\) 方向的象限内异或和为 \(0\),这是两个限制。

一般来讲,每个限制会让解空间大小减半。然而有些限制是重复的,比如限制点是矩形的四个角时,限制总数是 \(7\) 个而非 \(8\) 个。更一般地,如果存在一组点,它们的影响范围可以互相正好抵消,则存在 \(1\) 个限制是重复的。这相当于在 \(x=x_0\)\(y=y_0\) 之间连边,这样一组点则恰好构成一个环。因此有效限制总数则为限制点个数乘 \(2\) 再减去非树边个数,并查集维护即可,时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=3e5+9;
const int mod=998244353;

inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int QPow(int x,int y){
	int res=1;
	while(y){
		if(y&1) MulAs(res,x);
		MulAs(x,x);
		y>>=1;
	}
	return res;
}
inline int Inv(int x){return QPow(x,mod-2);}

vector<int> e[N];
int x[N],y[N],fa[N<<1],n,m,k;
inline int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
inline bool Merge(int x,int y){
	x=Find(x),y=Find(y);
	if(x==y) return 0;
	fa[y]=x;
	return 1;
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	cin>>n>>m>>k;
	for(int i=1;i<=k;i++) cin>>x[i]>>y[i];

	int cnt=2*k,tot=0;
	map<int,int> mp;
	for(int i=1;i<=k;i++){
		if(!mp[x[i]<<1]) mp[x[i]<<1]=++tot,fa[tot]=tot;
		x[i]=mp[x[i]<<1];
		if(!mp[y[i]<<1|1]) mp[y[i]<<1|1]=++tot,fa[tot]=tot;
		y[i]=mp[y[i]<<1|1];
		if(!Merge(x[i],y[i])) cnt--;
	}

	cout<<QPow(2,(1ull*n*m-cnt)%(mod-1))<<endl;

	return 0;
}

C. Don't be Clockwise

从外向内不断消去凸包即可,时间复杂度 \(O(n^2)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=3e3+9;

struct Point{
	ll x,y;
	Point(){};
	Point(ll _x,ll _y){x=_x,y=_y;}
	friend inline Point operator +(Point p,Point q){return Point(p.x+q.x,p.y+q.y);}
	friend inline Point operator -(Point p,Point q){return Point(p.x-q.x,p.y-q.y);}
}p[N];
inline ll Cross(Point p,Point q){return p.x*q.y-p.y*q.x;}

int n;
inline void Solve(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>p[i].x>>p[i].y;

	vector<int> vis(n+1,0),ord(n),ans;
	iota(ord.begin(),ord.end(),1);
	sort(ord.begin(),ord.end(),[](int i,int j){
		if(p[i].x!=p[j].x) return p[i].x<p[j].x;
		else return p[i].y<p[j].y;
	});

	while(ans.size()<n){
		vector<int> stk;
		for(int i=0;i<n;i++){
			if(vis[ord[i]]) continue ;
			while(stk.size()>1&&Cross(p[ord[i]]-p[stk.end()[-1]],p[stk.end()[-1]]-p[stk.end()[-2]])>=0) stk.pop_back();
			stk.push_back(ord[i]);
		}
		stk.pop_back();
		for(int i:stk) vis[i]=1,ans.push_back(i);
		stk.clear();
		for(int i=n-1;~i;i--){
			if(vis[ord[i]]) continue ;
			while(stk.size()>1&&Cross(p[ord[i]]-p[stk.end()[-1]],p[stk.end()[-1]]-p[stk.end()[-2]])>=0) stk.pop_back();
			stk.push_back(ord[i]);
		}
		if(stk.size()>1) stk.pop_back();
		for(int i:stk) vis[i]=1,ans.push_back(i);
	}

	for(int x:ans) cout<<x<<' ';cout<<endl;
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	int T;
	cin>>T;
	while(T--) Solve();

	return 0;
}

D. Except Ai

\(f_i\) 表示到 \(i\) 结束最少划分成几个连续段,则 \(\displaystyle f_i=\min_{\bigcup_{p=j+1}^i\{a_p\}\neq U} f_j+1\)。用 set 找出最远的 \(j\),单调队列优化 DP 即可,时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
const int N=5e5+9;

int f[N],a[N],pre[N],n,m;

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	deque<int> q;
	multiset<int> s;

	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];

	map<int,int> mp;
	for(int i=1;i<=n;i++){
		pre[i]=mp[a[i]];
		mp[a[i]]=i;
	}
	
	q.push_back(0);
	for(int t=min<int>(m,mp.size()+1);t--;) s.insert(0);
	for(int i=1;i<=n;i++){
		s.erase(s.find(pre[i]));
		s.insert(i);
		while(q.size()&&q.front()<*s.begin()) q.pop_front();
		f[i]=f[q.front()]+1;
		while(q.size()&&f[q.back()]>=f[i]) q.pop_back();
		q.push_back(i);
	}

	cout<<n-f[n]<<endl;

	return 0;
}

E. Find ''rururutata''

考虑对每个 \(i\) 计算向左最短的 \(\tt rururu\) 式串以及向右最短的 \(\tt tata\) 串。直接找出所有 runs,用线段树 check min 即可快速计算。后面就是二维数点,时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
const int N=5e5+9;
const int lgN=2e1;

vector<array<int,3>> runs,o;
int a[N],ql[N],qr[N],ans[N],n,q;

struct SufArr{
	int s[N],sa[N],rk[N<<1],lsa[N],lrk[N<<1],cnt[N],h[N],lg[N],mn[lgN][N],n;

	inline void Init(){
		for(int i=1;i<=n;i++) cnt[rk[i]=s[i]]++;
		for(int i=1;i<=n;i++) cnt[i]+=cnt[i-1];
		for(int i=n;i>=1;i--) sa[cnt[rk[i]]--]=i;
		for(int i=0;i<=n;i++) cnt[i]=0;

		int m=0;
		for(int i=1;i<=n;i++) lrk[i]=rk[i];
		for(int i=1;i<=n;i++){
			if(lrk[sa[i]]!=lrk[sa[i-1]]) m++;
			rk[sa[i]]=m;
		}

		for(int k=1;k<=n&&m<n;k<<=1){
			int t=0;
			for(int i=n-k+1;i<=n;i++) lsa[++t]=i;
			for(int i=1;i<=n;i++) if(sa[i]>k) lsa[++t]=sa[i]-k;
			
			for(int i=1;i<=n;i++) cnt[rk[lsa[i]]]++;
			for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
			for(int i=n;i>=1;i--) sa[cnt[rk[lsa[i]]]--]=lsa[i];
			for(int i=0;i<=m;i++) cnt[i]=0;

			m=0;
			for(int i=1;i<=n;i++) lrk[i]=rk[i];
			for(int i=1;i<=n;i++){
				if(lrk[sa[i]]!=lrk[sa[i-1]]||lrk[sa[i]+k]!=lrk[sa[i-1]+k]) m++;
				rk[sa[i]]=m;
			}
		}

		for(int i=1,k=0;i<=n;i++){
			if(k) k--;
			while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
			h[rk[i]]=k;
		}

		for(int i=2;i<=n;i++) lg[i]=lg[i>>1]+1;
		for(int i=1;i<=n;i++) mn[0][i]=h[i];
		for(int k=1;k<=lg[n];k++){
			for(int i=1;i<=n-(1<<k)+1;i++){
				mn[k][i]=min(mn[k-1][i],mn[k-1][i+(1<<k-1)]);
			}
		}
	}
	inline int Min(int l,int r){
		int k=lg[r-l+1];
		return min(mn[k][l],mn[k][r-(1<<k)+1]);
	}
	inline int LCP(int i,int j){
		if(i==j) return n-i+1;
		else return Min(min(rk[i],rk[j])+1,max(rk[i],rk[j]));
	}
}arr,rra;

struct SgT{
	int tag[N<<2];
	SgT(){memset(tag,0x3f,sizeof tag);}
	inline void Insert(int x,int L,int R,int l,int r,int k){
		if(l>r) return ;
		if(l<=L&&R<=r) return tag[x]=min(tag[x],k),void();
		int mid=L+R>>1;
		if(l<=mid) Insert(x<<1,L,mid,l,r,k);
		if(r>mid) Insert(x<<1|1,mid+1,R,l,r,k);
	}
	inline int Query(int x,int L,int R,int pos){
		if(L==R) return tag[x];
		int mid=L+R>>1;
		if(pos<=mid) return min(Query(x<<1,L,mid,pos),tag[x]);
		else return min(Query(x<<1|1,mid+1,R,pos),tag[x]);
	}
}pL,pR;

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);

	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	

	arr.n=rra.n=n;
	for(int i=1;i<=n;i++) arr.s[i]=rra.s[n-i+1]=a[i];
	arr.Init(),rra.Init();

	vector<int> stk;
	for(int c:{0,1}){
		for(int i=n;i>=1;i--){
			while(stk.size()&&((arr.rk[i]<arr.rk[stk.back()])^c)) stk.pop_back();
			if(stk.size()){
				int j=stk.back(),p=j-i;
				int l=i-rra.LCP(n-i+1,n-j+1)+1,r=j+arr.LCP(i,j)-1;
				if(r-l+1>=2*p&&arr.LCP(l,l+p)==r-l+1-p) runs.push_back({l,r,p});
			}
			stk.push_back(i);
		}
		stk.clear();
	}

	sort(runs.begin(),runs.end());
	runs.erase(unique(runs.begin(),runs.end()),runs.end());
	for(auto t:runs){
		pL.Insert(1,1,n,t[0]+3*t[2]-1,t[1],3*t[2]);
		pR.Insert(1,1,n,t[0],t[1]-2*t[2]+1,2*t[2]);
	}

	cin>>q;
	for(int i=1;i<=q;i++) cin>>ql[i]>>qr[i],o.push_back({ql[i],0,i});
	for(int i=1;i<n;i++){
		int lL=pL.Query(1,1,n,i),lR=pR.Query(1,1,n,i+1);
		if(lL>n||lR>n) continue ;
		o.push_back({i-lL+1,1,i+lR});
	}
	sort(o.begin(),o.end(),greater<array<int,3>>());
	
	int mnr=n+1;
	for(auto t:o){
		if(t[1]==0) ans[t[2]]=(mnr<=qr[t[2]]);
		else mnr=min(mnr,t[2]);
	}

	for(int i=1;i<=q;i++) cout<<(ans[i]?"Yes":"No")<<endl;

	return 0;
}

F. Increase Decrease

首先 LIS 和 LDS 每向后一位只会不变或增加 \(1\),且除了开头以外不会同时增加。

考虑如下构造,将结果序列由若干个值域连续下标不一定连续的单调下降子序列表示,这些子序列按照出现时间最大值依次递增。

  • 若向后一位 LIS 增加,则添加一个新的子序列。
  • 若向后一位 LDS 增加,则第一个子序列在向后增加一位,下标为当前位置,同时所有子序列的长度上限增加一位,该上限初始为 \(0\)
  • 若 LIS 和 LDS 均不变,则选择一个长度还没达到上限的子序列向后增加一位,下标为当前位置,若不存在这样的子序列则无解。

可以证明这样的构造是正确的,时间复杂度 \(O(n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
const int N=5e5+9;

int a[N],b[N],pos[N],t[N],c[N],p[N],n;

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) cin>>b[i];

	if(a[1]!=1||b[1]!=1) return cout<<-1<<endl,0;
	for(int i=1;i<n;i++){
		if(a[i+1]<a[i]) return cout<<-1<<endl,0;
		if(b[i+1]<b[i]) return cout<<-1<<endl,0;
		if(a[i+1]-a[i]+b[i+1]-b[i]>1) return cout<<-1<<endl,0;
	}
	for(int i=1;i<=n;i++) if(1ull*a[i]*b[i]<i) return cout<<-1<<endl,0;

	int cur=1,tot=0;
	for(int i=1;i<=n;i++){
		if(a[i]>a[i-1]) pos[i]=++tot,t[i]=1,c[pos[i]]=1;
		else if(b[i]>b[i-1]) cur=1,pos[i]=cur,t[i]=++c[cur];
		else{
			while(c[cur]>=b[i]) cur++;
			pos[i]=cur,t[i]=++c[cur];
		}
	}
	for(int i=1;i<=tot;i++) c[i]+=c[i-1];
	for(int i=1;i<=n;i++) p[i]=c[pos[i]]-t[i]+1;

	vector<int> inc,dec;
	for(int i=1;i<=n;i++){
		if(!inc.size()||p[i]>inc.back()) inc.push_back(p[i]);
		else *upper_bound(inc.begin(),inc.end(),p[i])=p[i];
		assert(inc.size()==a[i]);
		if(!dec.size()||p[i]<dec.back()) dec.push_back(p[i]);
		else *upper_bound(dec.begin(),dec.end(),p[i],greater<int>())=p[i];
		assert(dec.size()==b[i]);
	}

	for(int i=1;i<=n;i++) cout<<p[i]<<' ';cout<<endl;

	return 0;
}

Stage 22: Grand Prix of Kyoto

啊啊啊一个人打好吃力啊。

A. Kendama Challenge

考虑计算不存在长度为 \(K\) 的成功连续段的概率,设 \(f_i\) 表示在没有长度为 \(K\) 的成功连续段的情况下,第 \(i\) 次恰好失败的概率,则有转移 \(\displaystyle f_i=\left(1-\dfrac{A_i}{B_i}\right)\sum_{j=1}^K f_{i-j} \prod_{k=1}^{j-1} \dfrac {A_{i-k}}{B_{i-k}}\),队列维护即可,时间复杂度 \(O(n\log V)\),瓶颈在计算逆元。

#include<bits/stdc++.h>

using namespace std;

using ll=long long;
const int N=2e5+9;
const int mod=998244353;

inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ull*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ull*x*y%mod;}
inline int QPow(int x,int y){
	int res=1;
	while(y){
		if(y&1) MulAs(res,x);
		MulAs(x,x);
		y>>=1;
	}
	return res;
}
inline int Inv(int x){return QPow(x,mod-2);}

int a[N],b[N],f[N],pre[N],n,k;

signed main(){
	cin>>n>>k;
	for(int i=1;i<=n;i++) cin>>a[i]>>b[i];

	int sum=1;
	f[0]=1,pre[0]=1,a[n+1]=0,b[n+1]=1;
	for(int i=1;i<=n+1;i++){
		int p=Mul(a[i],Inv(b[i]));
		pre[i]=Mul(pre[i-1],p);
		f[i]=Mul(sum,Sub(1,p));
		MulAs(sum,p);
		AddAs(sum,f[i]);
		if(i>=k) SubAs(sum,Mul(f[i-k],Mul(pre[i],Inv(pre[i-k]))));
	}

	cout<<Sub(1,f[n+1])<<endl;

	return 0;
}

D. Campaign Speech

纯模拟。

考虑计算环上相邻两点最远距离,总长减最远距离即为答案。

剩下的就是写写写讨讨讨,时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;
const int N=1e5+9;

struct Point{
	int x,y;
	friend inline bool operator <(Point p,Point q){
		if(p.x!=q.x) return p.x<q.x;
		else return p.y<q.y;
	}
}p[N],q[N];

inline ll Dist(Point p,Point q){return abs(p.x-q.x)+abs(p.y-q.y);}

vector<int> o[N];
map<int,vector<array<int,3>>> X,Y;
int id[N],n,m;

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	cin>>n>>m;
	for(int i=0;i<n;i++) cin>>p[i].x>>p[i].y;
	for(int i=0;i<m;i++) cin>>q[i].x>>q[i].y;

	for(int i=0;i<n;i++){
		int j=(i+1)%n;
		if(p[i].x==p[j].x){
			int l=p[i].y,r=p[j].y;
			if(l>r) swap(l,r);
			X[p[i].x].push_back({l,r,i});
		}else{
			int l=p[i].x,r=p[j].x;
			if(l>r) swap(l,r);
			Y[p[i].y].push_back({l,r,i});
		}
	}
	for(auto &p:X) sort(p.second.begin(),p.second.end());
	for(auto &p:Y) sort(p.second.begin(),p.second.end());
	for(int i=0;i<m;i++){
		if(X.count(q[i].x)){
			auto &v=X[q[i].x];
			int p=lower_bound(v.begin(),v.end(),array<int,3>({q[i].y+1,0,0}))-v.begin()-1;
			if(p>=0&&q[i].y<=v[p][1]) id[i]=v[p][2];
		}
		if(Y.count(q[i].y)){
			auto &v=Y[q[i].y];
			int p=lower_bound(v.begin(),v.end(),array<int,3>({q[i].x+1,0,0}))-v.begin()-1;
			if(p>=0&&q[i].x<=v[p][1]) id[i]=v[p][2];
		}
		o[id[i]].push_back(i);
	}

	ll c=0,d=0,sum=0,lm=-1;
	for(int i=0;i<n;i++){
		int j=(i+1)%n,lst=-1;
		sort(o[i].begin(),o[i].end(),[&](int a,int b){return (q[a]<q[b])^(p[i]<p[j])^1;});
		for(int k:o[i]){
			if(!~lm) lm=sum+Dist(p[i],q[k]);
			else if(!~lst) d=max(d,sum+Dist(p[i],q[k]));
			else d=max(d,Dist(q[lst],q[k]));
			sum=Dist(q[k],p[j]);
			lst=k;
		}
		c+=Dist(p[i],p[j]);
		if(!o[i].size()) sum+=Dist(p[i],p[j]);
	}
	d=max(d,lm+sum);

	cout<<c-d<<endl;

	return 0;
}

F. 1e16 Cities

\[\begin{align} &\operatorname{lcm}(i,j)=A\cdot\gcd(i,j)+B \\ &\dfrac{\operatorname{lcm}(i,j)}{\gcd(i,j)}=A+\dfrac{B}{\gcd(i,j)} \\ &\dfrac{i}{\gcd(i,j)}\cdot\dfrac{j}{\gcd(i,j)}=A+\dfrac{B}{\gcd(i,j)} \end{align} \]

考虑枚举 \(\gcd(i,j)\),显然 \(\gcd(i,j)\mid B\),然后找到乘积恰为 \(A+\dfrac{B}{\gcd(i,j)}\) 且互质的数对 \((i',j')\),在 \((i'\gcd(i,j),j'\gcd(i,j))\) 之间连边,并查集维护异或和。时间复杂度为 \(O(\sqrt{B}+d(B)\sqrt{A+B})\)

#include<bits/stdc++.h>

using namespace std;

#define endl '\n'
using ll=long long;

const ll lim=1e16;
map<ll,ll> fa,s;
inline ll Find(ll x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
inline void Merge(ll x,ll y){
	if(x>lim||y>lim) return ;
	if(!fa.count(x)) fa[x]=s[x]=x;
	if(!fa.count(y)) fa[y]=s[y]=y;
	x=Find(x),y=Find(y);
	if(x==y) return ;
	fa[y]=x;
	s[x]^=s[y];
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(0);

	int q;
	ll a,b;
	cin>>a>>b;
	vector<ll> B;
	for(ll i=1;i*i<=b;i++){
		if(b%i) continue ;
		if(i*i!=b) B.push_back(b/i);
		B.push_back(i);
	}
	for(ll g:B){
		ll c=a+b/g;
		for(ll i=1;i*i<=c;i++){
			if(c%i) continue ;
			if(__gcd<ll>(i,c/i)!=1) continue ;
			Merge(i*g,c/i*g);
		}
	}

	cin>>q;
	while(q--){
		ll x;
		cin>>x;
		if(!fa.count(x)) cout<<x<<endl;
		else cout<<s[Find(x)]<<endl;
	}

	return 0;
}

G. The Symbolic Tree

感觉见过原题。

先离散化到 \(N\) 个数,设 \(f_{u,i}\) 表示考虑到 \(u\) 子树,\(u\)\(i\) 的方案数,转移是简单的。考虑容斥掉没有点取到的值,设 \(g_i\) 表示实际上有 \(i\) 个本质不同的值的方案数,则 \(\displaystyle g_i=f_{1,i}-\sum_{j=1}^{i-1} g_j\dbinom{i-1}{j-1}\),答案即为 \(\displaystyle \sum_{i=1}^N g_i\dbinom{K-1}{i-1}\)

#include<bits/stdc++.h>

using namespace std;

using ll=long long;
const int N=3e3+9;
const int mod=998244353;

inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ull*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ull*x*y%mod;}
inline int QPow(int x,int y){
	int res=1;
	while(y){
		if(y&1) MulAs(res,x);
		MulAs(x,x);
		y>>=1;
	}
	return res;
}
inline int Inv(int x){return QPow(x,mod-2);}

int fac[N],ifac[N];
inline void Init(int lim){
	fac[0]=1;
	for(int i=1;i<=lim;i++) fac[i]=Mul(fac[i-1],i);
	ifac[lim]=Inv(fac[lim]);
	for(int i=lim-1;~i;i--) ifac[i]=Mul(ifac[i+1],i+1);
}
inline int C(int n,int m){
	if(m<0||m>n) return 0;
	else return Mul(fac[n],Mul(ifac[m],ifac[n-m]));
}
inline int B(int n,int m){
	if(m<0||m>n) return 0;
	int ans=ifac[m];
	for(int i=n-m+1;i<=n;i++) MulAs(ans,i);
	return ans;
}

vector<int> e[N];
int f[N][N],g[N],s[N][N],n,k;
inline void DFS(int x,int fa){
	for(int i=1;i<=n;i++) f[x][i]=1;
	for(int y:e[x]){
		if(y==fa) continue ;
		DFS(y,x);
		for(int i=1;i<=n;i++) MulAs(f[x][i],s[y][i]);
	}
	for(int i=1;i<=n;i++) s[x][i]=Add(s[x][i-1],f[x][i]);
}

signed main(){
	cin>>n>>k,Init(n);
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}

	int ans=0;
	DFS(1,-1);
	for(int i=1;i<=n;i++){
		g[i]=f[1][i];
		for(int j=1;j<i;j++) SubAs(g[i],Mul(g[j],C(i,j)));
		AddAs(ans,Mul(g[i],B(k+1,i)));
	}

	cout<<ans<<endl;

	return 0;
}

J. Sum of max of iai

考虑计算 \(g(x)=\displaystyle \sum_{p\in \operatorname{Perm}(N)} [f(p)\leq x]=\prod_{i=1}^N \max\left(\left\lfloor\dfrac xi\right\rfloor-(N-i),0\right)\),直接从 \(1\)\(N^2\) 扫即可,时间复杂度 \(O(N^2)\)

#include<bits/stdc++.h>

using namespace std;

using ll=long long;
const int N=1e4+9;
const int M=1e8+9;

int n,mod;
inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ull*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ull*x*y%mod;}
inline int QPow(int x,int y){
	int res=1;
	while(y){
		if(y&1) MulAs(res,x);
		MulAs(x,x);
		y>>=1;
	}
	return res;
}
inline int Inv(int x){return QPow(x,mod-2);}


array<short,2> p[M];
int cnt[M],t[M],c[N],inv[N];

signed main(){
	cin>>n>>mod;

	int cur=1,fac=1;
	for(int i=1;i<=n;i++) inv[i]=Mul(Inv(i),i+1);
	for(int i=1;i<=n;i++) c[i]=-(n-i),MulAs(fac,i);

	int ans=0,m=n*n,o=0;
	for(int i=1;i<=n;i++){
		for(int j=1,k=i;j<=n;j++,k+=i) cnt[k]++;
	}
	for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
	for(int i=1;i<=n;i++){
		for(int j=1,k=i;j<=n;j++,k+=i){
			int x=cnt[k]--;
			p[x][0]=i,p[x][1]=j;
		}
	}
	for(int i=1;i<=n;i++) t[i*(n-i+1)]++;
	for(int i=1,j=1;i<=m;i++){
		AddAs(ans,Sub(fac,o==n?cur:0));
		while(j<=m&&signed(p[j][0])*p[j][1]<=i){
			if(c[p[j][0]]>0) MulAs(cur,inv[c[p[j][0]]]);
			c[p[j++][0]]++;
		}
		o+=t[i];
	}

	cout<<ans<<endl;

	return 0;
}

L. Make Many KUPC

从后向前贪心匹配即可,时间复杂度 \(O(N)\)

#include<bits/stdc++.h>

using namespace std;

using ll=long long;
const int mod=998244353;

inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ull*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ull*x*y%mod;}
inline int QPow(int x,int y){
	int res=1;
	while(y){
		if(y&1) MulAs(res,x);
		MulAs(x,x);
		y>>=1;
	}
	return res;
}
inline int Inv(int x){return QPow(x,mod-2);}

const int N=5e5+9;

int n;
char c[N];

signed main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>c[i];

	int ans=0;
	queue<int> q[3];
	for(int i=n;i>=1;i--){
		if(c[i]=='C') q[0].push(i);
		else if(c[i]=='P'){
			if(q[0].size()) q[1].push(Mul(q[0].front(),i)),q[0].pop();
		}else if(c[i]=='U'){
			if(q[1].size()) q[2].push(Mul(q[1].front(),i)),q[1].pop();
		}else if(c[i]=='K'){
			if(q[2].size()) AddAs(ans,Mul(q[2].front(),i)),q[2].pop();
		}
	}
	
	cout<<ans<<endl;

	return 0;
}

O. Xor Triangle

方案数等价于 \(\displaystyle \sum_{x=1}\sum_{y=1} [x\nsubseteq y][y\nsubseteq x][x\cap y\neq \varnothing]\),即 \(\displaystyle \sum_{x=1} (2^N-1)-(2^{\operatorname{popcount}(x)}-2)-2(2^{\operatorname{popcount}(N-x)}-1)\),进一步推导得到 \((2^N-1)^2-3^{N+1}+3\cdot (2^N-1)+2^{N+1}+1\)

#include<bits/stdc++.h>

using namespace std;

using ll=long long;
const int mod=998244353;

inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ull*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ull*x*y%mod;}
inline int QPow(int x,ll y){
	int res=1;
	while(y){
		if(y&1) MulAs(res,x);
		MulAs(x,x);
		y>>=1;
	}
	return res;
}
inline int Inv(int x){return QPow(x,mod-2);}

signed main(){
	ll n;
	cin>>n;

	int p2=QPow(2,n),p3=QPow(3,n),s2=Sub(p2,1);

	cout<<Sub(Mul(s2,s2),Sub(Mul(3,p3),Add(Add(Mul(3,s2),Mul(2,p2)),1)));

	return 0;
}
posted @ 2025-10-14 19:08  JoeyJiang  阅读(183)  评论(1)    收藏  举报