UNR #6 Day 1 Solution Set

面基之路

有一个关键发现是,我们不用管面基顺序,因为两个人相遇之后可以一起走,最后一起面基。那么问题就是要最小化所有 \(k+1\) 个关键点到某个点(这个点可以在边上)的距离的最大值。

分类讨论在点上面基还是在边上面基。边上就是做 \(k+1\) 个单源最短路。然后考虑枚举边 \(\langle u,v \rangle\),在边上面基。将所有人分成恰好两个集合 \(S,T\)\(S\) 内的人到 \(u\) 集合,\(T\) 内的人到 \(v\) 集合,然后可能在边上面基。算出 \(S\) 的所有人在 \(u\) 点集合的最小时间,\(T\) 同理。集合内的人全部到了应该到的点之后,都会往边上走,这样的话浅算一下就好了,这样时间复杂度是 \(O(k(n+m) \log m + m2^k)\) 的。

注意到有很多无用的状态,我们考虑将所有人按到 \(u\) 的时间从小到大排序,那么 \(S\) 内的人应该是一段前缀,\(T\) 就是一段后缀。这样只有 \(O(k)\) 个有效状态,时间复杂度 \(O(k(n+m)\log m)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
char buf[1<<18],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<18,stdin),p1==p2)?EOF:*p1++)
LL read()
{
	LL x=0;
	char c=getchar();
	while(c<'0' || c>'9')	c=getchar();
	while(c>='0' && c<='9')	x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
	return x;
}
void write(LL x)
{
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
LL n,m;
struct Edge{
	LL t,v;
	Edge(LL T=0,LL V=0){t=T,v=V;}
	inline bool operator < (Edge ano) const {return v>ano.v;}
};
vector<Edge> G[100005];
LL K;
LL cnt,a[23];
LL d[23][100005],dis[100005];
void Dijkstra(LL s)
{
	priority_queue<Edge> Q;
	memset(dis,63,sizeof dis);
	Q.push(Edge(s,0));
	dis[s]=0;
	while(!Q.empty())
	{
		Edge now=Q.top();
		Q.pop();
		if(now.v>dis[now.t])	continue;
		LL u=now.t;
		for(auto st:G[u])
		{
			LL v=st.t,w=st.v;
			if(dis[v]>dis[u]+w)
			{
				dis[v]=dis[u]+w;
				Q.push(Edge(v,dis[v]));
			}
		}
	}
}
typedef pair<LL,LL> P;
int main(){
	n=read(),m=read();
	for(LL i=1;i<=m;++i)
	{
		LL u=read(),v=read(),w=read();
		G[u].push_back(Edge(v,w));
		G[v].push_back(Edge(u,w));
	}
	K=read();
	vector<LL> Key;
	Key.push_back(1);
	for(LL i=1;i<=K;++i)	Key.push_back(read());
	sort(Key.begin(),Key.end()),Key.erase(unique(Key.begin(),Key.end()),Key.end());
	for(auto key:Key)
	{
		Dijkstra(a[++cnt]=key);
		for(LL i=1;i<=n;++i)	d[cnt][i]=dis[i];
	}
	LL ans=1e18;
	for(LL i=1;i<=n;++i)
	{
		LL maxd=0;
		for(LL j=1;j<=cnt;++j)	maxd=max(maxd,d[j][i]);
		ans=min(ans,maxd<<1);
	}
	for(LL i=1;i<=n;++i)
	{
		for(auto st:G[i])
		{
			LL u=i,v=st.t,w=st.v;
			static P op[25];
			#define mp make_pair
			for(LL j=1;j<=cnt;++j)	op[j]=mp(d[j][u],d[j][v]);
			sort(op+1,op+1+cnt,[&](P x,P y){return x.first<y.first || (x.first==y.first && x.second>y.second);});
			static LL suf[25];
			for(LL j=1;j<=cnt;++j)	suf[j]=op[j].second;
			for(LL j=cnt;j;--j)	suf[j]=max(suf[j+1],suf[j]);
			for(LL j=1;j<cnt;++j)
			{
				LL du=min(op[j].first,suf[j+1]),dv=max(op[j].first,suf[j+1]);
				if(du+w<=dv)	ans=min(ans,dv<<1);
				else	ans=min(ans,dv*2+(w-dv+du));
			}
		}
	}
	write(ans);
	return 0;
}

机器人表演

这种题有两种思考的方向。一种是用极长段描述序列,另一种是记录新序列中的子序列能匹配的最长原序列前缀。

这里,第一种讨论繁多贡献复杂,暂不考虑。用第二种方法去记录。

可以将插入的 01 看做左括号和右括号。

首先有一个观察是,如果新的序列能匹配就匹配,那么插入的括号在新序列的位置与插入的括号串能唯一描述一种方案。具体可以观察样例:01011 就是 0(1)1(5)0(1)1(3)0(3)1(5)0(4)1(5)

写个暴力对第二个样例做一样的事情,能匹配就匹配,然后看剩下来的括号串是不是一个合法的串,并记录下位置的状态——不对!答案少了。

思考问题出现的原因:我们钦定了能匹配则匹配,实际上我们可能匹配到了插入的括号串,导致剩下的括号串并不合法。比如 (()()(,原串为 (()(,发现在倒数第二个字符出现问题:之前没有左括号,但是出现了右括号,我们的程序就判定为不合法了。实际上我们可以把插入的串看做 0(4)1(5),这样做就是正确的了。

这样我们需要一个类似于回退的操作:定义 \(dp_{i,j,k}\) 表示,当前已经算了 \(i\) 个括号,在插入的括号串合法的情况下最长匹配了 \(j\) 个括号,插入的括号串中已经出现了 \(k\)() 的个数可以算出来(即为 \(i-j-k\))。

那么先一堆正常不过的分讨,我们剩下要处理的加入 ),不能和原串匹配,之前所有多出来的 ( 也被匹配干净了的情况。此时我们要找一个 ( 与之匹配。

但是前面有很多可以和它匹配的 ( 啊?那么我们要找到一个 (,使得:

  • 不出现新的未匹配的多出来的括号;
  • 这两个括号匹配之后,能与原串匹配的长度尽量的长,也就是使 \(j\) 这一维的变化量尽量小(这同时为了正确性和最优性考虑)。

那么分讨吧。首先,如果两个左括号都能匹配,并且匹配的括号中间那一段,将 ( 看做 \(1\) ) 看做 \(-1\) 相加的权值相等,那么选更近的好,这样 \(j\) 的变化量一定少。

然后匹配的括号中间那一段,将 ( 看做 \(1\) ) 看做 \(-1\) 相加的权值一定要为 \(0\),可以分类讨论证明:

  • 如果为 \(0\),首先我们肯定可以不匹配任意一个括号(事实上也不能),存在性证明;
  • 如果不为 \(0\),首先可以知道中间那一段权值是不大于 \(0\) 的,否则匹配的左括号可以向右移动;形似 ( (...) ) 的样子,如果要继续匹配的话,抽掉第二个 ( 肯定会导致有更多的 ) 需要匹配,这样的话中间的权值会变得越来越小,一定会留下不正确的括号。

那么匹配的规则很简单,就是找到前面第一个 ( 满足中间的权值为 \(0\),然后把这些括号全部变成插入的括号串就好了。

感性证明,歪打正着,也许也是运气使然。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
char buf[1<<18],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<18,stdin),p1==p2)?EOF:*p1++)
int read()
{
	int x=0;
	char c=getchar();
	while(c<'0' || c>'9')	c=getchar();
	while(c>='0' && c<='9')	x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
	return x;
}
void write(int x)
{
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
const int MOD=998244353;
inline int Add(int x,int y){return x+y>=MOD?x+y-MOD:x+y;}
inline int Sub(int x,int y){return x<y?x-y+MOD:x-y;}
inline int Mul(int x,int y){return 1ll*x*y%MOD;}
inline int add(int &x,int y){return x=Add(x,y);}
inline int sub(int &x,int y){return x=Sub(x,y);}
inline int mul(int &x,int y){return x=Mul(x,y);}
int QuickPow(int x,int p=MOD-2)
{
	int ans=1,base=x;
	while(p)
	{
		if(p&1)	ans=Mul(ans,base);
		base=Mul(base,base);
		p>>=1;
	}
	return ans;
}
int dp[905][305][305];
int a[305],pre[305],mdf[305];
int n,t;
const int COR=900;
int app[1805];
int main(){
	n=read(),t=read();
	for(int i=1;i<=n;++i)
	{
		a[i]=getchar()-'0';
		while(a[i]>1 || a[i]<0)	a[i]=getchar()-'0';
	}
	memset(app,-1,sizeof app);
	int d=0;
	app[COR]=0;
	pre[0]=-1;
	for(int i=1;i<=n;++i)
	{
		if(a[i]==0)	++d;
		else	--d;
		pre[i]=app[d-1+COR];
		app[d+COR]=i;
		mdf[i]=(i-pre[i]-1)/2+1;
	}
	dp[0][0][0]=1;
	int N=n+t*2;
	for(int i=0;i<N;++i)
	{
		for(int j=0;j<=n;++j)
		{
			for(int k=0;k<=t;++k)
			{
				int c=dp[i][j][k],l=i-j-k;
				if(!c || l<0)	continue;
				int u=a[j+1];
				if(j==n)
				{
					if(k<t)	add(dp[i+1][j][k+1],c);
					if(l<k)	add(dp[i+1][j][k],c);
					else if(k+mdf[j]<=t && pre[j]!=-1)	add(dp[i+1][pre[j]][k+mdf[j]],c);
				}
				else if(u==0)
				{
					add(dp[i+1][j+1][k],c);
					if(l<k)	add(dp[i+1][j][k],c);
					else if(k+mdf[j]<=t && pre[j]!=-1)	add(dp[i+1][pre[j]][k+mdf[j]],c);
				}
				else
				{
					add(dp[i+1][j+1][k],c);
					if(k<t)	add(dp[i+1][j][k+1],c);
				}
			}
		}
	}
	write(dp[N][n][t]);
	return 0;
}

稳健型选手

有一个初步的 \(O(nq\log n)\) 的想法:我们从前往后做,每次加入两个数,用堆维护取了的数的集合。因为加入了两个数之后还要弹掉一个数,弹掉前面的也行,我们就弹掉堆里面最小的就好了。

注意到我们不仅能往后加,也可以往前加。维护没取的数的集合,前面加入两个数都不选,然后在没选的数里面再选一个最大的出来选掉。这样可以用回滚莫队做到 \(O(n \sqrt n \log n)\),用奇怪的数据结构也许能通过此题。

当然这不重要,重要的是这题怎么做。

首先处理掉奇数长度的询问,可以发现最后一个我们一定能取,那就直接取掉最后一个,然后再做前面的东西。

然后现在是有一堆区间。假设这个区间 \([l,r]\) 过同一个点 \(p\),那么我们要合并 \([l,p]\)\([p+1,r]\),先假设两个拆出来的区间长度都是偶数吧。

对于每一个 \([x,p]\) 用主席树维护我们选择了哪些数,对于每一个 \([p+1,x]\) 用主席树维护我们没选哪些数。注意到前面 \([l,p]\) 中的数中我们最多选择 \(\frac{p-l+1}{2}\) 个数,那么我们在 \([l,p]\) 里面选了的数和 \([p+1,r]\) 中没选的数中,选择 \(\frac{p-l+1}{2}\) 个最大的出来就好。

到这里大家都应该知道要用猫树分治做了,不多讲了。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
char buf[1<<18],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<18,stdin),p1==p2)?EOF:*p1++)
LL read()
{
	LL x=0;
	char c=getchar();
	while(c<'0' || c>'9')	c=getchar();
	while(c>='0' && c<='9')	x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
	return x;
}
void write(LL x)
{
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
#define Mm LL mid=(l+r)>>1
LL a[200005],b[200005];
LL rt[200005],cnt;
LL sum[6000005],pnt[6000005],lc[6000005],rc[6000005];
void modify(LL &now,LL lst,LL l,LL r,LL p,LL w)
{
	now=++cnt;
	lc[now]=lc[lst],rc[now]=rc[lst],pnt[now]=pnt[lst]+w,sum[now]=sum[lst]+w*b[p];
	if(l==r)	return ;
	Mm;
	if(p<=mid)	modify(lc[now],lc[lst],l,mid,p,w);
	else	modify(rc[now],rc[lst],mid+1,r,p,w);
}
LL query(LL x,LL y,LL l,LL r,LL c)
{
	if((!x && !y) || !c)	return 0;
	if(l==r)	return b[l]*c;
	if(pnt[x]+pnt[y]==c)	return sum[x]+sum[y];
	Mm;
	if(pnt[rc[x]]+pnt[rc[y]]>=c)	return query(rc[x],rc[y],mid+1,r,c);
	return sum[rc[x]]+sum[rc[y]]+query(lc[x],lc[y],l,mid,c-pnt[rc[x]]-pnt[rc[y]]);
}
LL n,q,len;
LL ql[200005],qr[200005],ans[200005];
vector<LL> qry[200005];
void Solve(LL l,LL r,vector<LL> &Qry)
{
	if(l==r || Qry.empty())	return ;
	Mm;
	for(LL i=l-1;i<=r+2;++i)	qry[i].clear();
	vector<LL> qryl,qryr;
	for(auto id:Qry)
	{
		if(qr[id]<=mid)	qryl.push_back(id);
		else if(ql[id]>mid)	qryr.push_back(id);
		else	qry[qr[id]].push_back(id);
	}
	for(LL o=0;o<2;++o)
	{
		for(LL i=l-1;i<=r+2;++i)	rt[i]=0;
		for(LL i=1;i<=cnt;++i)	sum[i]=pnt[i]=lc[i]=rc[i]=0;
		cnt=0;
		mid+=o;
		priority_queue<LL> Q1;
		for(LL i=mid;i>l;i-=2)
		{
			Q1.push(a[i]),Q1.push(a[i-1]);
			if(i^mid)	rt[i]=rt[i+2];
			LL c=Q1.top();
			Q1.pop();
			modify(rt[i],rt[i],1,len,c,1);
			rt[i-1]=rt[i];
		}
		for(auto id:qry[mid])	ans[id]+=sum[rt[ql[id]]];
		LL root=0,ret=0;
		priority_queue<LL,vector<LL>,greater<>> Q2;
		for(LL i=mid+2;i<=r;i+=2)
		{
			Q2.push(a[i-1]),Q2.push(a[i]);
			ret+=b[a[i-1]]+b[a[i]];
			LL c=Q2.top();
			Q2.pop();
			ret-=b[c];
			modify(root,root,1,len,c,1);
			for(auto id:qry[i])	ans[id]+=ret+query(rt[ql[id]],root,1,len,(mid-ql[id]+1)>>1);
		}
	}
	--mid;
	Solve(l,mid,qryl),Solve(mid+1,r,qryr);
}
int main(){
	n=read(),q=read();
	for(LL i=1;i<=n;++i)	a[i]=b[i]=read();
	sort(b+1,b+1+n);
	len=unique(b+1,b+1+n)-b-1;
	for(LL i=1;i<=n;++i)	a[i]=lower_bound(b+1,b+1+len,a[i])-b;
	vector<LL> Id;
	for(LL i=1;i<=q;++i)
	{
		LL l=read(),r=read();
		if((r-l+1)&1)	ans[i]+=b[a[r--]];
		if(l<=r)	Id.push_back(i),ql[i]=l,qr[i]=r;
	}
	Solve(1,n,Id);
	for(LL i=1;i<=q;++i)	write(ans[i]),puts("");
	return 0;
}
posted @ 2022-08-11 20:58  SyadouHayami  阅读(82)  评论(0编辑  收藏  举报

My Castle Town.