1. 程式人生 > 其它 >[提高組集訓] 模擬賽6

[提高組集訓] 模擬賽6

風之軌跡「miracle」

題目描述

\(n\) 個點 \(m\) 條邊的有向無環圖,定義路徑長度為路徑上邊的數量。

問刪掉一個點之後所得到的最大的路徑長度,並且要求你輸出刪除的這個點(有多解輸出最小的一個)

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

解法

為了便於討論我們建立超級起點連向所有點,所有點再連向超級終點。

首先有一個基本的 \(\tt observation\):刪去 \(x\) 之後的最長路就是走起點拓撲序 \(<x\),終點拓撲序 \(>x\) 的邊得到的路徑最大值。

那麼我們可以預處理出經過每條邊的路徑最大值(正圖和反圖跑拓撲),然後類似掃描線,離開一個點的時候加入所有邊到 \(\tt set\)

,詢問之後刪除這個點的所有邊,時間複雜度 \(O(m\log m)\)

總結

刪除一個點並不需要真的刪去,你可以轉化成強制不經過它。

#include <cstdio>
#include <iostream>
#include <queue>
#include <set>
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 v<b.v;
		if(c==b.c) return u<b.u;
		return c>b.c;
	}
};set<node> 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<int> 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<ans || (c==ans && x<id))
				ans=c,id=x;
		}
		for(int j=G1.f[x];j;j=G1.e[j].next)
		{
			int v=G1.e[j].v;
			s.insert(node{x,v,G2.dp[v]+G1.dp[x]+1});
		}
	}
	printf("%d %d\n",id,ans);
}

羽未「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 <cstdio>
#include <set>
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<int> 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<b.mn)
		{
			r.mn=mn;r.lm=lm;
			r.rm=max(rm,b.mx);r.mx=max(mx,b.mx);
			r.sum=sum;return r;
		}
		r.mn=mn;r.lm=lm;r.rm=b.rm;
		r.mx=max(mx,b.mx);
		r.sum=sum+b.sum+max(rm,b.lm);
		return r;
	}
}t[4*M];
void fuck(int x,int c)
{
	if(!x) return ;
	t[x].mn+=c;fl[x]+=c;
}
void down(int i)
{
	if(fl[i])
	{
		fuck(i<<1,fl[i]);
		fuck(i<<1|1,fl[i]);
		fl[i]=0;
	}
}
void ins(int i,int l,int r,int id)
{
	if(l==r)
	{
		t[i].lm=b[id];t[i].rm=b[id+1];
		t[i].mx=max(b[id],b[id+1]);
		return ;
	}
	int mid=(l+r)>>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);
	}
}