[提高组集训] 模拟赛6


风之轨迹「miracle」

题目描述

\(n\) 个点 \(m\) 条边的有向无环图,定义路径长度为路径上边的数量。

问删掉一个点之后所得到的最大的路径长度,并且要求你输出删除的这个点(有多解输出最小的一个)

\(n\leq 5\times 10^5,m\leq 10^6\)

解法

为了便于讨论我们建立超级起点连向所有点,所有点再连向超级终点。

首先有一个基本的 \(\tt observation\):删去 \(x\) 之后的最长路就是走起点拓扑序 \(,终点拓扑序 \(>x\) 的边得到的路径最大值。

那么我们可以预处理出经过每条边的路径最大值(正图和反图跑拓扑),然后类似扫描线,离开一个点的时候加入所有边到 \(\tt set\),询问之后删除这个点的所有边,时间复杂度 \(O(m\log m)\)

总结

删除一个点并不需要真的删去,你可以转化成强制不经过它。

#include 
#include 
#include 
#include 
using namespace std; 
const int M = 2000005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,ans,id;
struct node
{
	int u,v,c;
	bool operator < (const node &b) const
	{
		if(c==b.c && u==b.u) return vb.c;
	}
};set s;
struct graph
{
	int tot,cnt,f[M],dp[M],o[M],d[M];
	struct edge{int v,next;}e[M<<1];
	void add(int u,int v)
	{
		d[v]++;
		e[++tot]=edge{v,f[u]},f[u]=tot;
	}
	void tpsort()
	{
		queue q;
		for(int i=0;i<=n+1;i++)
			if(!d[i]) q.push(i);
		while(!q.empty())
		{
			int u=q.front();q.pop();
			o[cnt++]=u;
			for(int i=f[u];i;i=e[i].next)
			{
				int v=e[i].v;d[v]--;
				dp[v]=max(dp[v],dp[u]+1);
				if(!d[v]) q.push(v);
			}
		}
	}
}G1,G2;
signed main()
{
	freopen("miracle.in","r",stdin);
	freopen("miracle.out","w",stdout);
	n=read();m=read();ans=n;
	for(int i=1;i<=m;i++)
	{
		int u=read(),v=read();
		G1.add(u,v);
		G2.add(v,u);
	}
	for(int i=1;i<=n;i++)
	{
		G1.add(0,i),G2.add(i,0);
		G1.add(i,n+1),G2.add(n+1,i);
	}
	G1.tpsort();G2.tpsort();
	for(int i=0;i<=n+1;i++)
	{
		int x=G1.o[i];
		for(int j=G2.f[x];j;j=G2.e[j].next)
		{
			int v=G2.e[j].v;
			s.erase(node{v,x,G2.dp[x]+G1.dp[v]+1});
		}
		if(!s.empty())
		{
			int c=s.begin()->c-2;
			if(c

羽未「umi」

题目描述

给定一个长度为 \(n\) 的颜色序列 \(a_i\),我们称序列是好的当且仅当所有颜色再序列上的出现位置连续。

我们可以把一种颜色整体修改成另一种颜色,问最终序列和原序列最小不同的位置数量。

你还需要支持 \(m\) 次修改,每次修改一个位置的颜色,然后给出答案。

\(n,m\leq 2\cdot 10^5\)

解法

考虑把原序列分段,那么每一段的代价就是 长度\(-\)最多的出现次数。然后考虑分段方式是确定的,设 \(b_i\) 表示有多少 \([l_c,r_c]\) 覆盖了 \([i,i+1]\),所有 \(b_i=0\) 的位置都是分界点。

问题变成了维护所有 \(0\) 之间的最大值,\(b\) 数组需要支持区间加法,权值数组需要支持单点修改。

我们可以转而维护 \(b\) 数组最小值之间的最大值之和,这样区间加法就对它没用影响了。上传的时候需要先讨论左右儿子最小值的关系,然后类似最大子段的方式合并即可,时间复杂度 \(O(n\log n)\)

#include 
#include 
using namespace std;
const int M = 200005;
const int up = 200000;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,a[M],b[M],fl[4*M];set s[M];
struct node
{
	int mn,lm,rm,mx,sum;
	node operator + (const node &b) const
	{
		node r;
		if(mn>b.mn)
		{
			r.mn=b.mn;r.lm=max(mx,b.lm);
			r.rm=b.rm;r.mx=max(mx,b.mx);
			r.sum=b.sum;return r;
		}
		if(mn>1;down(i);
	if(mid>=id) ins(i<<1,l,mid,id);
	else ins(i<<1|1,mid+1,r,id);
	t[i]=t[i<<1]+t[i<<1|1]; 
}
void add(int i,int l,int r,int L,int R,int c)
{
	if(L>r || l>R) return ;
	if(L<=l && r<=R) {fuck(i,c);return ;}
	int mid=(l+r)>>1;down(i);
	add(i<<1,l,mid,L,R,c);
	add(i<<1|1,mid+1,r,L,R,c);
	t[i]=t[i<<1]+t[i<<1|1];
}
void upd(int x)
{
	if(!s[x].size()) return ;
	int p=*s[x].begin();b[p]=s[x].size();
	add(1,0,n,p,*s[x].rbegin()-1,1);
	ins(1,0,n,p-1);ins(1,0,n,p);
}
void del(int x)
{
	if(!s[x].size()) return ;
	int p=*s[x].begin();b[p]=0;
	add(1,0,n,p,*s[x].rbegin()-1,-1);
	ins(1,0,n,p-1);ins(1,0,n,p);
}
signed main()
{
	freopen("umi.in","r",stdin);
	freopen("umi.out","w",stdout);
	n=read();m=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		s[a[i]].insert(i);
	}
	for(int i=1;i<=up;i++) upd(i);
	printf("%d\n",n-t[1].sum);
	while(m--)
	{
		int x=read(),y=read();
		if(a[x]==y)
		{
			printf("%d\n",n-t[1].sum);
			continue;
		}
		del(a[x]);del(y);
		s[a[x]].erase(x);
		s[y].insert(x);
		upd(a[x]);upd(y);a[x]=y;
		printf("%d\n",n-t[1].sum);
	}
}