CWOI 图论专题

由于作者很菜,基本上什么都不会,所以这也算半篇学习笔记(

1. CF1163F. Indecisive Taxi Fee

终于过了,感动。

先随便找一条 \(1\to n\) 的最短路。记这个边集为 \(S\),分类讨论一下:

  • \(t\notin S\) 时,答案要么是原来的最短路,要么是经过这条边的最短路。可以预处理 \(1,n\) 到每个点的最短路。

  • \(t\in S\) 时,答案要么是原来最短路长度加上这条边的长度变化量,要么是不经过这条边的最短路。

不难发现,上述做法的难点在于求出删去某条边后的最短路。考虑这样一个做法:

对于新的最短路,可以发现至少存在一种方案,使得这条路径的一段前缀会和原来 \(1\to n\) 的最短路相同,一段后缀会和原来 \(n\to 1\) 的最短路相同,只有中间一段不在 \(1\to n\) 路径上。证明就是考虑假设超过一段不同,因为只会删去一条边,所以至少有一段是可以调整到原来的最短路上的,这一定不劣。

记删去 \(S\) 中第 \(i\) 条边 \(S_i\) 后的最短路长度为 \(ans_i\),考虑随便钦定一条不在 \(S\) 中的边 \((i,j)\),用经过 \((i,j)\) 的最短路去更新一些 \(ans\)(至于为什么只需要钦定一条边,可以看这里的证明)。

对于每个点 \(u\),记 \(l_u\) 表示表示最小的 \(t\),使得在某条 \(1\to u\) 的最短路上,\(S_t\) 是第一条 \(S\) 上的不在其中的边。记 \(r_u\) 表示最大的 \(t\),使得在某条 \(u\to n\) 的最短路上,\(S_t\) 是最后一条 \(S\) 上的不在其中的边。于是就可以用 \(\text{dist}(1,i)+w(i,j)+\text{dist}(j,n)\) 更新 \([l_i,r_j]\) 中的 \(ans\),用 \(\text{dist}(1,j)+w(i,j)+\text{dist}(i,n)\) 更新 \([l_j,r_i]\) 中的 \(ans\)

预处理 \(l_u,r_u\) 可以再跑一遍最短路转移,或者直接按距离排序。例如计算 \(l_u\) 时,若边 \((u,v)\notin S\)\(\text{dist}(1,u)+w(u,v)=\text{dist}(1,v)\),那么 \(l_u\gets\min(l_u,l_v)\)。注意 \(l,r\) 的初值。复杂度 \(\mathcal{O}(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,w,id,nxt;
}e[400005];
int tot,head[200005];
void add(int u,int v,int w,int id){
	e[++tot]=(edge){v,w,id,head[u]},head[u]=tot;
}
struct Info{
	int u,w,id;
}p[200005];
int n,m,q,d[200005],vis[200005];
void dijkstra(int s){
	for(int i=1;i<=n;i++)d[i]=inf,vis[i]=0;
	priority_queue<pii,vector<pii>,greater<pii> >q;
	d[s]=0,q.push(mk(d[s],s));
	while(!q.empty()){
		int u=q.top().se;q.pop();
		if(vis[u])continue;
		vis[u]=1;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v,w=e[i].w,id=e[i].id;
			if(d[u]+w<d[v]){
				d[v]=d[u]+w,q.push(mk(d[v],v));
				p[v]=(Info){u,w,id};
			}
		}
	}
}
int on[200005],l[200005],r[200005],d1[200005],dn[200005],o[200005];
int eu[200005],ev[200005],ew[200005],tmp[200005],ans[200005],ver[200005];
vector<int>ad[200005],dl[200005];
int cmp1(int x,int y){
	return d1[x]<d1[y];
}
int cmpn(int x,int y){
	return dn[x]<dn[y];
}
signed main(){
	n=read(),m=read(),q=read();
	for(int i=1,u,v,w;i<=m;i++){
		u=read(),v=read(),w=read();
		eu[i]=u,ev[i]=v,ew[i]=w;
		add(u,v,w,i),add(v,u,w,i);
	}
	dijkstra(1);int cnt=0,num=0;
	for(int i=1;i<=n;i++)d1[i]=d[i];
	for(int u=n;u!=1&&u!=0;u=p[u].u)tmp[++cnt]=p[u].id,ver[++num]=u;
	reverse(tmp+1,tmp+cnt+1);reverse(ver+1,ver+num+1);
	dijkstra(n);for(int i=1;i<=cnt;i++)on[tmp[i]]=i;
	for(int i=1;i<=n;i++)dn[i]=d[i];
	for(int i=1;i<=n;i++)l[i]=inf,r[i]=-inf;
	l[1]=1,r[1]=0;
	for(int i=1;i<=num;i++)l[ver[i]]=i+1,r[ver[i]]=i;
	for(int i=1;i<=n;i++)o[i]=i;
	sort(o+1,o+n+1,cmp1);
	for(int t=1;t<=n;t++){
		int u=o[t];
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v,w=e[i].w,id=e[i].id;
			if(!on[id]&&d1[v]==d1[u]+w)l[v]=min(l[v],l[u]);
		}
	}
	sort(o+1,o+n+1,cmpn);
	for(int t=1;t<=n;t++){
		int u=o[t];
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v,w=e[i].w,id=e[i].id;
			if(!on[id]&&dn[v]==dn[u]+w)r[v]=max(r[v],r[u]);
		}
	}
	for(int i=1;i<=m;i++){
		if(on[i])continue;
		int u=eu[i],v=ev[i],w=ew[i];
		if(l[u]<=r[v])ad[l[u]].push_back(d1[u]+w+dn[v]),dl[r[v]+1].push_back(d1[u]+w+dn[v]);
		if(l[v]<=r[u])ad[l[v]].push_back(d1[v]+w+dn[u]),dl[r[u]+1].push_back(d1[v]+w+dn[u]);		
	}
	multiset<int>s;s.insert(inf);
	for(int i=1;i<=cnt;i++){
		for(auto x:ad[i])s.insert(x);
		for(auto x:dl[i])s.erase(s.find(x));
		ans[i]=*s.begin();
	}
	while(q--){
		int t=read(),x=read(),u=eu[t],v=ev[t],w=ew[t];
		if(!on[t])printf("%lld\n",min({d1[n],d1[u]+x+dn[v],d1[v]+x+dn[u]}));
		else printf("%lld\n",min(ans[on[t]],d1[n]-w+x));
	}
	return 0;
}

2. P2619 [国家集训队] Tree I

wqs 二分。

3. P5633 最小度限制生成树

是上一题的严格弱化版,唯一需要注意的地方是怎么判无解。

4. P4180 [BJWC2010] 严格次小生成树

典。先随便求一颗最小生成树,一颗严格次小生成树一定是在它的基础上替换了一条边,因为多替换几次要么没影响等于没替换,要么会使得权值大于只替换一条的。枚举加上去的非树边,它与原树形成一个环,找这个环上除了它之外最大的且边权不同的边即可。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int fa[100005];
int find(int x){
	return ((x==fa[x])?x:fa[x]=find(fa[x]));
}
struct Node{
	int u,v,w;
	bool operator <(const Node &b)const{
		return w<b.w;
	}
}E[300005];
struct edge{
	int v,w,nxt;
}e[200005];
int tot,head[100005];
void add(int u,int v,int w){
	e[++tot]=(edge){v,w,head[u]},head[u]=tot;
}
struct Info{
	int x,y;
	Info():x(-inf),y(-inf){};
	Info(int _x,int _y):x(_x),y(_y){};
	Info operator +(const Info &b)const{
		if(x!=b.x)return (Info){max(x,b.x),max(max(y,b.y),min(x,b.x))};
		if(y!=b.y)return (Info){x,max(y,b.y)};
		return (Info){x,y};
	}
}f[22][100005];
int dep[100005],val[100005],pa[22][100005],used[300005];
void dfs(int u,int fa){
	dep[u]=dep[fa]+1,pa[0][u]=fa,f[0][u]=(Info){val[u],-inf};
	for(int i=1;i<=20;i++)pa[i][u]=pa[i-1][pa[i-1][u]];
	for(int i=1;i<=20;i++)f[i][u]=f[i-1][u]+f[i-1][pa[i-1][u]];
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v,w=e[i].w;if(v==fa)continue;
		val[v]=w;dfs(v,u);
	}
}
Info ask(int u,int v){
	if(dep[u]>dep[v])swap(u,v);
	Info ans=(Info){-inf,-inf};
	for(int i=20;i>=0;i--){
		if(dep[v]-dep[u]>=(1ll<<i))ans=ans+f[i][v],v=pa[i][v];
	}
	if(u==v)return ans;
	for(int i=20;i>=0;i--){
		if(pa[i][u]!=pa[i][v])ans=ans+f[i][u],ans=ans+f[i][v],u=pa[i][u],v=pa[i][v];
	}
	ans=ans+f[0][u],ans=ans+f[0][v];
	return ans;
}
signed main(){
	int n=read(),m=read();
	for(int i=1,u,v,w;i<=m;i++){
		u=read(),v=read(),w=read();
		E[i]=(Node){u,v,w};
	}
	sort(E+1,E+m+1);
	for(int i=1;i<=n;i++)fa[i]=i;
	int cnt=0,sum=0;
	for(int i=1;i<=m;i++){
		int u=E[i].u,v=E[i].v,w=E[i].w;
		int fu=find(u),fv=find(v);
		if(fu==fv)continue;
		fa[fu]=fv,sum+=w,used[i]=1;
		add(u,v,w),add(v,u,w);
		if((++cnt)==n-1)break;
	}
	val[1]=-inf;dfs(1,0);
	int ans=inf;
	for(int i=1;i<=m;i++){
		if(used[i])continue;
		int u=E[i].u,v=E[i].v,w=E[i].w;
		Info tmp=ask(u,v);
		if(tmp.x==-inf)continue;
		if(tmp.x!=w)ans=min(ans,sum-tmp.x+w);
		else if(tmp.y!=-inf)ans=min(ans,sum-tmp.y+w);
	}
	printf("%lld\n",ans);
	return 0;
}

5. CF152E. Garden

比较板的斯坦纳树。

问题就是有 \(k\) 个关键点,需要通过一些点把这些关键点连起来,选一个点需要代价,求最小代价。显然最后图一定会连成一棵树,令 \(f_{i,j,S}\) 表示 \((i,j)\) 为所在连通块的根,连通块内包含 \(S\) 点集内的关键点的最小代价。转移分两种:

  • \((i,j)\) 只选了一个儿子。即 \(f_{i,j,S}\gets f_{x,y,S}+a_{i,j}\)

  • \((i,j)\) 选了多个儿子。即 \(f_{i,j,S+T}\gets f_{i,j,S}+f_{i,j,T}-a_{i,j}(S\cap T=\varnothing)\)

解释一下。虽然第一种转移似乎没有考虑 \((i,j)\) 是关键点,且 \(S\) 不包含 \((i,j)\) 的情况。但第二种转移是可以得到一个对的最小值的,且第一种转移也没有使答案错误的变小,所以这样还是没问题的。

第二种转移是简单的,只需要枚举子集。发现第一种转移会有环,无法确定转移顺序。但第一种转移不会改变点集,只需要从小到大枚举 \(S\),跑 dij/spfa 即可。复杂度 \(\mathcal{O}(3^knm+2^knm\log nm)\)\(\mathcal{O}(3^knm+2^kn^2m^2)\),似乎在一般图上 spfa 会有更小的常数。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
const int dx[]={1,-1,0,0},dy[]={0,0,1,-1};
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,m,k,vis[205],f[205][155],a[105][105];pii p[205][155];
int id(int x,int y){
	return (x-1)*m+y;
}
int getx(int v){
	return (v-1+m)/m;
}
int gety(int v){
	return v-((v-1)/m)*m;
}
int in(int x,int y){
	return (x>=1&&x<=n&&y>=1&&y<=m);
}
void spfa(int msk){
	queue<pii>q;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			vis[id(i,j)]=1,q.push(mk(i,j));
		}
	}
	while(!q.empty()){
		int x=q.front().fi,y=q.front().se;q.pop();vis[id(x,y)]=0;
		for(int i=0;i<4;i++){
			int nx=x+dx[i],ny=y+dy[i];
			if(!in(nx,ny))continue;
			if(f[id(nx,ny)][msk]>f[id(x,y)][msk]+a[nx][ny]){
				f[id(nx,ny)][msk]=f[id(x,y)][msk]+a[nx][ny];
				p[id(nx,ny)][msk]=mk(2,id(x,y));
				if(!vis[id(nx,ny)])q.push(mk(nx,ny)),vis[id(nx,ny)]=1;
			}
		}
	}
}
int res[105][105];
void solve(int i,int msk){
	int I=p[i][msk].fi,MSK=p[i][msk].se;
	if(I==-1){
		if(msk!=-1)res[getx(i)][gety(i)]=1;
		return;
	}
	if(I==1)solve(i,MSK),solve(i,msk-MSK);
	else res[getx(i)][gety(i)]=1,solve(MSK,msk);
}
signed main(){
	n=read(),m=read(),k=read();
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			a[i][j]=read();
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			for(int t=0;t<(1ll<<k);t++){
				f[id(i,j)][t]=inf,p[id(i,j)][t]=mk(-1,-1);
			}
		}
	}
	for(int i=0,x,y;i<k;i++){
		x=read(),y=read();f[id(x,y)][1ll<<i]=a[x][y],p[id(x,y)][1ll<<i]=mk(-1,0);
	}
	for(int msk=1;msk<(1ll<<k);msk++){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				for(int t=(msk-1)&msk;t;t=(t-1)&msk){
					if(f[id(i,j)][t]+f[id(i,j)][msk-t]-a[i][j]<f[id(i,j)][msk]){
						f[id(i,j)][msk]=f[id(i,j)][t]+f[id(i,j)][msk-t]-a[i][j];
						p[id(i,j)][msk]=mk(1,t);
					}
				}
			}
		}
		spfa(msk);
	}
	int ans=1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(f[ans][(1ll<<k)-1]>f[id(i,j)][(1ll<<k)-1])ans=id(i,j);			
		}
	}
	printf("%lld\n",f[ans][(1ll<<k)-1]);
	solve(ans,(1ll<<k)-1);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(res[i][j])putchar('X');
			else putchar('.');
		}
		puts("");
	}
	return 0;
}

6. gym102483F. Fastest Speedrun

最小树形图,不会 tarjan。

7. CF1697F. Too Many Constraints

直接做的话这类似一个 k-sat 问题,不可做。但是 \(k\) 比较小,考虑把每个点拆点,转化成 2-sat。设 \(x_{i,j}=[a_i\ge j]\),题目限制可以转化成如下建模:

  • \(1\le a_i\le k\)\([x_{i,k+1}=1]\to [x_{i,k+1}=0],[x_{i,1}=0]\to [x_{i,1}=1]\)

  • \(a_i\) 单调不降。\(\forall j,[x_{i,j}=1]\to [x_{i+1,j}=1],[x_{i+1,j}=0]\to [x_{i,j}=0]\)

  • \(a_i\neq v\)\([x_{i,v}=1]\to [x_{i,v+1}=1],[x_{i,v+1}=0]\to [x_{i,v}=0]\)

  • \(a_i+a_j\le v\)\(\forall p+q=v+1,[x_{i,p}=1]\to [x_{j,q}=0],[x_{j,q}=1]\to [x_{i,p}=0]\)

  • \(a_i+a_j\ge v\)\(\forall p+q=v-1,[x_{i,p+1}=0]\to [x_{j,q+1}=1],[x_{j,q+1}=0]\to [x_{i,p+1}=1]\).

  • 由于 \(x\) 的定义,还需要连 \(\forall j,[x_{i,j+1}=1]\to [x_{i,j}=1],[x_{i,j}=0]\to [x_{i,j+1}=0]\)

跑 2-sat 即可。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,nxt;
}e[1500005];
int tot,head[500005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int n,m,k,all,rc,cur,top,dfn[500005],low[500005],st[500005],in[500005],bel[500005],siz[500005];
void tarjan(int u){
	dfn[u]=low[u]=++cur;st[++top]=u,in[u]=1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(!dfn[v])tarjan(v),low[u]=min(low[u],low[v]);
		else if(in[v])low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]){
		int v;rc++;
		do{
			v=st[top--],in[v]=0;
			bel[v]=rc,siz[rc]++;
		}while(u!=v);
	}
}
int id(int x,int y,int o){
	return (x-1)*(k+1)+y+o*all;
}
int chk(int x){
	return (1<=x&&x<=k+1);
}
int val[30005],ans[30005];
void solve(){
	n=read(),m=read(),k=read(),all=n*(k+1);
	tot=rc=top=cur=0;
	for(int i=1;i<=all*2;i++)head[i]=dfn[i]=low[i]=st[i]=in[i]=bel[i]=siz[i]=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=k;j++){
			add(id(i,j+1,1),id(i,j,1));
			add(id(i,j,0),id(i,j+1,0));
		}
	}
	for(int i=1;i<=n;i++){
		add(id(i,k+1,1),id(i,k+1,0));
		add(id(i,1,0),id(i,1,1));		
	}
	for(int i=1;i<n;i++){
		for(int j=1;j<=k;j++){
			add(id(i,j,1),id(i+1,j,1));
			add(id(i+1,j,0),id(i,j,0));	
		}
	}
	while(m--){
		int op=read();
		if(op==1){
			int i=read(),x=read();
			add(id(i,x,1),id(i,x+1,1));
			add(id(i,x+1,0),id(i,x,0));
		}
		else if(op==2){
			int i=read(),j=read(),x=read();
			for(int a=0;a<=k+1;a++){
				int b=x+1-a;
				if(chk(a)&&chk(b))add(id(i,a,1),id(j,b,0));
				if(chk(a)&&chk(b))add(id(j,b,1),id(i,a,0));
			}
		}
		else{
			int i=read(),j=read(),x=read();
			for(int a=0;a<=k+1;a++){
				int b=x-1-a;
				if(chk(a+1)&&chk(b+1))add(id(i,a+1,0),id(j,b+1,1));
				if(chk(b+1)&&chk(a+1))add(id(j,b+1,0),id(i,a+1,1));
			}
		}
	}
	for(int i=1;i<=all*2;i++)if(!dfn[i])tarjan(i);
	for(int i=1;i<=all;i++)if(bel[i]==bel[i+all])return puts("-1"),void();
	for(int i=1;i<=all;i++)val[i]=(bel[i]>bel[i+all]);
	for(int i=1;i<=n;i++){
		ans[i]=0;
		for(int j=1;j<=k+1;j++){
			if(val[id(i,j,0)])ans[i]=max(ans[i],j);
		}
		printf("%lld ",ans[i]);
	}
	puts("");
}
signed main(){
	int T=read();
	while(T--){
		solve();
	} 
	return 0;
}

8. P3209 [HNOI2010] 平面图判定

考虑把哈密顿回路拎出来,剩下的边要么在环内要么在环外。容易发现两条相交的边必须一个在内一个在外,这就变成了一个 2-sat 问题(似乎并查集也是可以的)。

两两枚举边判断是否相交是 \(\mathcal{O}(m^2)\) 的,但由平面图定理可知平面图上 \(m\le 3n-6\),复杂度变为 \(\mathcal{O}(n^2)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,nxt;
}e[1000005];
int tot,head[2005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int n,m,rc,cur,top,dfn[2005],low[2005],st[2005],in[2005],bel[2005];
void tarjan(int u){
	dfn[u]=low[u]=++cur;st[++top]=u,in[u]=1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(!dfn[v])tarjan(v),low[u]=min(low[u],low[v]);
		else if(in[v])low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]){
		int v;rc++;
		do{
			v=st[top--],in[v]=0;
			bel[v]=rc;
		}while(u!=v);
	}
}
int eu[10005],ev[10005],p[205],I[205];
void solve(){
	n=read(),m=read();set<pii>s;
	for(int i=1;i<=m;i++)eu[i]=read(),ev[i]=read();
	for(int i=1;i<=n;i++)p[i]=read(),I[p[i]]=i;
	if(m>3*n-6)return puts("NO"),void();
	for(int i=1;i<=m;i++)if(eu[i]>ev[i])swap(eu[i],ev[i]);
	for(int i=1;i<=m;i++)s.insert(mk(eu[i],ev[i]));
	for(int i=1;i<=n;i++)s.erase(mk(min(p[i],p[i%n+1]),max(p[i],p[i%n+1])));
	m=0;for(auto x:s)m++,eu[m]=min(I[x.fi],I[x.se]),ev[m]=max(I[x.fi],I[x.se]);
	tot=rc=cur=top=0;for(int i=1;i<=m+m;i++)head[i]=dfn[i]=low[i]=st[i]=in[i]=bel[i]=0;
	for(int i=1;i<=m;i++){
		for(int j=i+1;j<=m;j++){
			int u1=eu[i],v1=ev[i];
			int u2=eu[j],v2=ev[j];
			int I=i,J=j;
			if(u1>u2)swap(u1,u2),swap(v1,v2),swap(I,J);
			if(u1<u2&&v1<v2&&v1>u2)add(I,J+m),add(I+m,J),add(J,I+m),add(J+m,I);
		}
	}
	for(int i=1;i<=m+m;i++)if(!dfn[i])tarjan(i);
	for(int i=1;i<=m;i++)if(bel[i]==bel[i+m])return puts("NO"),void();
	puts("YES");
}
signed main(){
	int T=read();
	while(T--){
		solve();
	} 
	return 0;
}

9. flower garden

10. P9726 [EC Final 2022] Magic

“这不是简单题吗,为什么是黑啊?”——marblue

记位置 \(i\) 会对答案产生贡献当且仅当 \(a_{i-1}\neq a_i\),问题可以转化成在 \(2n\) 个点中选最多的贡献给答案,现在考虑限制。对于两个区间 \([l_1,r_1),[l_2,r_2)\),我们分情况讨论一下:

  • \([l_1,r_1)\in[l_2,r_2)\)。显然我们先执行 \([l_2,r_2)\) 再执行 \([l_1,r_1)\) 不劣。

  • \([l_1,r_1)\cap[l_2,r_2)=\varnothing\)。显然互不影响。

  • \([l_1,r_1)\cap[l_2,r_2)\neq\varnothing\)。令 \(l_1<l_2,r_1<r_2\),如果 \([l_1,r_1)\) 在后面则 \(r_1\) 会产生贡献,否则 \(l_2\) 会产生贡献。所以 \(r_1\)\(l_2\) 不能同时选择,这就是一个建图后的独立集。

发现题目保证 \(l_i,r_i\) 互不相等,故这是一个二分图,跑 bitset 优化 dinic/匈牙利即可,复杂度 \(\mathcal{O}(\dfrac{n^3}{w})/\mathcal{O}(\dfrac{n^2\sqrt{n}}{w})\)。似乎还可以用可持久化线段树优化建图的 dinic,好像是 \(\mathcal{O}(n^2\log n)\),但我不太会(

是 luogu 上的倒数第二劣解,不知道为什么 dinic 这么慢啊,可能复杂度假了?

证明一下为什么找到独立集后一定有对应解。无解当且仅当对于区间执行顺序的先后限制构成环。考虑找到这个环上随便一个区间 \([l_1,r_1)\),假设它取的是右端点 \(r_1\) 产生贡献,找到它下一个区间 \([l_2,r_2)\)\(l_1<l_2,r_1<r_2\),此时只能是 \(r_2\) 产生贡献。于是递归下去,因为 \(r_i\) 互不相同,所以环上的区间的 \(r\) 单调递增,故不会形成环。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,S,T,dep[10005];bitset<10005>g[10005];
int bfs(){
	for(int i=1;i<=T;i++)dep[i]=0;
	dep[S]=1;queue<int>q;q.push(S);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int v=g[u]._Find_first();v<=T;v=max(v+1,(int)g[u]._Find_next(v))){
			if(g[u][v]==0)continue;
			if(!dep[v])dep[v]=dep[u]+1,q.push(v);
		}
	}
	return dep[T];
}
int dinic(int u,int flow){
	if(u==T)return flow;
	int rest=0;
	for(int v=g[u]._Find_first();v<=T&&flow;v=max(v+1,(int)g[u]._Find_next(v))){
		if(g[u][v]==0)break;
		if(dep[v]!=dep[u]+1)continue;
		int k=dinic(v,min(flow,1));if(!k)dep[v]=0;
		rest+=k,flow-=k;if(k)g[u][v]=0,g[v][u]=1;
	}
	return rest;
}
int l[5005],r[5005],pos[10005];
signed main(){
	n=read(),S=2*n+1,T=2*n+2;
	for(int i=1;i<=n;i++){
		l[i]=read(),r[i]=read();
		pos[l[i]]=0,pos[r[i]]=1;
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(l[i]<l[j]&&l[j]<r[i]&&r[i]<r[j]){
				g[l[j]][r[i]]=1;
			}
		}
	}
	for(int i=1;i<=2*n;i++)if(pos[i]==0)g[S][i]=1;
	for(int i=1;i<=2*n;i++)if(pos[i]==1)g[i][T]=1;
	int ans=2*n;
	while(bfs())ans-=dinic(S,inf);
	printf("%d\n",ans);	
	return 0;
}

11. P4630 [APIO2018] 铁人两项

妈的,只会看题解了,一点思维没有。

这个不经过重复点的限制就很有意思,考虑点双。初始的思路是发现当 \(s,c,f\) 在一个点双里边的时候一定有解,然后大力分讨,按三个点所在点双之间的关系大力分类。不知道能不能做,就算可以也非常麻烦,需要干掉很多不合法的情况,完全不会写。

然后看题解,感觉好妙啊。就是假设我们枚举 \(s,f\),考虑所有 \(s,f\) 之间的简单路径,它们的交减去端点就是 \(c\) 的取值范围。然后发现建完点双之后一旦经过一个方点,这个方点上挂的所有圆点都可以计入答案。然后考虑给每个点赋权,方点赋成接的圆点数量,圆点是 \(-1\)然后你发现现在就相当于枚举两个圆点,然后路径求和。简单变成枚举路径上的点,计算有多少条路径经过它。复杂度 \(\mathcal{O}(n+m)\)

发现是模板题,没想出来,自闭。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,nxt;
}e[1600005];
int tot,head[200005],deg[200005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot,deg[v]++;
}
int rc,cur,top,dfn[200005],low[200005],st[200005];vector<int>dcc[200005];
void tarjan(int u){
	dfn[u]=low[u]=++cur;st[++top]=u;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(!dfn[v]){
			tarjan(v),low[u]=min(low[u],low[v]);
			if(low[v]==dfn[u]){
				int x;rc++;
				do{
					x=st[top--],dcc[rc].push_back(x);
				}while(x!=v);
				dcc[rc].push_back(u);
			}
		}
		else low[u]=min(low[u],dfn[v]);
	}
}
int n,m,ans,vis[200005],a[200005],siz[200005];
void calc(int u,int fa){
	siz[u]=(u<=n);
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		calc(v,u);siz[u]+=siz[v];
	}
}
void dfs(int u,int fa,int all){
	vis[u]=1;int sum=all*all;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		dfs(v,u,all);sum-=siz[v]*siz[v];
	}
	sum-=(all-siz[u])*(all-siz[u]);
	if(u<=n)sum-=1;
	ans+=sum*a[u];
}
signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++)a[i]=-1;
	for(int i=1,u,v;i<=m;i++){
		u=read(),v=read(),add(u,v),add(v,u);
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i])tarjan(i),top--;
	}
	for(int i=1;i<=n;i++){
		if(!deg[i])dcc[++rc].push_back(i);
	}
	tot=0;for(int i=1;i<=n;i++)head[i]=0;
	for(int i=1;i<=rc;i++){
		a[i+n]=(int)dcc[i].size();
		for(auto x:dcc[i])add(x,i+n),add(i+n,x);
	}
	for(int i=1;i<=n+rc;i++){
		if(!vis[i])calc(i,0),dfs(i,0,siz[i]);
	}
	printf("%lld\n",ans);
	return 0;
}

12. CF487E. Tourists

因为题目要求不经过重复点,考虑点双。根据点双的定义,路径上经过的所有点双里面的最小值都可以取到,可以缩点。

但是点双是会有重复点的,不能直接缩。考虑圆方树。对于所有点双,建一个对应的虚拟点,称为方点;把这个点双里的所有点全部向这个点连边,称为圆点。显然方点与方点间由圆点相连,这就构成了一棵树。

对这棵树树剖,定义圆点权值为题目中所给,方点权值为它对应点双内所有点权值最小值,原问题等价于求这棵树对应路径上经过的所有点的点权最小值。这样定义在修改时不太好做,因为所有包含它的方点都会受到影响。考虑修改方点点权定义为它所有儿子点权的最小值,这样每次修改时只用改它父亲这一个方点了。可以对每个方点开 multiset 来维护。

有一种特殊情况需要考虑:当 \(u,v\)\(\text{lca}\) 为方点时,需要加上 \(fa_{\text{lca}(u,v)}\) 的点权。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,nxt;
}e[500005];
int tot,head[200005],deg[200005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot,deg[v]++;
}
int cnt;vector<int>dcc[200005];
int t,dfn[200005],low[200005],tp,S[200005];
void tarjan(int u){
	dfn[u]=low[u]=++t;
	S[++tp]=u;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
			if(low[v]==dfn[u]){
				cnt++;
				for(int x=0;x!=v;tp--){
					x=S[tp],dcc[cnt].push_back(x);
				}
				dcc[cnt].push_back(u);
			}
		}
		else low[u]=min(low[u],dfn[v]);
	}
}
int siz[200005],son[200005],pa[200005],dep[200005];
int n,m,q,a[200005];multiset<int>s[200005];
void dfs1(int u,int fa){
	siz[u]=1,son[u]=0,pa[u]=fa,dep[u]=dep[fa]+1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		if(v<=n)s[u].insert(a[v]);
		dfs1(v,u);siz[u]+=siz[v];
		if(siz[son[u]]<siz[v])son[u]=v;
	}
}
int cur,rnk[200005],top[200005];
void dfs2(int u,int rt){
	top[u]=rt,dfn[u]=++cur,rnk[cur]=u;
	if(son[u])dfs2(son[u],rt);
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==pa[u])continue;
		if(v!=son[u])dfs2(v,v);
	}
}
struct segtree{
	#define ls p<<1
	#define rs p<<1|1
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	struct Node{
		int mi;
	}c[800005];
	void pushup(int p){
		c[p].mi=min(c[ls].mi,c[rs].mi);
	}
	void build(int l,int r,int p){
		if(l==r){
			if(rnk[l]>n)c[p].mi=(*s[rnk[l]].begin());
			else c[p].mi=inf;
			return;
		}
		int mid=(l+r)>>1;
		build(lson),build(rson);
		pushup(p);
	}
	void update(int l,int r,int p,int x,int k){
		if(l==r){c[p].mi=k;return;}
		int mid=(l+r)>>1;
		if(x<=mid)update(lson,x,k);
		else update(rson,x,k);
		pushup(p);
	}
	int query(int l,int r,int p,int L,int R){
		if(L<=l&&r<=R)return c[p].mi;
		int mid=(l+r)>>1,res=inf;
		if(L<=mid)res=min(res,query(lson,L,R));
		if(R>mid)res=min(res,query(rson,L,R));
		return res;
	}
	int askPath(int u,int v){
		int res=inf;
		while(top[u]!=top[v]){
			if(dep[top[u]]<dep[top[v]])swap(u,v);
			res=min(res,query(1,cur,1,dfn[top[u]],dfn[u]));
			u=pa[top[u]];
		}
		if(dep[u]<dep[v])swap(u,v);
		res=min(res,query(1,cur,1,dfn[v],dfn[u]));
		if(pa[v]&&v>n)res=min(res,a[pa[v]]);
		else res=min(res,a[v]); 
		return res;
	}
	#undef ls
	#undef rs
	#undef lson
	#undef rson
}Tr;
char op[10];
signed main(){
	n=read(),m=read(),q=read();
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1,u,v;i<=m;i++){
		u=read(),v=read(),add(u,v),add(v,u);
	}
	for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i),tp--;
	for(int i=1;i<=n;i++){
		if(!deg[i])cnt++,dcc[cnt].push_back(i);
	}
	tot=0;for(int i=1;i<=n;i++)head[i]=0;
	for(int i=1;i<=cnt;i++){
		for(auto x:dcc[i])add(x,i+n),add(i+n,x);
	}
	dfs1(1,0);dfs2(1,1);Tr.build(1,cur,1);
	while(q--){
		scanf("%s",op);int x=read(),y=read();
		if(op[0]=='C'){
			if(pa[x]&&pa[x]>n){
				s[pa[x]].erase(s[pa[x]].find(a[x]));
				a[x]=y,s[pa[x]].insert(y);
				Tr.update(1,cur,1,dfn[pa[x]],(*s[pa[x]].begin()));
			}
			else a[x]=y;
		}
		else{
			int res=Tr.askPath(x,y);
			printf("%lld\n",res);
		}
	}
	return 0;
}

13. loj6240. 仙人掌

14. P5471 [NOI2019] 弹跳

单点向一个矩阵内连边,考虑线段树套线段树优化建图。发现这个题空间限制很小,考虑将内层的线段树替换为平衡树,空间复杂度变为 \(n\log n\)

注意到如果真的把边建出来依然会爆空间,可以不直接连边。我们对每种弹跳装置建一个虚点,然后跑 dij。如果当前拿出来拓展的点 \(u\) 是个实点,就尝试拓展以 \(u\) 为起点的弹跳装置;否则,在线段树上查找 \(u\) 对应区域内的点,尝试更新它们,然后因为 dij 的特性,不管成不成功这些点都可以在线段树上被删去了。时间复杂度 \(\mathcal{O}(n\log^2 n)\),空间复杂度 \(\mathcal{O}(n\log n+m)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
set<pii>s[280005];
void ins(int l,int r,int p,int x,int y,int I){
	s[p].insert({y,I});
	if(l==r)return;
	int mid=(l+r)>>1;
	if(x<=mid)ins(l,mid,p<<1,x,y,I);
	else ins(mid+1,r,p<<1|1,x,y,I);
}
priority_queue<pii,vector<pii>,greater<pii> >q;
int n,m,h,w,P[150005],T[150005],L[150005],R[150005],D[150005],U[150005];
int px[70005],py[70005],d[220005],vis[220005];vector<int>g[70005];
void upd(int l,int r,int p,int I){
	if(L[I]<=l&&r<=R[I]){
		while(!s[p].empty()){
			auto it=s[p].lower_bound({D[I],0});
			if(it==s[p].end()||(*it).fi>U[I])break;
			int u=(*it).se;if(d[u]>d[I+n])d[u]=d[I+n],q.push({d[u],u});
			s[p].erase(it);
		}
		return;
	}
	int mid=(l+r)>>1;
	if(L[I]<=mid)upd(l,mid,p<<1,I);
	if(R[I]>mid)upd(mid+1,r,p<<1|1,I);
}
signed main(){
	freopen("jump.in","r",stdin);
	freopen("jump.out","w",stdout);
	n=read(),m=read(),w=read(),h=read();
	for(int i=1;i<=n;i++){
		px[i]=read(),py[i]=read(),ins(1,w,1,px[i],py[i],i);
	}
	for(int i=1;i<=m;i++){
		P[i]=read(),T[i]=read(),L[i]=read(),R[i]=read(),D[i]=read(),U[i]=read();
		g[P[i]].push_back(i);
	}
	for(int i=1;i<=n+m;i++)d[i]=inf;
	d[1]=0;q.push({d[1],1});
	while(!q.empty()){
		int u=q.top().se;q.pop();
		if(vis[u])continue;
		vis[u]=1;
		if(u<=n){for(auto v:g[u])d[v+n]=d[u]+T[v],q.push({d[v+n],v+n});}
		else upd(1,w,1,u-n);
	}
	for(int i=2;i<=n;i++)printf("%lld\n",d[i]);
	return 0;
}

15. P3243 [HNOI2015] 菜肴制作

发现直接求最小字典序并不符合题目要求,但是我们发现每次把最大的在合法范围内往最后扔是合法的,所以求反图的字典序最大的答案即可。结论证明

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,nxt;
}e[100005];
int tot,head[100005],deg[100005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot,deg[v]++;
}
void solve(){
	int n=read(),m=read();
	tot=0;for(int i=1;i<=n;i++)deg[i]=head[i]=0;
	for(int i=1,u,v;i<=m;i++){
		u=read(),v=read(),add(v,u);
	}
	priority_queue<int,vector<int>,less<int> >q;
	for(int i=1;i<=n;i++)if(!deg[i])q.push(i);
	vector<int>ans;
	while(!q.empty()){
		int u=q.top();q.pop();ans.push_back(u);
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v;
			if((--deg[v])==0)q.push(v);
		}
	}
	if((int)ans.size()!=n)return puts("Impossible!"),void();
	reverse(ans.begin(),ans.end());
	for(auto x:ans)printf("%lld ",x);
	puts("");
}
signed main(){
	int T=read();
	while(T--){
		solve();
	}
	return 0;
}

16. [ABC237Ex] Hakata

需要知道一个结论的简单题。

考虑把所有回文子串找出来,两两根据包含关系连边。容易发现这就是在求一个最长反链,根据 Dilworth 定理有最长反链等于最小链覆盖,直接做就行了。

这样做复杂度似乎爆炸,但我们需要知道一个结论:一个字符串的不同回文子串数量不超过 \(|S|\),因为每往字符串最后添加一个字符,新增的回文子串个数不超过 \(1\)

考虑反证。假如在加入 \(s_n\) 时有 \(s[x\ldots n]\)\(s[y\ldots n]\) 都没有在 \(s[1\ldots n-1]\) 中出现过。令 \(x<y\),因为 \(rev(s[x\ldots x+n-y])=s[y\ldots n]\),且 \(s[y\ldots n]\) 是回文串,所以 \(s[y\ldots n]=rev(s[y\ldots n])=rev(rev(s[x\ldots x+n-y]))=s[x\ldots x+n-y]\),与题设矛盾。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
using namespace std;
const int inf=1e18;
const ull base=13331;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
ull f[805],g[805],pw[805];char s[805];
ull askf(int l,int r){
	return f[r]-f[l-1]*pw[r-l+1];
}
ull askg(int l,int r){
	return g[l]-g[r+1]*pw[r-l+1];
}
int check(int l,int r){
	int mid=(l+r)>>1;
	if((r-l+1)%2==0)return (askf(l,mid)==askg(mid+1,r));
	else return (askf(l,mid)==askg(mid,r));
}
int L[805],R[805];map<ull,int>mp;
int sub(int x,int y){
	for(int i=L[x];i+(R[y]-L[y]+1)-1<=R[x];i++){
		if(askf(i,i+(R[y]-L[y]+1)-1)==askf(L[y],R[y]))return 1;
	}
	return 0;
}
struct edge{
	int v,w,nxt;
}e[500005];
int head[805],tot=1,now[805];
void add(int u,int v,int w){
	e[++tot]=(edge){v,w,head[u]},head[u]=tot;
}
void adde(int u,int v,int w){
	add(u,v,w),add(v,u,0);
}
int dep[805],S,T;
int bfs(){
	for(int i=1;i<=T;i++)dep[i]=0;
	dep[S]=1;queue<int>q;q.push(S);now[S]=head[S];
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=head[u];i;i=e[i].nxt){
			if(dep[e[i].v])continue;
			if(e[i].w)now[e[i].v]=head[e[i].v],dep[e[i].v]=dep[u]+1,q.push(e[i].v);
		}
	}
	return dep[T];
}
int dinic(int u,int flow){
	if(u==T)return flow;
	int rest=0;
	for(int i=now[u];i&&flow;i=e[i].nxt){
		now[u]=i;
		if(e[i].w==0||dep[e[i].v]!=dep[u]+1)continue;
		int k=dinic(e[i].v,min(e[i].w,flow));
		if(k==0)dep[e[i].v]=0;
		e[i].w-=k,e[i^1].w+=k;flow-=k,rest+=k;
	}
	return rest;
}
int d[805][805];
signed main(){
	scanf("%s",s+1);int len=strlen(s+1),n=0;
	pw[0]=1;for(int i=1;i<=len;i++)pw[i]=pw[i-1]*base;
	for(int i=1;i<=len;i++)f[i]=f[i-1]*base+s[i];
	for(int i=len;i>=1;i--)g[i]=g[i+1]*base+s[i];
	for(int i=1;i<=len;i++){
		for(int j=i;j<=len;j++){
			if(check(i,j)&&!mp.count(askf(i,j)))mp[askf(i,j)]=++n,L[n]=i,R[n]=j;
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			d[i][j]=((i==j)?0:inf);
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(sub(i,j))d[i][j]=1;
		}
	}
	for(int k=1;k<=n;k++){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(d[i][j]<inf&&i!=j)adde(i,j+n,1);
		}
	}
	S=2*n+1,T=2*n+2;
	for(int i=1;i<=n;i++)adde(S,i,1),adde(i+n,T,1);
	int ans=n;
	while(bfs())ans-=dinic(S,inf);
	printf("%lld\n",ans);
	return 0;
}

17. P7215 [JOISC2020] 首都

一个理论 \(\mathcal{O}(n\log n)\) 的做法。枚举每种颜色,找到对应点,任意两点路径上所有颜色都需要合并对吧。优化就是把这些点按 dfs 序排序,显然所有相邻两点的路径的交构成了整个联通块,所以总共只有 \(\mathcal{O}(n)\) 条路径了。

直接连边显然也是不行的,可以树剖,然后变成区间连边,可以线段树优化建图,复杂度 \(\mathcal{O}(n\log^2 n)\)。发现一次只会连一条重链的前缀/后缀,只用连 \(\mathcal{O}(n\log n)\) 条边了。还有一种优化方法,就是你发现这道题是可以重复连边的,可以使用 st 表优化建图,复杂度也是 \(\mathcal{O}(n\log n)\)

建完图之后缩点,答案就是 \(\min\{s_i-1\}\),其中 \(s_i\) 表示从第 \(i\) 个 scc 能到达多少种颜色。似乎需要拓扑排序,但是你发现求的是最小值,所以只需要对没有出边的所有 scc 求 min 即可。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,nxt;
}e[10000005];
int tot,head[5000005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int siz[200005],son[200005],Fa[200005],dep[200005];
void dfs1(int u,int fa){
	Fa[u]=fa;siz[u]=1,son[u]=0,dep[u]=dep[fa]+1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		dfs1(v,u);siz[u]+=siz[v];
		if(siz[son[u]]<siz[v])son[u]=v;
	}
}
int cur,dfn[200005],rnk[200005],top[200005];
void dfs2(int u,int rt){
	top[u]=rt;dfn[u]=++cur,rnk[cur]=u;
	if(son[u])dfs2(son[u],rt);
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==Fa[u]||v==son[u])continue;
		dfs2(v,v);
	}
}
int getlca(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		u=Fa[top[u]];
	}
	if(dep[u]<dep[v])swap(u,v);
	return v;
}
int f[22][200005],Log[200005];
void addsub(int l,int r,int u){
	int o=Log[r-l+1];
	add(u,f[o][l]),add(u,f[o][r-(1ll<<o)+1]);
}
void adde(int u,int v,int k){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		addsub(dfn[top[u]],dfn[u],k),u=Fa[top[u]];
	}
	if(dep[u]<dep[v])swap(u,v);
	addsub(dfn[v],dfn[u],k);
}
int n,m,CUR,rc,tp,DFN[5000005],LOW[5000005],SIZ[5000005],in[5000005],st[5000005],bel[5000005];
void tarjan(int u){
	DFN[u]=LOW[u]=++CUR,in[u]=1,st[++tp]=u;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(!DFN[v])tarjan(v),LOW[u]=min(LOW[u],LOW[v]);
		else if(in[v])LOW[u]=min(LOW[u],DFN[v]);
	}
	if(LOW[u]==DFN[u]){
		int v;rc++;
		do{
			v=st[tp--],in[v]=0;
			SIZ[rc]+=(v<=m),bel[v]=rc;
		}while(u!=v);
	}
}
int c[200005],deg[5000005];vector<int>ct[200005];
int cmp(int x,int y){
	return dfn[x]<dfn[y];
}
signed main(){
	n=read(),m=read();
	for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
	dfs1(1,0);dfs2(1,1);tot=0;for(int i=1;i<=n;i++)head[i]=0;
	for(int i=1;i<=n;i++)c[i]=read(),ct[c[i]].push_back(i);
	Log[1]=0;for(int i=2;i<=n;i++)Log[i]=Log[i>>1]+1;
	int num=m;
	for(int j=0;j<=Log[n];j++){
		for(int i=1;i+(1ll<<j)-1<=n;i++){
			f[j][i]=++num;
		}
	}
	for(int i=1;i<=n;i++)add(f[0][i],c[rnk[i]]);
	for(int j=1;j<=Log[n];j++){
		for(int i=1;i+(1ll<<j)-1<=n;i++){
			add(f[j][i],f[j-1][i]),add(f[j][i],f[j-1][i+(1ll<<(j-1))]);
		}
	}
	for(int i=1;i<=m;i++){
		if(ct[i].empty())continue;
		sort(ct[i].begin(),ct[i].end(),cmp);
		for(int j=0;j<(int)ct[i].size();j++){
			adde(ct[i][j],ct[i][(j+1)%((int)ct[i].size())],i);
		}
	}
	for(int i=1;i<=num;i++)if(!DFN[i])tarjan(i);
	for(int u=1;u<=num;u++){
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v;
			if(bel[u]!=bel[v])deg[bel[u]]=1;
		}
	}
	int ans=m;
	for(int i=1;i<=rc;i++){
		if(!deg[i])ans=min(ans,SIZ[i]);
	}
	printf("%lld\n",ans-1);
	return 0;
}

18. CF1648E. Air Reform

19. [AGC056C] 01 Balanced

先考虑对前缀和跑差分约束,因为 spfa 可以被卡到 \(\mathcal{O}(nm)\),所以这是过不去的,考虑优化。

发现我们需要用 spfa 的原因是在满足 \(m\) 条限制时会连负权边。不妨记 \(s_i=\sum\limits_{j=1}^i[a_j=0]-\sum\limits_{j=1}^i[a_j=1]\),发现这样我们就只用连 0/1 的边了,可以直接 bfs,复杂度 \(\mathcal{O}(n+m)\)

为什么这样求出来的就是字典序最小的解?因为这是差分约束,我们求出来的 \(s_i\) 都顶到了上界。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,w,nxt;
}e[3000005];
int tot,head[1000005];
void add(int u,int v,int w){
	e[++tot]=(edge){v,w,head[u]},head[u]=tot;
}
int d[1000005];
signed main(){
	int n=read(),m=read();
	for(int i=1;i<=n;i++){
		add(i,i-1,1),add(i-1,i,1);
	}
	for(int i=1,l,r;i<=m;i++){
		l=read(),r=read(),add(l-1,r,0),add(r,l-1,0);
	} 
	for(int i=0;i<=n;i++)d[i]=inf;
	deque<int>q;d[0]=0;q.push_back(0);
	while(!q.empty()){
		int u=q.front();q.pop_front();
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v,w=e[i].w;
			if(d[v]<inf)continue;
			d[v]=d[u]+w;
			if(w)q.push_back(v);
			else q.push_front(v);
		}
	}
	for(int i=1;i<=n;i++)printf("%lld",(d[i]-d[i-1]<=0));
	return 0;
}

20. P7295 [USACO21JAN] Paint by Letters P

神秘做法。

考虑怎么统计联通块个数。根据欧拉公式 \(v-e+f=c+1\) 可以知道,只需要统计 \(v,e,f\) 就行了。统计 \(v,e\) 是简单的,按下不表。对于 \(f\),我们可以 dfs 搜出所有的空腔联通块,记联通块上、下、左、右分别为

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define y1 y3456
using namespace std;
const int inf=1e18;
const int dx[]={1,-1,0,0},dy[]={0,0,1,-1},Dx[]={1,0,0,0},Dy[]={0,0,1,0};
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,m,q,num,a[1005][1005],b[1005][1005],vis[1005][1005];char s[1005][1005];
int in(int x,int y){
	return (1<=x&&x<=n&&1<=y&&y<=m);
}
int tag[1000005],L[1000005],R[1000005],U[1000005],D[1000005];
void dfs(int x,int y,int I){
	vis[x][y]=I;
	L[I]=min(L[I],y),R[I]=max(R[I],y);
	U[I]=min(U[I],x),D[I]=max(D[I],x); 
	for(int i=0;i<4;i++){
		int nx=x+dx[i],ny=y+dy[i];
		if((dx[i]!=0&&s[x+Dx[i]][y]==s[x+Dx[i]][y+1])||(dy[i]!=0&&s[x][y+Dy[i]]==s[x+1][y+Dy[i]]))continue;
		if(nx<1||nx>n-1||ny<1||ny>m-1){tag[I]=1;continue;}
		if(vis[nx][ny])continue;
		dfs(nx,ny,I);
	}
}
int qx1[1005],qy1[1005],qx2[1005],qy2[1005],sum[1005][1005],ans[1005];
struct Info{
	int id,a,b,c,d;
}p[1001005];
int c[1005][1005];
void add(int x,int y,int k){
	for(int i=x;i<=m;i+=i&-i)for(int j=y;j<=m;j+=j&-j)c[i][j]+=k;
}
int ask(int x,int y){
	int res=0;
	for(int i=x;i;i-=i&-i)for(int j=y;j;j-=j&-j)res+=c[i][j];
	return res;
}
void solve(int l,int r){
	if(l>=r)return;
	int mid=(l+r)>>1;
	solve(l,mid),solve(mid+1,r);
	sort(p+l,p+mid+1,[](Info x,Info y){return x.b<y.b;});
	sort(p+mid+1,p+r+1,[](Info x,Info y){return x.b<y.b;});
	vector<int>tmp;
	for(int i=l,j=mid+1;j<=r;j++){
		while(i<=mid&&p[i].b<=p[j].b){
			if(p[i].id>num){i++;continue;}
			add(m-p[i].c+1,p[i].d,1-tag[p[i].id]),tmp.push_back(i),i++;
		}
		if(p[j].id>num)ans[p[j].id-num]+=ask(m-p[j].c+1,p[j].d);
	}
	for(auto x:tmp)add(m-p[x].c+1,p[x].d,-1+tag[p[x].id]);
}
signed main(){
	n=read(),m=read(),q=read();
	for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
	for(int i=1;i<n;i++)for(int j=1;j<m;j++){
		if(!vis[i][j]){
			num++;L[num]=R[num]=j,U[num]=D[num]=i; 
			dfs(i,j,num);
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+(s[i][j]==s[i][j-1])+(s[i][j]==s[i-1][j]);
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			a[i][j]=a[i][j-1]+(s[i][j]==s[i][j-1]);
		}
	}
	for(int j=1;j<=m;j++){
		for(int i=1;i<=n;i++){
			b[j][i]=b[j][i-1]+(s[i][j]==s[i-1][j]);
		}
	}
	for(int i=1;i<=q;i++){
		qx1[i]=read(),qy1[i]=read(),qx2[i]=read(),qy2[i]=read();
		ans[i]+=(qx2[i]-qx1[i]+1)*(qy2[i]-qy1[i]+1);
		ans[i]-=sum[qx2[i]][qy2[i]]-sum[qx1[i]][qy2[i]]-sum[qx2[i]][qy1[i]]+sum[qx1[i]][qy1[i]]+a[qx1[i]][qy2[i]]-a[qx1[i]][qy1[i]]+b[qy1[i]][qx2[i]]-b[qy1[i]][qx1[i]];
	}
	for(int i=1;i<=num;i++)p[i]={i,U[i],D[i]+1,L[i],R[i]+1};
	for(int i=1;i<=q;i++)p[num+i]={num+i,qx1[i],qx2[i],qy1[i],qy2[i]};
	sort(p+1,p+num+q+1,[](Info x,Info y){
		if(x.a^y.a)return x.a>y.a;
		return x.id<y.id;
	});
	solve(1,num+q);
	for(int i=1;i<=q;i++)printf("%lld\n",ans[i]);
	return 0;
}

21. P3623 [APIO2008] 免费道路

22. P3953 [NOIP2017 提高组] 逛公园

23. [AGC025E] Walking on a Tree

posted @ 2023-11-21 16:31  xx019  阅读(69)  评论(0编辑  收藏  举报