2026.10 做题记录1

10.12

补一下最近做的题吧,推荐一下《终将成为你》,好看。

学习了二进制警报器和一些 dp 优化,放在博客里的就不在这里写了。

Shik and Travel

题意

给定一棵 \(n\) 个节点的完全二叉树,边有边权,根为 \(1\) 号节点,你需要将所有叶子节点组成的序列 \(p\) 重排,令有 \(m\) 个叶子节点,求 \(\max\limits_{i=2}^{m} \operatorname{dis}(p_i,p_{i-1})\) 最小值,其中 \(\operatorname{dis}(x,y)\) 表示 \(x\) 与 \(y\) 在这棵二叉树上的距离。

\(2\le n\le 2^{17}\)。

solution

感觉算是比较简单,比其他神秘计数好多了。

首先显然要二分,然后对于每个非叶子节点,访问叶子的顺序显然是先访问所有左子树的叶子,再到当前节点继续访问右子树的所有叶子,最后离开子树,或者先右后左也是一样的。

一开始我的思路是令 \(f_u(x)\) 表示进入 \(u\) 时 \(u\) 到第一个访问叶子节点距离为 \(x\) 时最后一个访问叶子节点到 \(u\) 的距离最小值,\(g_u(x)\) 表示最后一个访问叶子节点到 \(u\) 的距离为 \(x\) 时 \(u\) 到第一个访问叶子节点距离最小值。

考虑节点 \(u\),令 \(s_x\) 表示 \(x\) 子树内叶子节点个数,钦定左子树内叶子节点个数小于右子树,那么如果先进入左子树,则通过两个子树的 \(f\) 转移,先进入右子树则通过两个子树的 \(g\) 转移,惊奇地发现只用 \(f_u\) 和 \(g_u\) 就能够表示节点 \(u\) 的所有最优状态,而这个 \(u\) 的最优状态数只有 \(2\times \min(s_{ls_u},s_{rs_u})\) 个,通过启发式合并的复杂度分析,所有合法状态总数居然是 \(O(n\log n)\) 级别的!

实际上这个状态设计得有点傻,因为 \(u\) 到第一个访问叶子节点距离为 \(x\) 时最后一个访问叶子节点到 \(u\) 的距离为 \(y\),那么将路径反过来,就可以得到一条 \(u\) 到第一个访问叶子节点距离为 \(y\) 时最后一个访问叶子节点到 \(u\) 的距离为 \(x\) 的路径,所以这个 \(f,g\) 本质没啥区别,所以可以设计 \(f_u(x,y)\) 表示 \(u\) 子树内存在一条合法路径,使得 \(u\) 到第一个访问叶子节点距离为 \(x\) 时最后一个访问叶子节点到 \(u\) 的距离为 \(y\)。

不难发现只需要所有最优的 \(f\) 即可,即若有对于 \(u\) 内两条路径 \((x_1,y_1),(x_2,y_2)\) 有 \(x_1\ge x_2\land y_1\ge y_2\),那么 \((x_1,y_1)\) 一定不会是最优选择,转移要求是 \(y_{ls}+w_{ls}+w_{rs}+x_{rs}\le T\),其中 \(T\) 是二分出来的最大值限制,满足则可以得到一组路径 \((x_{ls}+w_{ls},y_{rs}+w_{rs})\),转移时只需保证 \(f_{ls},f_{rs}\) 内 \(x\) 单调,用 vector 维护配合双指针即可转移,钦定先左后右的转移顺序,得到所有有效状态后翻转 \(x,y\),由于转移后得到 \(x\) 单调递增 \(y\) 单调递减,所以归并一下即可维护 \(f_u\) 的单调性。

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

Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n;
struct Edge{int v,w;};
vector<Edge> e[N];

ll T;
struct Node{ll x,y;};
vector<Node> f[N];

inline void dfs(int now) {
	if (e[now].empty()) return f[now].push_back({0,0}),void();
	int ls=e[now].front().v,wl=e[now].front().w;
	int rs=e[now].back().v,wr=e[now].back().w;
	dfs(ls);dfs(rs);
	if (f[ls].size()>f[rs].size()) swap(ls,rs),swap(wl,wr);
	if (f[ls].empty()) return;
	int p=0,sz=f[rs].size();
	ll mx=T-wl-wr;
	vector<Node> tmp1,tmp2;
	for (Node k:f[ls]) {
		ll x=k.x,y=k.y;
		while (p<sz&&y+f[rs][p].x<=mx) p++;
		if (p) {
			tmp1.push_back({x+wl,f[rs][p-1].y+wr});
			tmp2.push_back({f[rs][p-1].y+wr,x+wl});
		}
	}
	reverse(tmp2.begin(),tmp2.end());
	int sz1=tmp1.size(),sz2=tmp2.size(),p1=0,p2=0;
	while (p1<sz1||p2<sz2) {
		Node k;
		if (p2==sz2||(p1!=sz1&&tmp1[p1].x<tmp2[p2].x)) k=tmp1[p1++];
		else k=tmp2[p2++];
		while (p1<sz1&&tmp1[p1].x==k.x) k.y=min(k.y,tmp1[p1++].y);
		while (p2<sz2&&tmp2[p2].x==k.x) k.y=min(k.y,tmp2[p2++].y);
		if (f[now].empty()||k.y<f[now].back().y) f[now].push_back(k);
	}
}

inline bool check(ll _T) {
	f(i,1,n) f[i].clear();
	T=_T;dfs(1);
	return !f[1].empty();
}

int main() {
	read(n);
	f(i,2,n) {
		int fa,w;read(fa);read(w);
		e[fa].push_back({i,w});
	}
	ll l=0,r=2e10,res=-1;
	while (l<=r) {
		ll mid=l+r>>1;
		if (check(mid)) res=mid,r=mid-1;
		else l=mid+1;
	}
	printf("%lld\n",res);
	return 0;
}

「FAOI-R15」XOR and Interval Operation

题意

给定长度为 \(n\) 序列 \(a\),你可以进行以下操作若干次:

  • 给出 \(1\le l<r\le n\),求出 \(k=\bigoplus\limits_{l}^{r} a_i\),其中 \(\oplus\) 表示异或操作;
  • 对于所有 \(l\le i\le r\),执行 \(a_i\leftarrow a_i\oplus k\)。

求能否经过若干次操作使得 \(a\) 全部变成 \(0\),可以证明如果有则存在一组操作数不超过 \(1.5\times 10^5\) 次的方案,判断是否有解,有解还需输出方案。

\(1\le n\le 10^5,1\le a_i\le 10^5\)。

solution

有点好笑,记录一下。

按位考虑,发现每层独立,于是可以逐层将每个位置均变成 \(0\),变成 \(0\) 后后面操作不会对当前层造成影响,于是也许可以设计出次数为 \(O(n\log V)\) 的做法,但这样显然很难优化。

从 \(1.5\times 10^5\) 入手,因为用 \(3\) 次操作将两个位置变成 \(0\) 似乎不太现实,于是考虑用 \(6\) 次操作将 \(4\) 个位置同时变成 \(0\),于是直接搜索,惊奇地找到了若干组对于所有情况都满足操作后每个位置都变成 \(0\) 的解,于是特判 \(n\le 3\),\(n\ge 4\) 直接构造即可。

由于每层独立,所以搜索时状态只有 \(2^4\) 种,搜索量只有 \((\frac{4\times 3}{2})^{6}\times 2^4\),时间复杂度 \(O(n)\)。

Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n;
int a[N];

struct Node{int l,r;};
vector<Node> ans;

int main() {
	read(n);
	f(i,1,n) read(a[i]);
	if (n<=3) {
		if (n==3&&a[1]==a[2]&&a[1]==a[3]) puts("Yes\n1\n1 3");
		else puts("No");
		return 0;
	}
	puts("Yes");
	for (int l=1;l<=n;l+=4) {
		if (l+3>n) l=n-3;
		ans.push_back({l+0,l+2});
		ans.push_back({l+1,l+3});
		ans.push_back({l+0,l+1});
		ans.push_back({l+1,l+3});
		ans.push_back({l+2,l+3});
		ans.push_back({l+0,l+2});
	}
	printf("%d\n",(int)(ans.size()));
	for (Node k:ans) printf("%d %d\n",k.l,k.r);
	return 0;
}

[JAG 2026 Summer Camp #2] Longest Path on DAG

solution

令 \(f_i\) 为以 \(i\) 结尾最长链。

使用 cdq 转移可以做到 \(O(n\log^2 n)\)。

但是我没想到,首先白点向黑点转移是很简单的,排序后分治判定可以做到总共 \(O(n\log n)\),黑点向白点转移我不会,于是写了个线段树维护可能的最优黑点,每次查询 pushdown 到下面区间,惊奇地发现可以通过,但是 AI 说可以卡到 \(O(n^2)\)。、

Code
#include<cstdio>
#include<algorithm>
#include<bitset>
#include<vector>
#include<set>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=500+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,st;
bitset<N> a;
int f[N],fr[N],id[N];

inline bool ask_in(int u,bitset<N> &ok) {
	printf("in %d ",u);
	f(i,1,n) putchar(ok[i]+'0');
	putchar(10);
	fflush(stdout);
	int x;read(x);
	return x;
}

inline bool ask_out(int u,bitset<N> &ok) {
	printf("out %d ",u);
	f(i,1,n) putchar(ok[i]+'0');
	putchar(10);
	fflush(stdout);
	int x;read(x);
	return x;
}

inline bool solve(int l,int r) {
	static bitset<N> ok;
	ok.reset();
	f(i,l,r) ok.set(id[i]);
	if (!ask_in(st,ok)) return 0;
	if (l==r) {
		f[st]=f[id[l]]+1;
		fr[st]=id[l];
		return 1;
	}
	int mid=l+r>>1;
	if (solve(mid+1,r)) return 1;
	return solve(l,mid);
}

struct Node{int l,r,now,k;};
inline bool operator <(const Node &a,const Node &b) {return f[a.k]<f[b.k];}

struct Segment_Tree {
	set<int> tr[N<<2];
	inline int ls(int x) {return x<<1;}
	inline int rs(int x) {return x<<1|1;}
	
	inline void update(int x,int y,int l,int r,int now,int k) {
		if (x<=l&&y>=r) {
			static bitset<N> ok;
			ok.reset();
			f(i,l,r) if (!a[i]) ok.set(i);
			if (!ok.any()) return;
			if (!ask_out(k,ok)) return;
			return tr[now].insert(k),void();
		}
		int mid=l+r>>1;
		if (x<=mid) update(x,y,l,mid,ls(now),k);
		if (y>mid) update(x,y,mid+1,r,rs(now),k);
	}
	
	struct Node query(int x,int l,int r,int now) {
		Node ret={0,0,0,0};
		for (int x:tr[now]) ret=max(ret,{l,r,now,x});
		if (l==r) return ret;
		Node nxt={0,0,0,0};
		int mid=l+r>>1;
		if (x<=mid) nxt=query(x,l,mid,ls(now));
		else nxt=query(x,mid+1,r,rs(now));
		if (nxt<ret) return ret;
		return nxt;
	}
}tr;

inline void solve(int x) {
	f[x]=1;
	if (a[x]) {
		int len=0;
		f(i,1,x-1) if (!a[i]) id[++len]=i;
		if (!len) f[x]=1;
		else {
			sort(id+1,id+len+1,[](int x,int y){return f[x]<f[y];});
			st=x;solve(1,len);
		}
		tr.update(1,n,1,n,1,x);
	}
	else {
		Node ret=tr.query(x,1,n,1);
		while (ret.k) {
			int l=ret.l,r=ret.r,now=ret.now,k=ret.k;
			if (l==r) {
				f[x]=f[k]+1;
				fr[x]=k;
				break;
			}
			tr.tr[now].erase(k);
			int mid=l+r>>1;
			static bitset<N> ok;
			ok.reset();
			f(i,l,mid) if (!a[i]) ok.set(i);
			if (ok.any()&&ask_out(k,ok)) tr.tr[now<<1].insert(k);
			ok.reset();
			f(i,mid+1,r) if (!a[i]) ok.set(i);
			if (ok.any()&&ask_out(k,ok)) tr.tr[now<<1|1].insert(k);
			ret=tr.query(x,1,n,1);
		}
	}
}

int main() {
	read(n);
	f(i,1,n) {
		char ch=getchar();
		while (ch!='0'&&ch!='1') ch=getchar();
		a.set(i,ch-'0');
	}
	f(i,1,n) solve(i);
	int id=0;
	f(i,1,n) if (f[i]>f[id]) id=i;
	vector<int> ans;
	while (id) {
		ans.push_back(id);
		id=fr[id];
	}
	reverse(ans.begin(),ans.end());
	printf("! %d ",(int)(ans.size()));
	for (int x:ans) printf("%d ",x);
	return 0;
}

周长

题意

给定 \(n\times m\) 网格,每个网格上有权值,求周长为 \(C\) 的封闭图形内部权值和最大值,保证 \(C\) 为偶数。

\(1\le n,m\le n\times m\le 3\times 10^6,4\le C\le 2\times (n+m)\)。

solution

神了。

不难发现答案一定由一个矩形圈起,否则周长不变一定可以扩展为一个包含所有当前圈定位置的矩形。

发现对于两条斜线 \(x+y=d_1\) 和 \(x+y=d_1+\frac{C}{2}-2\),若矩形左上角位于第一条斜线,右下角位于第二条斜线,则对于左上角 \((x_1,y_1)\),其最优决策点随 \(x_1\) 递增而单调不减,即具有决策单调性,证明可以通过画图考虑面积反证。

于是枚举第一条斜线,用各种决策单调性做法即可做到 \(O(n\log n)\) 或 \(O(n)\) 求出答案。

这个斜线决策单调性是人想得出来的吗。

Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=1e7+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,m,m1,C;
ll s[N],ans;

inline int id(int x,int y) {return x*m1+y;}
inline ll W(int l1,int r1,int l2,int r2) {return s[id(r1,r2)]-s[id(r1,l2)]-s[id(l1,r2)]+s[id(l1,l2)];}

inline void solve(int l,int r,int L,int R,int dx,int dy) {
	if (l>r) return;
	int x=(l+r>>1),y=dx-x,M=L;
	ll res=0;
	f(i,L,R) {
		int xx=i,yy=dy-i;
		if (xx<=x||yy<=y) continue;
		ll val=W(x,xx,y,yy);
		if (val>=res) res=val,M=i;
	}
	ans=max(ans,res);
	solve(l,x-1,L,M,dx,dy);
	solve(x+1,r,M,R,dx,dy);
}

inline void solve() {
	read(n);read(m);read(C);C/=2;m1=m+1;
	f(i,1,n) f(j,1,m) {int x;read(x);s[id(i,j)]=x;}
	f(i,1,n) f(j,1,m) s[id(i,j)]+=s[id(i-1,j)]+s[id(i,j-1)]-s[id(i-1,j-1)];
	ans=0;
	for (int dx=0,dy=C;dy<=n+m;dx++,dy++) solve(max(0,dx-m+1),min(n-1,dx),max(1,dy-m),min(n,dy-1),dx,dy);
	printf("%lld\n",ans);
}

int main() {
	int T;
	read(T);
	while (T--) solve();
	return 0;
}

Investors

solution

发现每次加操作一定对于后缀加,否则左端点不变右端点扩展到后缀一定不劣。

那么对于一次后缀加,这段后缀到除去这段后缀的前缀所有逆序对一定被消掉,后缀内部逆序对不变。

那么原题等价于分成 \(k+1\) 个子区间,每个子区间内逆序对个数之和最小。

不难发现权值函数满足四边形不等式,且可以通过 \(O(\log n)\) 移动指针求出,于是 wqs 二分后使用 larsch 配合 \(O(\log n)\) 移指针即可做到 \(O(n\log^3 n)\),跑不过 \(O(n^2)\)。

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=6000+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,k;
int a[N],mp[N];

ll f[N],g[N],cost_wqs;
int p[N];

struct W_get {
	int l,r,res;
	struct BIT {
		int v[N];
		inline void add(int p,int k) {for(;p<=n;p+=(p&-p))v[p]+=k;}
		inline int qry(int p) {int r=0;for(;p;p-=(p&-p))r+=v[p];return r;}
	}tr1,tr2;
	
	inline void update(int x,int k) {
		tr1.add(a[x],k);
		tr2.add(n-a[x]+1,k);
	}
	
	inline void init() {
		f(i,1,n) tr1.v[i]=tr2.v[i]=0;
		l=1;r=1;res=0;
		tr1.add(a[1],1);
		tr2.add(n-a[1]+1,1);
	}
	
	inline ll W(int qL,int qR) {
		while (l>qL) {
			update(--l,1);
			res+=tr2.qry(n-a[l]);
		}
		while (r<qR) {
			update(++r,1);
			res+=tr1.qry(a[r]-1);
		}
		while (l<qL) {
			res-=tr2.qry(n-a[l]);
			update(l++,-1);
		}
		while (r>qR) {
			res-=tr1.qry(a[r]-1);
			update(r--,-1);
		}
		return res;
	}
}W_1,W_2;

inline void solve(int l,int r) {
	if (r-l<=1) return;
	int mid=l+r>>1;
	for (int j=p[l],Rmx=min(p[r],mid-1);j<=Rmx;j++) {
		ll now=f[j]+W_1.W(j+1,mid)+cost_wqs;
		if (now<=f[mid]) f[mid]=now,p[mid]=j;
	}
	solve(l,mid);
	for (int j=l+1,Rmx=mid;j<=Rmx;j++) {
		ll now=f[j]+W_2.W(j+1,r)+cost_wqs;
		if (now<=f[r]) f[r]=now,p[r]=j;
	}
	solve(mid,r);
}

inline void larsch() {
	f[0]=0;W_1.init();W_2.init();
	f(i,1,n) f[i]=W_1.W(1,i)+cost_wqs,p[i]=0;
	solve(0,n);
}

inline bool check(ll x) {
	cost_wqs=x;larsch();
	f(i,1,n) g[i]=g[p[i]]+1;
	return g[n]>=k;
}

inline void solve() {
	read(n);read(k);k=min(k+1,n);
	f(i,1,n) read(a[i]),mp[i]=a[i];
	sort(mp+1,mp+1+n);
	int L=unique(mp+1,mp+1+n)-mp-1;
	f(i,1,n) a[i]=lower_bound(mp+1,mp+1+L,a[i])-mp;
	reverse(a+1,a+1+n);
	ll l=0,r=n*n,res=0;
	while (l<=r) {
		ll mid=l+r>>1;
		if (check(mid)) res=mid,l=mid+1;
		else r=mid-1;
	}
	check(res);
	printf("%lld\n",f[n]-k*res);
}

int main() {
	int T;
	read(T);
	while (T--) solve();
	return 0;
}

Graph Weighting

solution

这个题好厉害。

\(n,m,q,L\) 同阶,分析复杂度时统一用 \(n\) 表示。

首先不难发现点双内边权一定相等,于是将所有点双取出,令点数为 \(x\),边数为 \(y\),则边权均为 \(w\),为生成树权值贡献 \((x-1)\times w\),为代价贡献 \(y\times w^2\)。

套路地按照 \(x\) 分组,由于每个点双代价是凸的所以将相同 \(x\) 的所有点双对应凸包用闵可夫斯基和合并,相当于用堆维护最小差分,这一部分时间复杂度 \(O(n\log^2 n)\)(调和级数加上堆)。

由于合并出来的也是凸的,但是最后得到答案的背包数组不一定是凸的,所以对一类 \(x\) 合并时需要按照 \(\bmod x\) 分组,由于合并出来背包是凸的,所以可以决策单调性优化做到单次 \(O(n\log n)\) 或单次 \(O(n)\) 合并。

经典结论是最多有 \(\sqrt n\) 个不同 \(x\),所以总复杂度 \(O(n\sqrt n\log n)\) 或 \(O(n\sqrt n)\)。

Code
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr ll inf=1e18+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,m,q,V;
int head[N],Etot=1;
struct Edge{int next,to;}e[N<<1];

inline void add(int u,int v) {
	e[++Etot]={head[u],v};
	head[u]=Etot;
}

int dfn[N],low[N],dfn_idx,col_idx;
int st1[N],top1,st2[N<<1],top2;
int eL[N],vL[N],dide[N<<1];

inline void tarjan(int now) {
	dfn[now]=low[now]=++dfn_idx;
	st1[++top1]=now;
	for (int i=head[now];i;i=e[i].next) {
		int v=e[i].to;
		if (!dide[i]) {
			dide[i]=dide[i^1]=1;
			st2[++top2]=i;
		}
		if (!dfn[v]) {
			tarjan(v);
			low[now]=min(low[now],low[v]);
			if (low[v]==dfn[now]) {
				col_idx++;
				for (int x=0;x!=v;) {
					x=st1[top1--];
					vL[col_idx]++;
				}
				for (int x=0;x!=i;) {
					x=st2[top2--];
					eL[col_idx]++;
				}
			}
		}
		else low[now]=min(low[now],dfn[v]);
	}
}

ll f[N],g[N],A[N],B[N];
inline void solve(int l,int r,int L,int R) {
	if (l>r) return;
	int mid=l+r>>1,M=L,Rmx=min(mid,R);ll res=inf;
	f(i,L,Rmx) {
		ll now=A[i]+g[mid-i];
		if (now<=res) res=now,M=i;
	}
	B[mid]=min(B[mid],res);
	solve(l,mid-1,L,M);
	solve(mid+1,r,M,R);
}

struct Node{
	ll val,t,num;
	inline bool operator <(const Node &x)const {return val>x.val;}
};
priority_queue<Node> que[N];

inline void solve(int x) {
	f(i,1,q) g[i]=inf;
	priority_queue<Node> tmp;swap(tmp,que[x]);
	int Len=0;
	for (int i=x;i<=q&&!tmp.empty();i+=x) {
		Node now=tmp.top();
		tmp.pop();Len++;
		g[Len]=g[Len-1]+now.val;
		if (now.num!=V) {
			ll num=now.num+1;
			tmp.push({(num*num-(num-1)*(num-1))*now.t,now.t,num});
		}
	}
	for (int r=0,Rmx=min(x-1,q);r<=Rmx;r++) {
		int cnt=0;
		for (int i=r;i<=q;i+=x,cnt++) A[cnt]=B[cnt]=f[i];
		solve(0,cnt-1,0,cnt-1);
		for (int i=r,t=0;i<=q;i+=x,t++) f[i]=B[t];
	}
}

int main() {
	read(n);read(m);read(q);read(V);
	f(i,1,m) {
		int u,v;read(u);read(v);
		add(u,v);add(v,u);
	}
	tarjan(1);
	f(i,1,col_idx) que[vL[i]].push({eL[i],eL[i],1});
	f(i,1,q) f[i]=inf;
	f(i,1,n) if (!que[i].empty()) solve(i);
	f(i,0,q) printf("%lld ",f[i]==inf?-1:f[i]);
	return 0;
}

[PA 2022] Nawiasowe podziały

solution

同样地发现权值函数满足四边形不等式,于是果断 wqs 二分然后 larsch,现在唯一的问题就是如何 \(O(1)\) 计算权值函数或者移指针。

以左括号为例,发现若在左端加入一个左括号,那么它能够匹配的右括号是唯一的,那么预处理对每一对配对的左右括号染色,并将相邻配对括号染成同种颜色,那么移动指针加入一个括号时能够增加的子串数量即为相同颜色的配对数量,用桶维护可以 \(O(1)\) 移动指针。

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

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,k;
char ch[N];
int col[N],pei[N],col_idx;

ll f[N],g[N],cost_wqs;
int p[N];

struct W_get {
	int l,r,cnt[N];
	ll res;
	
	inline void init() {
		f(i,1,col_idx) cnt[i]=0;
		l=1;r=1;res=0;
	}
	
	inline ll W(int qL,int qR) {
		while (l>qL) {
			l--;
			if (ch[l]=='('&&pei[l]<=r) {
				cnt[col[l]]++;
				res+=cnt[col[l]];
			}
		}
		while (r<qR) {
			r++;
			if (ch[r]==')'&&pei[r]>=l) {
				cnt[col[r]]++;
				res+=cnt[col[r]];
			}
		}
		while (l<qL) {
			if (ch[l]=='('&&pei[l]<=r) {
				res-=cnt[col[l]];
				cnt[col[l]]--;
			}
			l++;
		}
		while (r>qR) {
			if (ch[r]==')'&&pei[r]>=l) {
				res-=cnt[col[r]];
				cnt[col[r]]--;
			}
			r--;
		}
		return res;
	}
}W_1,W_2;

inline void solve(int l,int r) {
	if (r-l<=1) return;
	int mid=l+r>>1;
	for (int j=p[l],Rmx=min(p[r],mid-1);j<=Rmx;j++) {
		ll now=f[j]+W_1.W(j+1,mid)+cost_wqs;
		if (now<=f[mid]) f[mid]=now,p[mid]=j;
	}
	solve(l,mid);
	for (int j=l+1,Rmx=mid;j<=Rmx;j++) {
		ll now=f[j]+W_2.W(j+1,r)+cost_wqs;
		if (now<=f[r]) f[r]=now,p[r]=j;
	}
	solve(mid,r);
}

inline void larsch() {
	f[0]=0;W_1.init();W_2.init();
	f(i,1,n) f[i]=W_1.W(1,i)+cost_wqs,p[i]=0;
	solve(0,n);
}

inline bool check(ll x) {
	cost_wqs=x;
	larsch();
	f(i,1,n) g[i]=g[p[i]]+1;
	if (g[n]==k) {
		printf("%lld\n",f[n]-x*k);
		exit(0);
	}
	return g[n]>k;
}

int main() {
	read(n);read(k);
	scanf("%s",ch+1);
	static int st[N],top;
	f(i,1,n) {
		if (ch[i]=='(') st[++top]=i,pei[i]=n+1;
		else if (top) {
			pei[i]=st[top];pei[st[top]]=i;
			if (col[st[top]-1]) col[i]=col[st[top]]=col[st[top]-1];
			else col[i]=col[st[top]]=++col_idx;
			top--;
		}
	}
	ll l=0,r=1e10,res=0;
	while (l<=r) {
		ll mid=l+r>>1;
		if (check(mid)) res=mid,l=mid+1;
		else r=mid-1;
	}
	check(res);
	printf("%lld\n",f[n]-k*res);
	return 0;
}

10.14

Another Exercise on Graphs (hard version)

solution

二分后可以转化成边权为 \(0\) 或 \(1\),判定 \(u\) 到 \(v\) 最短路长度是否小于 \(k\)。

考虑这个过程,本质上需要做的事情是:从小到大考虑每条边,将一条边边权从 \(1\) 改成 \(0\),维护全源最短路,当 \(u\) 到 \(v\) 最短路在加入这条边后变成了 \(k-1\) 时,说明询问 \((u,v,k)\) 的答案为这条边边权。

维护这个全源最短路是唯一要解决的问题,考虑所有边权为 \(0\) 的边,若加入一条边 \((u,v)\) 前 \(u\) 和 \(v\) 已经在一个连通块内,那么加入这条边不会给最短路带来任何影响,反之会使连通块数量减少,所以有意义的边只有 \(n-1\) 条,于是只考虑这些有意义的边,每次加边时用这条边的两个端点松弛任意两点最短路即可,答案存在一个数组里 \(O(1)\) 回答。

时间复杂度 \(O(n^3+q)\)。

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=400+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,m,q;
int ans[N][N][N],dis[N][N];
struct Edge{int u,v,w;}e[N*N];
inline bool cmp(Edge x,Edge y) {return x.w<y.w;}

int f[N];
inline int find(int x) {
	if (f[x]==x) return x;
	return f[x]=find(f[x]);
}

inline void solve() {
	read(n);read(m);read(q);
	f(i,1,n) f(j,1,n) dis[i][j]=(i!=j)*inf;
	f(i,1,m) {
		int u,v,w;
		read(u);read(v);read(w);
		e[i]={u,v,w};
		dis[u][v]=dis[v][u]=1;
	}
	f(k,1,n) f(i,1,n) f(j,1,n) if ((i!=j)&&(i!=k)&&(j!=k)) dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
	sort(e+1,e+1+m,cmp);
	f(i,1,n) f[i]=i;
	f(i,1,m) {
		int u=e[i].u,v=e[i].v,w=e[i].w;
		if (find(u)==find(v)) continue;
		f[find(u)]=find(v);
		dis[u][v]=0;ans[u][v][0]=ans[v][u][0]=w;
		f(i,1,n) f(j,1,n) if (i^j) {
			int val=min(dis[i][u]+dis[v][j],dis[i][v]+dis[u][j]);
			if (dis[i][j]>val) {
				dis[i][j]=val;
				ans[i][j][val]=ans[j][i][val]=w;
			}
		}
	}
	while (q--) {
		int u,v,k;
		read(u);read(v);read(k);
		printf("%d ",ans[u][v][k-1]);
	}
	putchar(10);
}

int main() {
	int T;
	read(T);
	while (T--) solve();
	return 0;
}

MST in Modulo Graph

solution

去掉点权相同的点。

对于点权 \(x,a,b\),若有 \(kx\le a<b\le (k+1)x\),那么点权为 \(b\) 的点一定不可能连上点权为 \(x\) 的点,因为边 \((a,x),(a,b)\) 边权均小于 \((b,x)\),所以对于每个点权枚举 \(k\),找到点权 \(\ge kx\) 的第一个点进行连边,再暴力做 Kruskal 即可。

\(n,V\) 同阶,时间复杂度 \(O(n\log^2 n)\)。

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=5e5+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,m;
int p[N],pt[N],nxt[N];
inline int W(int x,int y) {return max(p[x],p[y])%min(p[x],p[y]);}

int f[N];
struct Edge{int u,v,w;}e[N*30];
inline bool cmp(Edge x,Edge y) {return x.w<y.w;}

inline int find(int x) {
	if (f[x]==x) return x;
	return f[x]=find(f[x]);
}

inline ll Kruskal() {
	f(i,1,n) f[i]=i;
	sort(e+1,e+1+m,cmp);
	ll sum=0;
	f(i,1,m) {
		int u=find(e[i].u),v=find(e[i].v);
		if (u^v) f[u]=v,sum+=e[i].w;
	}
	return sum;
}

inline void solve() {
	read(n);
	int V=0;m=0;
	f(i,1,n) read(p[i]),V=max(V,p[i]),pt[p[i]]=i;
	F(i,V,1) nxt[i]=(pt[i]?pt[i]:nxt[i+1]);
	f(i,1,V) if (pt[i]) for (int j=i;j<=V;j+=i) {
		if (j==i) {if (nxt[j+1]) e[++m]={pt[i],nxt[j+1],W(pt[i],nxt[j+1])};}
		else {if (nxt[j]) e[++m]={pt[i],nxt[j],W(pt[i],nxt[j])};}
	}
	printf("%lld\n",Kruskal());
	f(i,1,V) pt[i]=nxt[i]=0;
}

int main() {
	int T;
	read(T);
	while (T--) solve();
	return 0;
}

Candy Piles

solution

按 \(a\) 从大到小排序。

令 \(f_{i,j}\) 表示还剩下第 \([i,n]\) 堆,进行 \(j\) 次操作二时状态是否先手必胜。

可以看作在网格图上的移动,初始在 \((1,1)\),第 \(i\) 列可行范围是 \([i,a_i]\),每次向上或向右走,走出边界者输。

不难发现 \(f_{i,j}=f_{i+1,j+1}\),于是找到最大的 \(x\) 满足 \((x,x)\) 在边界内,那么 \((1,1)\) 状态和 \((x,x)\) 相同,而在 \((x,x)\) 只能向上或向右走,对两个方向判断即可。

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=1e5+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n;
int a[N];

int main() {
	read(n);
	f(i,1,n) read(a[i]);
	sort(a+1,a+1+n,[](int x,int y){return x>y;});
	f(i,1,n) if (a[i+1]<i+1) {
		int flag1=((a[i]-i)&1);
		int now=i;
		while (a[now+1]>=i) now++;
		int flag2=((now-i)&1);
		if (flag1|flag2) puts("First");
		else puts("Second");
		return 0;
	}
	return 0;
}

Moving Pieces on Graph

solution

无脑最短路树。

考虑经过非树边,经过非树边 \((u,v)\) 的答案为 \(f_{u}+g_{v}+dis(s,t)+1\),其中 \(f_{u}\) 表示去掉 \(t\) 后 \(u\) 到 \(s\) 的最短距离,\(g_v\) 表示去掉 \(s\) 后 \(v\) 到 \(t\) 的最短距离,\(dis(u,v)\) 表示 \(u\) 到 \(v\) 最短路长度。

考虑不经过非树边,考虑每个度数不小于 \(3\) 的点 \(x\) 进行中转,若它是 \(s\) 到 \(t\) 最短路上经过的点(不包括 \(s,t\)),贡献为 \(dis(s,t)\times 2+2\),否则贡献为 \(dis(s,t)\times 2+\min (dis(s,x),dis(t,x))\times 4+4\)。

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

Code
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,m,s,t;
int d[N],f[N],did[N];
int dis1[N],dis2[N],dis3[N],dis4[N];

int head[N],vis[N<<1],E_tot=1;
struct Edge{int next,to;}e[N<<1];

inline void add(int u,int v) {
	e[++E_tot]={head[u],v};
	head[u]=E_tot;
}

inline void bfs(int *dis,int st,int op=0) {
	queue<int> q;
	f(i,1,n) dis[i]=inf;
	dis[st]=0;f[st]=0;
	q.push(st);
	while (!q.empty()) {
		int now=q.front();
		q.pop();
		for (int i=head[now];i;i=e[i].next) {
			int v=e[i].to;
			if (v==(s^t^st)&&op) continue;
			if (dis[v]<=dis[now]+1) continue;
			if (st==s&&!op) vis[i]=vis[i^1]=1,f[v]=now;
			dis[v]=dis[now]+1;
			q.push(v);
		}
	}
}

int main() {
	read(n);read(m);read(s);read(t);
	f(i,1,m) {
		int u,v;read(u);read(v);
		d[u]++;d[v]++;add(u,v);add(v,u);
	}
	int flag=(m==n-1);
	f(i,1,n) flag&=(d[i]<=2);
	if (flag) return puts("-1"),0;
	bfs(dis3,s,1);bfs(dis4,t,1);bfs(dis2,t);bfs(dis1,s);
	int ans=inf;
	f(u,1,n) for (int i=head[u];i;i=e[i].next) if (!vis[i]&&dis3[u]!=inf&&dis4[e[i].to]!=inf) {
		int v=e[i].to;
		ans=min(ans,dis1[t]+dis3[u]+dis4[v]+1);
	}
	int now=f[t];
	while (now^s) did[now]=1,now=f[now];
	f(i,1,n) if (d[i]>=3) {
		if (did[i]) ans=min(ans,dis1[t]*2+2);
		else ans=min(ans,dis1[t]*2+min(dis1[i],dis2[i])*4+4);
	}
	printf("%d\n",ans);
	return 0;
}

/*
6 8 5 4
2 1
3 1
4 3
5 4
6 2
3 6
4 6
6 1

6

4 4 1 2
1 2
2 3
3 4
4 2

6

10 12 1 3
2 1
3 1
4 3
5 4
6 3
7 6
8 6
9 4
10 7
10 2
8 3
9 3

6

10 12 6 10
2 1
3 2
4 3
5 1
6 5
7 4
8 5
9 4
10 6
3 8
5 9
9 6

5

10 12 4 7
2 1
3 2
4 2
5 3
6 4
7 4
8 1
9 4
10 8
6 2
5 1
5 8

6
*/

10.15

BBQ Hard

solution

神秘转化。

不难看出题目要求 \(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{i-1}\binom{a_i+a_j+b_i+b_j}{a_i+a_j}\),转化为格路计数,求 \((0,0)\) 走到 \((a_i+a_j,b_i+b_j)\) 方案数。

不妨换成求 \((-a_i,-b_i)\) 走到 \((a_j,b_j)\) 方案数,于是对于每个起点 \((-a_i,-b_i)\) 加上贡献 \(1\),\(O(V^2)\) dp 求出每个点方案数,再枚举 \(j\) 加上即可。

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

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int V=2000;
constexpr int inf=1e9+10;

constexpr int mod=1e9+7;
constexpr int inv2=(mod+1)>>1;
inline int ad(int x,int y) {return ((x+y>=mod)?(x+y-mod):(x+y));}
inline void add(int &x,int y) {x=ad(x,y);}

inline ll qpow(ll a,int b) {
	ll res=1;
	while (b) {
		if (b&1) res=res*a%mod;
		a=a*a%mod;b>>=1;
	}
	return res;
}

ll fac[N],inv[N];
inline void init(int n) {
	fac[0]=1;
	f(i,1,n) fac[i]=fac[i-1]*i%mod;
	inv[n]=qpow(fac[n],mod-2);
	F(i,n,1) inv[i-1]=inv[i]*i%mod;
}

inline ll C(int n,int m) {
	if (n<0||m<0||n<m) return 0;
	return fac[n]*inv[m]%mod*inv[n-m]%mod;
}

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n;
int a[N],b[N];
int f[V*2+10][V*2+10];

int main() {
	read(n);init(V*4);
	f(i,1,n) {
		read(a[i]);read(b[i]);
		f[V-a[i]][V-b[i]]++;
	}
	f(i,0,V*2) f(j,0,V*2) {
		if (i) add(f[i][j],f[i-1][j]);
		if (j) add(f[i][j],f[i][j-1]);
	}
	ll ans=0;
	f(i,1,n) {
		ans+=f[V+a[i]][V+b[i]];
		ans-=C((a[i]+b[i])*2,a[i]*2);
	}
	printf("%d\n",(int)(((ans%mod+mod)*inv2)%mod));
	return 0;
}

Rabbit Exercise

solution

考虑相邻三个数 \(x,y,z\),对于 \(y\) 进行操作有一半概率变成 \(x,2x-y,z\),一半概率变成 \(x,2z-y,z\),则期望下变成 \(x,x+z-y,z\)。

经典地差分一下,那么差分从 \(y-x,z-y\) 变成 \(z-y,y-x\),相当于交换相邻差分,所以直接转成置换快速幂即可。

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

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;int f=1;
	char ch=getchar();
	while (ch<48) {if(ch=='-')f=-1;ch=getchar();}
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();x*=f;
}

int n,m;
int a[N];
ll k,d[N],f[N],g[N];

inline void solve(ll *a,ll *b) {
	static ll c[N];
	f(i,1,n) c[i]=a[b[i]];
	f(i,1,n) a[i]=c[i];
}

inline void qpow(ll b) {
	while (b) {
		if (b&1) solve(g,f);
		solve(f,f);b>>=1;
	}
}

int main() {
	read(n);n--;
	f(i,1,n+1) read(a[i]);
	f(i,1,n) d[i]=a[i+1]-a[i];
	f(i,1,n) f[i]=i,g[i]=i;
	scanf("%d%lld",&m,&k);
	while (m--) {int x;read(x);swap(f[x],f[x-1]);}
	qpow(k);
	printf("%d\n",a[1]);
	solve(d,g);d[1]+=a[1];
	f(i,1,n) printf("%lld\n",d[i]+=d[i-1]);
	return 0;
}

Decrementing

solution

思维题,好难www。

依次考虑这几种情况:

  • 若出现了 \(1\),只需要判定所有数减去 \(1\) 的和的奇偶性即可;
  • 若出现了奇数个偶数,那么对于一个偶数减 \(1\),后手无论如何都无法改变偶数数量奇偶性,所以先手必胜;
  • 否则出现偶数个偶数,若奇数个数超过 \(1\),则先手无论如何都改变不了偶数数量奇偶性,先手必输,否则找到唯一一个奇数将其减 \(1\) 并所有数除以它们的 \(\gcd\),递归检验。

每次递归值域减半,所以复杂度 \(O(n\log ^2 V)\)。

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n;
int a[N];
inline int gcd(int x,int y) {return y?gcd(y,x%y):x;}

int main() {
	read(n);
	f(i,1,n) read(a[i]);
	int d=1;
	while (1) {
		int c0=0,c1=0;
		ll sum=0;
		f(i,1,n) {
			if (a[i]^1) {
				if (a[i]&1) c1++;
				else c0++;
			}
			sum+=a[i]-1;
		}
		if ((c0+c1)^n) {
			if ((sum&1)==d) puts("First");
			else puts("Second");
			return 0;
		}
		if (c0&1) {
			puts(d?"First":"Second");
			return 0;
		}
		if (c1!=1) {
			puts(d?"Second":"First");
			return 0;
		}
		int g=0;
		f(i,1,n) {
			if (a[i]&1) a[i]--;
			if (!g) g=a[i];
			else g=gcd(a[i],g);
		}
		f(i,1,n) a[i]/=g;
		d^=1;
	}
	return 0;
}

Rearranging

solution

等价于给不互质的位置连双向边,给图定向成 DAG ,使最大拓扑序最小。

然后瞎猜一个结论,每个位置按去往点的大小排序,每次找没遍历过的最小点 dfs,发现很优秀根本 hack 不掉。

交一发,我草,直接过了。感性理解就是尽可能让小的点限制大的点。

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

Code
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<bitset>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2000+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,idx;
int a[N];
bitset<N> e[N],vis;
vector<int> T[N];

inline void dfs(int now) {
	vis.set(now);
	f(i,1,n) if (!vis[i]&&e[now][i]) T[now].push_back(i),dfs(i);
}

inline int gcd(int x,int y) {return y?gcd(y,x%y):x;}
int main() {
	read(n);
	f(i,1,n) read(a[i]);
	sort(a+1,a+1+n);
	f(i,1,n) f(j,i+1,n) if (gcd(a[i],a[j])^1) e[i].set(j),e[j].set(i);
	priority_queue<int> q;
	f(i,1,n) if (!vis[i]) dfs(i),q.push(i);
	while (!q.empty()) {
		int now=q.top();q.pop();
		printf("%d ",a[now]);
		for (int v:T[now]) q.push(v);
	}
	return 0;
}

[AMPPZ 2019] The Great Drone Show

solution

首先这是平面图,根据典中典 trick 直接每次找到度数最小的点并给其边定向为出边,再删去这个点,那么根据平面图经典结论每个点最多有 \(5\) 条出边。

于是每个点维护它不断边情况下的可移动区间,这里只需要考虑入边带来的限制即可,每次单点修改只需要对出边指向的点改变限制即可,用 set 维护可以做到单次 \(O(\log n)\)。

于是可以得到每条边的删除时间,直接做 Kruskal 重构树或最大生成树即可 \(O(\log n)\) 回答一个询问。

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

代码细节有点多。

Code
#include<cstdio>
#include<algorithm>
#include<vector>
#include<bitset>
#include<queue>
#include<set>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#include<cmath>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=5e5+10;
constexpr int M=3e6+10;
constexpr ll inf=1e18+10;

inline void read(int &x) {
	x=0;int f=1;
	char ch=getchar();
	while (ch<48) {if(ch=='-')f=-1;ch=getchar();} 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();x*=f;
}

int n,m;
int d[N];

struct Point{int x,y;ll z;}p[N];
struct Edge1{int u,v,w,ok;}es[M];
vector<int> e[N],G[N];
bitset<N> vis_tp;

inline void topu() {
	struct PQ {
	int id,val;
	inline bool operator <(const PQ &x)const {return val>x.val;}
};
	priority_queue<PQ> q;
	f(i,1,n) q.push({i,d[i]});
	while (!q.empty()) {
		int now=q.top().id;q.pop();
		if (vis_tp[now]) continue;
		vis_tp.set(now);
		for (int id:e[now]) {
			int v=es[id].v;
			if (vis_tp[v]) continue;
			G[now].push_back(id);
			q.push({v,--d[v]});
		}
	}
}

int now_id;
struct Set_mx_mn {
	int u;
	struct LR{ll L,R;};
	__gnu_pbds::gp_hash_table<int,int> Len;
	__gnu_pbds::gp_hash_table<int,LR> dis;
	
	struct Node_mx {
		int id;ll val;
		inline bool operator <(const Node_mx &x)const {return (val==x.val)?(id<x.id):(val<x.val);}
	};
	set<Node_mx> R;
	
	struct Node_mn {
		int id;ll val;
		inline bool operator <(const Node_mn &x)const {return (val==x.val)?(id<x.id):(val>x.val);}
	};
	set<Node_mn> L;
	
	inline void clear() {
		L.clear();R.clear();
		Len.clear();dis.clear();
	}
	
	long long isqrt(long long x) {
		long long r = sqrtl((long double)x);
		while ((__int128)(r + 1) * (r + 1) <= x) ++r;
		while ((__int128)r * r > x) --r;
		return r;
	}
	
	inline LR calc(int v,ll k) {
		ll dx=abs(p[u].x-p[v].x),dy=abs(p[u].y-p[v].y);
		k=k*k-dx*dx-dy*dy;
		ll d=isqrt(k);
		return {p[v].z-d,p[v].z+d};
	}
	
	inline void del(int v) {
		int id=Len[v];
		es[id].ok=now_id;
		LR y=dis[v];
		L.erase({v,y.L});R.erase({v,y.R});
		Len[v]=-1;
	}
	
	inline void chk() {
		ll now=p[u].z;
		while (!L.empty()&&L.begin()->val>now) del(L.begin()->id);
		while (!R.empty()&&R.begin()->val<now) del(R.begin()->id);
	}
	
	inline void update(int v) {
		if (Len[v]==-1) return;
		LR x=calc(v,es[Len[v]].w),y=dis[v];
		L.erase({v,y.L});L.insert({v,x.L});
		R.erase({v,y.R});R.insert({v,x.R});
		dis[v]=x;chk();
	}
}mp[N];

struct Edge2{int v,w;};
vector<Edge2> T[N];

struct DSU {
	int f[N];
	inline void init() {f(i,1,n)f[i]=i;}
	inline int find(int x) {
		if (f[x]==x) return x;
		return f[x]=find(f[x]);
	}
	
	inline void merge(int u,int v) {
		u=find(u);v=find(v);
		if (u^v) f[u]=v;
	}
}ds;

int f[22][N],mx[22][N],dep[N];
inline void dfs(int now,int fa) {
	dep[now]=dep[fa]+1;
	f[0][now]=fa;
	f(i,1,20) {
		f[i][now]=f[i-1][f[i-1][now]];
		mx[i][now]=min(mx[i-1][now],mx[i-1][f[i-1][now]]);
	}
	for (Edge2 E:T[now]) {
		int v=E.v,w=E.w;
		if (v==fa) continue;
		mx[0][v]=w;
		dfs(v,now);
	}
}

inline void Kruskal() {
	sort(es+1,es+1+m*2,[](Edge1 x,Edge1 y){return x.ok>y.ok;});
	ds.init();
	f(i,1,m*2) {
		int u=es[i].u,v=es[i].v,w=es[i].ok;
		if (ds.find(u)==ds.find(v)) continue;
		ds.merge(u,v);
		T[u].push_back({v,w});
		T[v].push_back({u,w});
	}
	dfs(1,0);
}

inline void clear() {
	f(i,1,n) {
		d[i]=0;
		p[i]={0,0,0};
		e[i].clear();
		G[i].clear();
		T[i].clear();
		vis_tp.reset(i);
		mp[i].clear();
	}
	f(i,1,m*2) es[i]={0,0,0,0};
}

inline int query(int u,int v) {
	int ret=1e9;
	if (dep[u]<dep[v]) swap(u,v);
	int k=dep[u]-dep[v];
	while (k) {
		int d=__builtin_ctz(k&-k);k^=(k&-k);
		ret=min(ret,mx[d][u]);
		u=f[d][u];
	}
	if (u==v) return ret;
	F(i,20,0) if (f[i][u]^f[i][v]) {
		ret=min(ret,mx[i][u]);
		ret=min(ret,mx[i][v]);
		u=f[i][u];v=f[i][v];
	}
	ret=min(ret,mx[0][u]);
	ret=min(ret,mx[0][v]);
	return ret;
}

inline void solve() {
	read(n);
	f(i,1,n) read(p[i].x),read(p[i].y),p[i].z=0;
	read(m);
	int idx=0;
	f(i,1,m) {
		int u,v,w;read(u);read(v);read(w);
		es[++idx]={u,v,w,0};
		e[u].push_back(idx);
		es[++idx]={v,u,w,0};
		e[v].push_back(idx);
		d[u]++;d[v]++;
	}
	topu();
	f(i,1,n) mp[i].u=i;
	f(u,1,n) for (int id:G[u]) {
		int v=es[id].v;
		mp[v].Len[u]=id;
		mp[v].update(u);
	}
	int T;read(T);
	f(i,1,T) {
		now_id=i;
		int x,h;read(x);read(h);
		p[x].z+=h;
		mp[x].chk();
		for (int id:G[x]) mp[es[id].v].update(x);
	}
	for (int i=1;i<=idx;i+=2) {
		if (es[i].ok==0) es[i].ok=T+1;
		if (es[i+1].ok==0) es[i+1].ok=T+1;
		int t=min(es[i].ok,es[i+1].ok);
		es[i].ok=es[i+1].ok=t;
	}
	Kruskal();
	int q;read(q);
	f(i,1,q) {
		int u,v;read(u);read(v);
		int ret=query(u,v);
		printf("%d\n",ret==T+1?-1:ret);
	}
	clear();
}

int main() {
	int T;
	read(T);
	while (T--) solve();
	return 0;
}

[ICPC 2022 WF] 动物园管理

题意

给定一张 \(n\) 个点 \(m\) 条边的无向图,保证无重边无自环,每个点有初始点权和目标点权,你可以进行的操作如下:

  • 指定图中的一个简单环,沿环上某个方向循环移位一位。

你需要判定是否能够在若干次操作后使得每个点的点权均变成目标点权。

\(1\le n\le 4\times 10^5,0\le m\le 4\times 10^5\)。

solution

神秘群论,好厉害。

首先选中的环一定不能包含割边,所以将割边去掉,分别判断每个边双连通分量。

对于一个边双连通分量,我们依次考虑以下几种情况。

分量中初始点权与目标点权构成可重集不同

这种情况显然无解。

分量是一个简单环

那么操作只能选中这个环,能到达的状态是初始点权的循环移位,使用最小表示法即可线性判定。

分量中存在偶环

根据群论的知识,我们知道:在非简单环的边双连通图中,通过组合不同环上的循环置换,可以生成所有 \(3\)-循环,也就是说我们只需关心置换的奇偶性。

我们知道对于一个排列,其逆序对数奇偶性等于排列长度与置换环数量之差的奇偶性,因为将该排列归位需要 \(l-c\) 次交换(\(l,c\) 分别为序列长度,置换环数量),而一次交换必然改变排列逆序对的奇偶性。

那么对于偶环操作,相当于进行奇数次交换两个位置,那么这个排列的逆序对数奇偶性就可以改变,即可以生成奇置换,结合 \(3\)-循环能生成偶置换群,于是可以生成所有置换,那么推广到可重集情形上就一定可以从初始状态变到目标状态。

分量中不存在偶环

还是先考虑排列,对于一个奇环进行操作相当于进行偶数次交换两个位置,于是排列逆序对数奇偶性是固定的,所以需要判断初始排列与目标排列的逆序对数奇偶性是否相同。

扩展到可重集上,若初始序列中存在重复元素,目标状态的置换表示不唯一,交换两个相同元素的位置即可翻转奇偶性,因此奇偶性不再构成限制,必然可行,若不存在重复元素则与排列的判定相同。

实现细节

判断是否有偶环可以找出所有点双连通分量,若一个点双连通分量有偶数个点或分量中边数大于点数,则一定存在偶环,因为若不存在偶环,则每个点双连通分量要么是单条边,要么是一个奇环(否则同一点双连通分量的两个奇环可以拼出一个偶环)。

求逆序对奇偶性可以直接利用置换环数量做到线性。

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

Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=4e5+10;
constexpr int M=(N<<1);
constexpr int V=1e6+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

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

int head[N],E_tot=1;
struct Edge{int next,to;}e[M];

inline void add(int u,int v) {
	e[++E_tot]={head[u],v};
	head[u]=E_tot;
}

int did[M],vis[N];

namespace Edge_tarjan {
	int dfn[N],low[N],dfn_idx;
	inline void tarjan(int now,int fa) {
		dfn[now]=low[now]=++dfn_idx;
		for (int i=head[now];i;i=e[i].next) {
			int v=e[i].to;
			if (!dfn[v]) {
				tarjan(v,now);
				low[now]=min(low[now],low[v]);
				if (low[v]>dfn[now]) did[i]=did[i^1]=1;
			}
			else if (v^fa) low[now]=min(low[now],dfn[v]);
		}
	}
}

int Es,flag;
int dep[N];
vector<int> pt;

inline void EX() {
	puts("impossible");
	exit(0);
}

inline void dfs(int now,int fa) {
	dep[now]=dep[fa]+1;
	pt.push_back(now);
	vis[now]=1;
	for (int i=head[now];i;i=e[i].next) {
		if (did[i]) continue;
		int v=e[i].to;Es++;
		if (!vis[v]) dfs(v,now);
	}
}

inline void solve_min(int *s,int n) {
	static int p[N];
	f(i,1,n) s[i+n]=s[i];
	int i=1,j=2,k=0;
	while (i<=n&&j<=n) {
		for(k=0;k<n&&s[i+k]==s[j+k];k++);
		if (s[i+k]>s[j+k]) i=i+k+1;
		else j=j+k+1;
		if (i==j) j++;
	}
	i=min(i,j);
	f(k,1,n) p[k]=s[i+k-1];
	f(i,1,n) s[i]=p[i];
}

inline void solve1() {
	static int p1[N<<1],p2[N<<1];
	int cnt=0;
	for (int p:pt) {
		p1[++cnt]=a[p];
		p2[cnt]=b[p];
	}
	solve_min(p1,cnt);
	solve_min(p2,cnt);
	f(i,1,cnt) if (p1[i]^p2[i]) EX();
}

inline void solve2() {
	static int p1[N],p2[N],mp[V];
	int len=0,cnt=0;
	for (int x:pt) {
		if (!mp[a[x]]) mp[a[x]]=++cnt;
		if (!mp[b[x]]) mp[b[x]]=++cnt;
		p1[++len]=mp[a[x]];
		p2[len]=mp[b[x]];
	}
	int flag=0;
	static bool fc[N];
	f(i,1,len) if (!fc[i]) {
		int now=i;
		while (!fc[now]) fc[now]=1,now=p1[now];
		flag^=1;
	}
	f(i,1,len) fc[i]=0;
	f(i,1,len) if (!fc[i]) {
		int now=i;
		while (!fc[now]) fc[now]=1,now=p2[now];
		flag^=1;
	}
	f(i,1,len) fc[i]=0;
	for (int x:pt) mp[a[x]]=0;
	if (flag) EX();
}

int col_idx;
int ps[N],es[N];

namespace Point_tarjan {
	int dfn[N],low[N],dfn_idx;
	int st1[N],top1,st2[M],top2,dide[M];
	
	inline void tarjan(int now) {
		dfn[now]=low[now]=++dfn_idx;
		st1[++top1]=now;
		for (int i=head[now];i;i=e[i].next) {
			if (did[i]) continue;
			int v=e[i].to;
			if (!dide[i]) {
				dide[i]=dide[i^1]=1;
				st2[++top2]=i;
			}
			if (!dfn[v]) {
				tarjan(v);
				low[now]=min(low[now],low[v]);
				if (low[v]==dfn[now]) {
					col_idx++;
					for (int x=0;x!=v;) {
						x=st1[top1--];
						ps[col_idx]++;
					}
					ps[col_idx]++;
					for (int x=0;x!=i;) {
						x=st2[top2--];
						es[col_idx]++;
					}
				}
			}
			else low[now]=min(low[now],dfn[v]);
		}
	}
}

inline void solve(int x) {
	Es=flag=0;
	pt.clear();
	dfs(x,0);
	if (Es/2==pt.size()) return solve1();
	static int t[V];
	int ok=0;
	for (int x:pt) {
		if (t[a[x]]) ok=1;
		t[a[x]]++;
	}
	for (int x:pt) {
		if (!t[b[x]]) EX();
		t[b[x]]--;
	}
	Point_tarjan::tarjan(x);
	f(i,1,col_idx) {
		if ((ps[i]&1)==0) flag=1;
		if (es[i]>ps[i]) flag=1;
		ps[i]=es[i]=0;
	}
	col_idx=0;
	if (flag||ok) return;
	solve2();
}

int main() {
	read(n);read(m);
	f(i,1,n) read(a[i]),read(b[i]);
	f(i,1,m) {
		int u,v;read(u);read(v);
		add(u,v);add(v,u);
	}
	f(i,1,n) if (!Edge_tarjan::dfn[i]) Edge_tarjan::tarjan(i,0);
	f(i,1,n) if (!vis[i]) solve(i);
	puts("possible");
	return 0;
}
posted @ 2026-10-12 19:01  Ff472130  阅读(6)  评论(0)    收藏  举报