[提高组集训2021] 模拟赛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);
}
}