UNR #1

争夺圣杯

题目描述

点此看题

解法

比较小清新的签到题,但还是挺有意思的!

首先明确题目其实是想让你分别求出每个 \(m\) 对应的答案是多少。可以考虑贡献法,设点 \(i\) 作为最大值的范围是 \([l_i,r_i]\)(值相同位置小的大),设管辖区间两段较小的长度是 \(mn\),较大的长度是 \(mx\)

  • \(m\leq mn\),则贡献是 \(m\cdot a_i\)
  • \(mn<m\leq mx\),则贡献是 \(mn\cdot a_i\)
  • \(mx<m\leq mx+mn-1\),则贡献是 \((mx+mn-x)\cdot a_i\)

那么可以用差分法维护 \(m\) 对应的系数和常数,时间复杂度 \(O(n)\)

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 1000005;
#define int long long
const int MOD = 998244353;
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,a[M],s[M],c[M],d[M],L[M],R[M];
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		while(m && a[s[m]]<a[i]) m--;
		L[i]=s[m]+1;s[++m]=i;
	}
	s[m=0]=n+1;
	for(int i=n;i>=1;i--)
	{
		while(m && a[s[m]]<=a[i]) m--;
		R[i]=s[m]-1;s[++m]=i;
	}
	for(int i=1;i<=n;i++)
	{
		int mn=i-L[i]+1,mx=R[i]-i+1;
		if(mn>mx) swap(mn,mx);
		c[1]+=a[i];c[mn+1]-=a[i];
		c[mx+1]-=a[i];c[mn+mx]+=a[i];
		d[mn+1]+=mn*a[i];d[mx+1]-=mn*a[i];
		d[mx+1]+=(mn+mx)*a[i];
		d[mx+mn]-=(mx+mn)*a[i];
	}
	for(int i=1;i<=n;i++)
	{
		c[i]+=c[i-1];d[i]+=d[i-1];
		ans^=((c[i]*i)+d[i])%MOD;
	}
	printf("%lld\n",ans);
}

奇怪的线段树

题目描述

点此看题

解法

草,傻逼错误调了半个小时,全体目光向我看齐,我宣布个事,我是个傻逼。

言归正传,我们先考虑如何判断无解,发现充要条件是每个黑点的祖先都是黑点,可以做这个判断。

然后我们只需要考虑染黑那些儿子不是黑点的黑点,对于一次染色,我们考察线段树上定位节点的性质:

  • 定位节点所代表的区间不交且连续,在树上不存在祖先\(-\)后代关系。
  • 定位节点的形式一定是一段连续的右儿子\(+\)一段连续的左儿子(这个只能感性理解,我也不会证明)
  • 任意一段连续的右儿子\(+\)一段连续的左儿子都是可被一次区间染色的(可以归纳法证明数量关系)

区间染色等价于选取一段连续的右儿子\(+\)一段连续的左儿子当成定位节点来染色,可以建图表达这个过程。对于所有右儿子 \(i\),我们连向所有 \(l_j=r_i+1\) 的节点 \(j\);对于所有左儿子 \(i\),我们连向所有 \(l_j=r_i+1\) 的左儿子 \(j\)

这样限制被成选取一条路径,有一些点要求必须被经过。那么我们把每个点拆成入点和出点,然后跑网络流,对于有要求的点我们把入点和出点连一条下界为 \(1\) 的边,那么跑有源汇上下界最小流即可。

第二类边是 \(O(n)\) 的,但是第一类边是 \(O(n^2)\) 的,考虑优化建图。我们建立 \(n\) 个辅助节点,首先第 \(l_i\) 个辅助节点连向点 \(i\),对于右儿子 \(i\),我们把 \(i\) 连向第 \(r_i+1\) 个辅助节点,这样我们就只需要跑一个点数和边数都是 \(O(n)\) 级别的网络流。

最后注意,最后要求白色的点不能建入图中,如果一开始没发现这个细节就会向我一样痛苦地调试半个小时。

总结

本题的关键仍然是 \(\tt zkw\) 的哨兵思想,可以结合 线段树 来理解。

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <queue>
using namespace std;
const int M = 100005;
const int inf = 0x3f3f3f3f;
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,cnt,a[M],b[M],L[M],R[M],ls[M],rs[M],pd[M];
int tot=1,ans,ns,nt,S,T,f[M],cur[M],dis[M];
struct edge{int v,c,next;}e[M<<2];
void add(int u,int v,int c)
{
	e[++tot]=edge{v,c,f[u]},f[u]=tot;
	e[++tot]=edge{u,0,f[v]},f[v]=tot;
}
void init(int x,int l,int r)
{
	a[x]=read();L[x]=l;R[x]=r;
	if(l==r) {b[x]=a[x];return ;}
	int mid=read();
	init(ls[x]=++cnt,l,mid);
	init(rs[x]=++cnt,mid+1,r);
	pd[ls[x]]=1;
	if(!a[x] && (a[ls[x]] || a[rs[x]]))
		{puts("OwO");exit(0);}
	b[x]=a[x] && (!a[ls[x]]) && (!a[rs[x]]);
}
int bfs()
{
	for(int i=1;i<=nt;i++) dis[i]=0;
	dis[S]=1;queue<int> q;q.push(S);
	while(!q.empty())
	{
		int u=q.front();q.pop();
		if(u==T) return 1;
		for(int i=f[u];i;i=e[i].next)
		{
			int v=e[i].v;
			if(e[i].c>0 && !dis[v])
				dis[v]=dis[u]+1,q.push(v);
		}
	}
	return 0;
}
int dfs(int u,int ept)
{
	if(u==T) return ept;
	int flow=0,tmp=0;
	for(int &i=cur[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(e[i].c>0 && dis[v]==dis[u]+1)
		{
			tmp=dfs(v,min(ept,e[i].c));
			if(!tmp) continue;
			e[i].c-=tmp;e[i^1].c+=tmp;
			ept-=tmp;flow+=tmp;
			if(!ept) break;
		}
	}
	return flow;
}
signed main()
{
	n=read();init(cnt=1,1,n);
	S=5*n+1;T=5*n+2;ns=5*n+3;nt=5*n+4;
	for(int i=1;i<2*n;i++) if(a[i])//23333
	{
		add(ns,i,inf);
		add(i+2*n,nt,inf);
		//
		add(i,i+2*n,inf);
		if(b[i]) add(i,T,1),add(S,i+2*n,1);
		//
		add(L[i]+4*n,i,inf);
		//
		if(!pd[i] && R[i]<n)
			add(i+2*n,R[i]+1+4*n,inf);
		if(pd[i]) for(int j=i;j<2*n;j++)
			if(pd[j] && L[j]==R[i]+1)
				add(i+2*n,j,inf);
	}
	add(nt,ns,inf);
	while(bfs())
	{
		for(int i=1;i<=nt;i++) cur[i]=f[i];
		dfs(S,inf);
	}
	ans=e[tot].c;e[tot].c=e[tot-1].c=0;
	S=nt;T=ns;
	while(bfs())
	{
		for(int i=1;i<=nt;i++) cur[i]=f[i];
		ans-=dfs(S,inf);
	}
	printf("%d\n",ans);
}

火车管理

题目描述

点此看题

解法

根本就不是小清新题!第一次写带标记的主席树给我写傻了!

最难的是二操作,考虑撤回操作其实相当于让这个单点的时间回溯。那么我们用主席树来保存每个时刻的线段树,首先查询覆盖这个单点的时刻是多少,设为 \(x\),然后去时刻 \(x-1\) 查询这个点的栈顶,再更新当前版本的主席树即可。

那么一、三操作都可以直接在主席树上修改和询问。注意主席树的下传也需要新建节点,下传后若节点信息改变需要重新复制,在询问时如果遇到覆盖标记就直接退出,这样就可以卡好新建的点数了。

#include <cstdio>
#include <iostream>
using namespace std;
const int N = 500005;
const int M = 130*N;
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,ty,ans,cnt,rt[N],a[N],ls[M],rs[M],s[M],fl[M];
void copy(int x,int y)
{
	ls[x]=ls[y];rs[x]=rs[y];
	s[x]=s[y];fl[x]=fl[y];
}
void down(int x,int l,int r)
{
	if(!x || !fl[x]) return ;
	copy(++cnt,ls[x]);ls[x]=cnt;
	copy(++cnt,rs[x]);rs[x]=cnt;
	int mid=(l+r)>>1;
	s[ls[x]]=a[fl[x]]*(mid-l+1);
	s[rs[x]]=a[fl[x]]*(r-mid);
	fl[ls[x]]=fl[rs[x]]=fl[x];fl[x]=0;
}
void add(int &x,int y,int l,int r,int L,int R,int c)
{
	copy(x=++cnt,y);
	if(L<=l && r<=R)
	{
		s[x]=(r-l+1)*a[c];
		fl[x]=c;return ;
	}
	int mid=(l+r)>>1;down(y,l,r);copy(x,y);
	if(L<=mid) add(ls[x],ls[y],l,mid,L,R,c);
	if(mid<R) add(rs[x],rs[y],mid+1,r,L,R,c);
	s[x]=s[ls[x]]+s[rs[x]];
}
int ask(int x,int l,int r,int L,int R)
{
	if(L>r || l>R) return 0;
	if(fl[x]) return (min(r,R)-max(L,l)+1)*a[fl[x]];
	if(L<=l && r<=R) return s[x];
	int mid=(l+r)>>1;
	return ask(ls[x],l,mid,L,R)
	+ask(rs[x],mid+1,r,L,R);
}
int get(int x,int l,int r,int id)
{
	if(l==r || fl[x]) return fl[x];
	int mid=(l+r)>>1;
	if(mid>=id) return get(ls[x],l,mid,id);
	return get(rs[x],mid+1,r,id);
}
signed main()
{
	n=read();m=read();ty=read();
	for(int i=1;i<=m;i++)
	{
		int op=read();rt[i]=rt[i-1];
		if(op==2)
		{
			int x=(read()+ans)%n+1,t=get(rt[i],1,n,x);
			if(!t) continue;
			t=get(rt[t-1],1,n,x);
			add(rt[i],rt[i],1,n,x,x,t);
			continue;
		}
		int l=(read()+ans)%n+1,r=(read()+ans)%n+1;
		if(l>r) swap(l,r);
		if(op==1)
		{
			int tmp=ask(rt[i],1,n,l,r);
			ans=tmp*ty;printf("%d\n",tmp);
		}
		else a[i]=read(),add(rt[i],rt[i],1,n,l,r,i);
	}
}
posted @ 2022-05-10 22:07  C202044zxy  阅读(211)  评论(0编辑  收藏  举报