2023.2.28日寄

2023.2.28 日寄

一言

一个人成熟的标志之一,就是明白每天发生在自己身上99%的事情,于别人而言根本毫无意义。——马克-鲍尔莱恩


模拟赛

Click Here


2023.2.28 日寄

模拟赛

文明种植计划

题意

\(~~~~\) 一个排列初始时 \(p_i=i\),给出 \(n\) 个数的目标位置(\(n\) 为偶数),每次允许交换满足 \(2|i-j|\geq n\) 的两个位置 \(i,j\) 上的数。构造一组在 \(M\leq 3\times 10^5\) 以内的方案。
\(~~~~\) \(1\leq n\leq 3\times 10^5\).

题解

\(~~~~\) 原题 CF1148C 限制是 \(5n\),随便搞。

\(~~~~\) 这题我们考虑一种在三步之内交换两个东西的方法,具体来说如果我们想把 \(y\) 换到 \(x\)

\(~~~~\) 如果两个能直接互换那直接换即可,否则:

\(~~~~\)\(x,y\) 同在某一侧,那就利用另一侧的端点来换。若在异侧,那么把 \(y\) 先换到对侧的端点,交换 \(1\)\(n\) 再换到 \(x\) 即可。

\(~~~~\) 可以发现把每个数换到一个位置用 \(3\) 步及以内,那就直接把每个数换到目标位置即可。当然因为 \(1\)\(n\) 在过程中会被打乱,所以 \(1\)\(n\) 要最后来放。

代码
查看代码
// LUOGU_RID: 103248552
#include <bits/stdc++.h>
#define ll long long
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
    x*=f;
}
vector<PII>Ans;
int n,Aim[300005],P[300005],pos[300005],To[300005];
inline int Abs(int x){return x>=0?x:-x;}
void Swap(int x,int y)
{
	if(2*Abs(x-y)>=n) Ans.push_back(mp(x,y)),swap(P[x],P[y]),pos[P[x]]=x,pos[P[y]]=y;
	else
	{
		if(x<=n/2&&y<=n/2)
		{
			Ans.push_back(mp(x,n));
			Ans.push_back(mp(n,y));
			Ans.push_back(mp(x,n));
			swap(P[x],P[y]); pos[P[x]]=x; pos[P[y]]=y;
		}
		else if(x>n/2&&y>n/2)
		{
			Ans.push_back(mp(x,1));
			Ans.push_back(mp(1,y));
			Ans.push_back(mp(x,1));
			swap(P[x],P[y]); pos[P[x]]=x; pos[P[y]]=y;
		}
		else if(x<=n/2&&y>n/2)
		{
			Ans.push_back(mp(y,1));
			Ans.push_back(mp(1,n));
			Ans.push_back(mp(n,x));
			swap(P[y],P[1]);swap(P[1],P[n]);swap(P[n],P[x]);
			pos[P[x]]=x; pos[P[n]]=n; pos[P[1]]=1; pos[P[y]]=y; 
		}
		else
		{
			Ans.push_back(mp(x,n));
			Ans.push_back(mp(1,n));
			Ans.push_back(mp(n,y));
			swap(P[x],P[n]); swap(P[1],P[n]); swap(P[n],P[y]);
			pos[P[x]]=x; pos[P[n]]=n; pos[P[1]]=1; pos[P[y]]=y; 
		}
	}
	
}
int main() {
//	freopen("project.in","r",stdin);
//	freopen("project.out","w",stdout);
	read(n);
	for(int i=1,x;i<=n;i++) read(x),Aim[x]=i,To[i]=x;
	for(int i=1;i<=n;i++) P[i]=i,pos[i]=i;
	for(int i=2;i<n;i++)
	{
		if(Aim[i]!=P[i])
			Swap(i,pos[Aim[i]]);
	}
	if(Aim[1]!=P[1]) Swap(pos[Aim[1]],1);
	if(Aim[n]!=P[n]) Swap(pos[Aim[n]],n);
	printf("%d\n",(int)Ans.size());
	for(int i=0;i<(int)Ans.size();i++) printf("%d %d\n",Ans[i].first,Ans[i].second);
	return 0;
}
/*
瑶草一何碧,春入武陵溪。溪上桃花无数,花上有黄鹂。我欲穿花寻路,直入白云深处,浩气展虹霓。只恐花深里,红露湿人衣。
坐玉石,欹玉枕。拂金徽。谪仙何处,无人伴我白螺杯。我为灵芝仙草,不为朱唇丹脸,长啸亦何为。醉舞下山去,明月逐人归。
*/ 

创世神的引导

\(~~~~\) 是一道捐出去的原创题,所以应出题人要求不写。


闯入的观测者

题意

\(~~~~\)\(n\) 行的三角形,第 \(i\) 行有 \(i\) 个数。从 \((1,1)\) 开始画折线,每次 \((i,j)\)\((i+1,j)\)\((i+1,j+1)\) 画折线,要求共 \(m\) 条折线,每条折线都不在上一条折线的左侧。且有 \(k\) 个限制 \((x,y,z)\),表示第 \(x\) 条折线在 \((y,c)\) 画折线必须向 \((y+1,c+z)\)
\(~~~~\) \(1\leq n,m\leq 20\).

题解

\(~~~~\) 考虑 \(dp_{i,S}\) 表示第 \(i\) 条折线,状态为 \(S\),那么直接转移复杂度 \(\mathcal{O(4^n \text{Poly}(n))}\),显然过不了。

\(~~~~\) 我们一格一格来,这样是 \(\mathcal{O(nm)}\) 的状态阶段,但是可以 \(\mathcal{O(1)}\) 转移过来。(类似轮廓线dp)但是怎么判左侧这个限制呢?还得加一维来记录上一条线在这里的 \(j\)。这样是 \(\mathcal{O(2^nn^2m)}\) 的,看起来很接近了,再优化一点点。

\(~~~~\) 我们发现实质是上一条线的 \(\operatorname{popcount}\) 前缀和时刻小于等于这条线的 \(\operatorname{popcount}\) 前缀和,那我们就不记录上一条线在这里的位置,而是改为如果在某个位置走了 \(1\) 那就把剩下的限制放宽即可,也即减去 \(\operatorname{lowbit}\).

代码
查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
    T sig=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')sig=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
    x*=sig;
}
const int MOD=1e9+7;
inline int Add(int a,int b){return (a+b)%MOD;}
inline int Dec(int a,int b){return (a-b+MOD)%MOD;}
inline int Mul(int a,int b){return 1ll*a*b%MOD;}
int f[25][25],dp[25][1048580];
inline int lowbit(int x){return x&(-x);}
int main() {
	memset(f,-1,sizeof(f));
	int n,m,q;
	read(n);read(m);read(q);
	for(int i=1,x,y,z;i<=q;i++) read(x),read(y),read(z),f[x][y]=z;
	dp[0][0]=1;int Now=0;
	for(int i=1;i<=m;i++)
	{
		for(int qwq=1,Tmp=1,now=(1<<(n-1))-1;qwq<n;qwq++,now-=Tmp,Tmp<<=1)
		{
			if(f[i][qwq]==0)
			{
				Now++; memset(dp[Now&1],0,sizeof(dp[Now&1]));
				for(int k=0;k<(1<<(n-1));k++)
					if(!(k&Tmp)) dp[Now&1][k]=Add(dp[Now&1][k],dp[!(Now&1)][k]);
			}
			if(f[i][qwq]==1)
			{
				Now++; memset(dp[Now&1],0,sizeof(dp[Now&1]));
				for(int k=0;k<(1<<(n-1));k++)
					dp[Now&1][k-lowbit(k&now)+Tmp]=Add(dp[Now&1][k-lowbit(k&now)+Tmp],dp[!(Now&1)][k]); 
			}
			if(f[i][qwq]==-1)
			{
				Now++; memset(dp[Now&1],0,sizeof(dp[Now&1]));	
				for(int k=0;k<(1<<(n-1));k++)
				{
					if(!(k&Tmp)) dp[Now&1][k]=Add(dp[Now&1][k],dp[!(Now&1)][k]);
					dp[Now&1][k-lowbit(k&now)+Tmp]=Add(dp[Now&1][k-lowbit(k&now)+Tmp],dp[!(Now&1)][k]);
				}
			}
		}
	}
	int Ans=0;
	for(int i=0;i<(1<<(n-1));i++) Ans=Add(Ans,dp[Now&1][i]);
	printf("%d",Ans);
	return 0;
}
/*
瑶草一何碧,春入武陵溪。溪上桃花无数,花上有黄鹂。我欲穿花寻路,直入白云深处,浩气展虹霓。只恐花深里,红露湿人衣。
坐玉石,欹玉枕。拂金徽。谪仙何处,无人伴我白螺杯。我为灵芝仙草,不为朱唇丹脸,长啸亦何为。醉舞下山去,明月逐人归。
*/

杂题选做

「CF533D」Landmarks

题意

\(~~~~\)\(x_0,x_1,\dots,x_n,x_{n+1}\) 处共有 \(n+2\) 根柱子,其中 \(1\sim n\) 的柱子有一个承重值。当一个柱子到与自己两边柱子的中点的距离和 \(\geq d_i\) 时这根柱子就会倒塌。认为 \(x_0\)\(x_{n+1}\) 的柱子承重为 \(+ \infty\)。当只剩下这两根柱子时整个房子就会倒塌。求加入或替换一个柱子,使得房子不倒塌的最小承重值。
\(~~~~\) \(1\leq n\leq 10^5\).

题解

\(~~~~\) 非常神的题目。妙可能谈不上,但这个分讨确实强大。

\(~~~~\) 首先考虑一下一个柱子会塌那就是 \(\frac{x_{i+1}-x_i}{2}-\frac{x_{i}-x_{i-1}}{2}> d_i\) ,也就是 \(x_{i+1}-x_{i-1}>2d_i\) ,和 \(x_{i-1}\) 以及 \(x_{i+1}\) 之外柱子的位置没有什么关系。那换句话说我们加入柱子后其实还有大段的柱子没有受到影响。

\(~~~~\) 所以我们维护一个 \(L_i\) 表示对于前缀 \([1,i-1]\) 的柱子,不管 \(i\) 怎么样最近的还立着的柱子,包括 \(0\)\(n+1\) 在内。同理定义 \(R_i\) 表示后缀最近。这个可以用单调栈做两遍来维护。

\(~~~~\) 然后我们来考虑以下三种情况:

\(~~~~\) 1.不需要加入柱子也不会塌。那就是存在至少一根柱子,满足 \(x_{R_i}-x_{L_i} \leq 2d_i\) ,如果找到了那答案就是 \(0\) 了。

\(~~~~\) 2.替换一根柱子,那么替换的柱子的承重必然是 \(\frac{x_{R_i}-x_{L_i}}{2}\),对所有柱子找一遍 \(\min\) 即可。

\(~~~~\) 3.加入一根柱子,继续分类:

\(~~~~~~~~~\) 3.1:加入的柱子是最后唯一立起来的:那么稳固度:\(\frac{x_{n+1}-x_0}{2}\).

\(~~~~~~~~~\) 3.2:加入的柱子位于一根端点和其他柱子之间:那么枚举那根“其他柱子”,它的 \(L\)\(R\) 一定是端点,那么找到这根柱子之后就直接加入这根柱子和端点柱子距离的一半即可。

\(~~~~~~~~~\) 3.3: 加入的柱子在两根其他柱子之间,假设这样的柱子是 \(i,j(i<j)\) ,那加入的高度必然是 \(\frac{x_j-x_i}{2}\),但是,等等,怎么判断一个加入是合法的,或者说可以放的?

\(~~~~~~~~~\) 首先 \(i,j\) 最后应该都是立起来的,所以假设这个柱子的位置是 \(p\),那么 \(\frac{p-x_{L_i}}{2}\leq d_i\)\(\frac{x_{R_j}-p}{2}\leq d_j\) 。显然这个 \(p\) 让人不舒服,所以我们把两个式子加起来:\(\frac{x_{R_j}-x_{L_i}}{2}\leq d_i+d_j\)

\(~~~~~~~~~\) 其次这个柱子能找到合适的 \(p\) 安放,由于有 \(x_i<p<x_j\) 那么由 \(\frac{p-x_{L_i}}{2}\leq d_i\) \(\Rightarrow\) \(x_i-x_{L-i}<2d_i\) 。同理由 \(\frac{x_{R_j}-p}{2}\leq d_j\) \(\Rightarrow\) \(x_{R_j}-x_j<2d_j\) 。满足这两个要求即可。

\(~~~~~~~~~\) 所以现在来看这一类的 \(i,j\) 怎么找?第一个限制我们把不等号两边化成只与 \(i\)\(j\) 相关:\(x_{R_j}-2d_j\leq x_{L_i}\)\(+2d_i\) 。第二个限制只与 \(i,j\) 有关直接提出来合法的。那么第一个限制就直接用 BIT 维护一个前缀 \(\min\) 或 后缀 \(\max\) 即可。

\(~~~~\) 以上三种(或者说五种)情况取每种的最小/最大值即可。

代码
查看代码
#include <bits/stdc++.h>
#define ll long long
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
    x*=f;
}
ll L[100005],R[100005];
ll pos[100005],d[100005],Sta[100005],Top;
ll brr[100005],tot;
inline ll ID(ll x){return lower_bound(brr+1,brr+1+tot,x)-brr;}
/*后缀最大值的BIT*/
struct BIT{
	ll tr[100005];
	inline ll lowbit(ll x){return x&(-x);}
	void Add(ll x,ll Val){for(;x;x-=lowbit(x)) tr[x]=max(tr[x],Val+1);}
	ll Query(ll x){ll ret=0;for(;x<=tot;x+=lowbit(x)) ret=max(ret,tr[x]);return ret-1;}
}BIT;
int main() {
	ll n;read(n);
	for(ll i=0;i<=n+1;i++) read(pos[i]);
	for(ll i=1;i<=n;i++) read(d[i]);
	for(ll i=1;i<=n;i++)
	{
		while(Top&&pos[i]-pos[L[Sta[Top]]]>2ll*d[Sta[Top]]) Top--;
		L[i]=Sta[Top];
		while(Top&&pos[L[Sta[Top]]]+2ll*d[Sta[Top]]<=pos[L[i]]+2ll*d[i]) Top--;
		Sta[++Top]=i;
	}
	Sta[Top=0]=n+1;
	for(ll i=n;i>=1;i--)
	{
		while(Top&&pos[R[Sta[Top]]]-pos[i]>2ll*d[Sta[Top]]) Top--;
		R[i]=Sta[Top];
		while(Top&&pos[R[Sta[Top]]]-2ll*d[Sta[Top]]>=pos[R[i]]-2ll*d[i]) Top--;
		Sta[++Top]=i;
	}
	/*Type1 加入没有用*/
	for(ll i=1;i<=n;i++)
		if(pos[R[i]]-pos[L[i]]<=2*d[i]) return puts("0")&0;
	/*Type2 全房的希望*/
	double Ans=1.0*pos[n+1]/2;
	/*Type3 替换一根柱子*/ 
	for(ll i=1;i<=n;i++) if(1.0*(pos[R[i]]-pos[L[i]])/2>=d[i]) Ans=min(Ans,1.0*(pos[R[i]]-pos[L[i]])/2);
	/*Type4 在两个普通柱子之间*/
	for(ll i=1;i<=n;i++)
		if(pos[i]-pos[L[i]]<2ll*d[i]) brr[++tot]=pos[L[i]]+2ll*d[i];
	sort(brr+1,brr+1+tot); tot=unique(brr+1,brr+1+tot)-brr-1;
	for(ll i=1;i<=n;i++)
	{
		if(pos[R[i]]-pos[i]<2*d[i])
		{
			ll res=BIT.Query(ID(pos[R[i]]-2ll*d[i]));
			if(~res) Ans=min(Ans,1.0*(pos[i]-res)/2);	
		}
		if(pos[i]-pos[L[i]]<2ll*d[i]) BIT.Add(ID(pos[L[i]]+2ll*d[i]),pos[i]); 
	}
	/*Type5 在普通和顶梁柱之间*/ 
	for(ll i=1;i<=n;i++)
	{
		if(L[i]==0&&pos[R[i]]-pos[i]<2ll*d[i]) Ans=min(Ans,1.0*pos[i]/2);
		if(R[i]==n+1&&pos[i]-pos[L[i]]<2ll*d[i]) Ans=min(Ans,1.0*(pos[n+1]-pos[i])/2);
	}
	printf("%.10f",Ans);
	return 0;
}
/*
瑶草一何碧,春入武陵溪。溪上桃花无数,花上有黄鹂。我欲穿花寻路,直入白云深处,浩气展虹霓。只恐花深里,红露湿人衣。
坐玉石,欹玉枕。拂金徽。谪仙何处,无人伴我白螺杯。我为灵芝仙草,不为朱唇丹脸,长啸亦何为。醉舞下山去,明月逐人归。

非常的耐人寻味啊
首先维护前缀能保留上的最左的柱子
pos{i+1}-pos_{i-1} \geq 2d_i
*/
posted @ 2023-02-28 18:42  Azazеl  阅读(33)  评论(0)    收藏  举报