2023NOIPA层联测12(ACCODER413)

2023NOIPA层联测12(ACCODER413)

2023年10月16日 / 信息学 / 算法

[ABC267F] Exactly K Steps

题目见洛谷。利用每个点在树上距离最远的点一定是直径之一之一的性质,再使用倍增LCA的方法就可以了。

细节不再赘述,自行看代码吧。

#include
using namespace std;

#define pii pair<int,int>
#define fi first
#define se second
const int maxn=2*1e5;
const int maxm=20;

int n,m;
struct Edge{int v,nxt;};
Edge e[maxn*2+5];
int hd[maxn+5],et;

pii pt[2];
int f[maxn+5][maxm+5];
int de[maxn+5];
int pw[maxm+5];

inline void Adde(int u,int v){e[et].v=v,e[et].nxt=hd[u],hd[u]=et++;}

pii Dfs(int u,int fa,bool flg)
{
	int v;
	pii mx=pii(0,u),tmp;
	for(int i=hd[u];~i;i=e[i].nxt)
	{
		v=e[i].v;
		if(v==fa) continue;
		if(flg)
		{
			f[v][0]=u;
			de[v]=de[u]+1;
		}
		tmp=Dfs(v,u,flg);
		tmp.fi++;
		mx=max(mx,tmp);
	}
	return mx;
}

inline void Init()
{
	pw[0]=1;
	for(int j=1;j<=maxm;j++)
	{
		pw[j]=pw[j-1]*2;
		for(int i=1;i<=n;i++)
			f[i][j]=f[f[i][j-1]][j-1];
	}
}
inline int Lca(int x,int y)
{
	if(de[x]<de[y]) swap(x,y);
	for(int i=maxm;i>=0;i--)
		if(de[f[x][i]]>=de[y])
			x=f[x][i];
	for(int i=maxm;i>=0;i--)
		if(f[x][i]!=f[y][i])
			x=f[x][i],y=f[y][i];
	if(x==y) return x;
	else return f[x][0];
}
inline int Dist(int x,int y){return de[x]-de[y];}
inline int Jump(int x,int v)
{
	for(int i=maxm;i>=0;i--)
		if(pw[i]<=v)
			v-=pw[i],x=f[x][i];
	return x;
}

signed main()
{
	freopen("intoxicated.in","r",stdin);
	freopen("intoxicated.out","w",stdout);
	
	int u,v;
	memset(e,-1,sizeof(e));
	memset(hd,-1,sizeof(hd));
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		scanf("%d%d",&u,&v);
		Adde(u,v);Adde(v,u);
	}
	pt[0]=Dfs(1,0,1);
	pt[1]=Dfs(pt[0].se,0,0);
	Init();
	int tmp,t1,t2;
	bool flg;
	scanf("%d",&m);
	while(m--)
	{
		flg=0;
		scanf("%d%d",&u,&v);
		for(int i=0;i<=1;i++)
		{
			tmp=Lca(u,pt[i].se);
			t1=Dist(u,tmp);
			t2=Dist(pt[i].se,tmp);
			if(v<=t1)
			{
				flg=1;
				printf("%d\n",Jump(u,v));
				break;
			}
			else if(v<=t1+t2)
			{
				flg=1;
				printf("%d\n",Jump(pt[i].se,t1+t2-v));
				break;
			}
		}
		if(!flg) printf("-1\n");
	}
	
	return 0;
}

[CF1168C] And Reachability

题目见洛谷。看到按位操作,往二进制方向想。

我们先预处理 a [ i ] [ j ] a[i][j] a[i][j]表示第 i i i个数二进制的第 j j j位( 0 0 0开始)。

对于 a [ x ] , a [ y ] a[x],a[y] a[x],a[y]它们之间可以到达要存在一个 j j j使得 a [ x ] [ j ] = a [ y ] [ j ] = 1 a[x][j]=a[y][j]=1 a[x][j]=a[y][j]=1。下文我们把 j j j称为 ( x , y ) (x,y) (x,y)的路径。

考虑动态规划, g [ i ] [ j ] g[i][j] g[i][j]表示小于 i i i的位置可以与 a [ i ] a[i] a[i]接上并且第 j j j位为 1 1 1的最大位置。

那么对于询问 [ l , r ] [l,r] [l,r],只要判断是否存在 j j j使得 g [ r ] [ j ] ≥ l g[r][j] \geq l g[r][j]l即可。

怎么转移,分两种情况需要或不需要中转,枚举 k k k作为中转的路径。

  • 中转: g [ i ] [ j ] = m a x ( g [ i ] [ j ] , g [ l ] [ j ] ) ( a [ l ] [ k ] = a [ i ] [ k ] = 1 ) g[i][j]=max(g[i][j],g[l][j]) (a[l][k]=a[i][k]=1) g[i][j]=max(g[i][j],g[l][j])(a[l][k]=a[i][k]=1)
  • 不中转: g [ i ] [ j ] = m a x ( g [ i ] [ j ] , l ) ( a [ l ] [ k ] = a [ i ] [ k ] = a [ l ] [ j ] = 1 ) g[i][j]=max(g[i][j],l) (a[l][k]=a[i][k]=a[l][j]=1) g[i][j]=max(g[i][j],l)(a[l][k]=a[i][k]=a[l][j]=1)

此时仍需枚举 l l l,考虑再动态规划预处理 l l l。观察 l l l的条件不难发现 l l l需要满足 l , i l,i l,i之间有路径 k k k,并且因为 g g g递增,所以 l l l越大越好。

定义 f [ i ] [ j ] f[i][j] f[i][j]表示小于 i i i的第 j j j位为 1 1 1的最大点。那么有转移:

  • 如果 a [ i − 1 ] [ j ] = 1 a[i-1][j]=1 a[i1][j]=1 f [ i ] [ j ] = i − 1 f[i][j]=i-1 f[i][j]=i1
  • 否则, f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j]=f[i-1][j] f[i][j]=f[i1][j]

所有初始值都为 0 0 0即可解决这道题。

#include
using namespace std;

const int maxn=3*1e5;
const int maxs=20;

int n,m,q;
int a[maxn+5][maxs+5];

int st[maxs+5],stt;
int f[maxn+5][maxs+5],g[maxn+5][maxs+5];

signed main()
{
	freopen("and.in","r",stdin);
	freopen("and.out","w",stdout);
	
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i][0]);
		stt=0;
		while(a[i][0]>0)
		{
			st[stt++]=a[i][0]&1;
			a[i][0]>>=1;
		}
		for(int j=0;j<stt;j++)
			a[i][j]=st[j];
		m=max(m,stt);
	}
	
	for(int i=1;i<=n;i++)
		for(int j=0;j<m;j++)
			f[i][j]=a[i-1][j]?i-1:f[i-1][j];
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<m;j++)
		{
			for(int k=0;k<m;k++)
			{
				if(a[i][k])
				{
					g[i][j]=max(g[i][j],g[f[i][k]][j]);
					if(a[f[i][k]][j]) g[i][j]=max(g[i][j],f[i][k]);
				}
			}
		}
	}
	
	int x,y;
	bool flg;
	while(q--)
	{
		scanf("%d%d",&x,&y);
		flg=0;
		for(int i=0;i<m;i++)
		{
			if(a[x][i]&&g[y][i]>=x)
			{
				flg=1;
				break;
			}
		}
		printf("%s\n",flg?"Shi":"Fou");
	}
	
	return 0;
}

[CF1651F] Tower Defense

题目见洛谷。考虑分块,一个怪经过一座塔只有两种情况:

  • 塔内能量清零,怪还有血
  • 怪被某一个塔击杀

一个怪只能被击杀 m m m次,若依第二种情况只会出现 m m m次。

我们对于每一个块维护一个平推标记,并记录上一次操作的时间,被平推 i i i秒后造成的伤害,对于每个怪一次处理每个块。

  • 如果怪每某一个塔击杀,暴力做,撤销推平标记
  • 否则,如果没有推平标记,暴力做,打上推平标记
  • 否则,怪血量减掉对应的值,推平标记不变。

分析一下,时间、空间复杂度为 O ( n n ) O(n\sqrt n) O(nn )级别左右。虽然时间没问题,但是空间并不允许。

我们考虑离线怪物,因为怪物依次经过块,所以只要对于每一个块枚举怪物就好,只需保存当前块的信息,空间降至 O ( n ) O(n) O(n)级别。

所以我们解决了这道题,当然代码中还有很多实现细节,可以参考注释。

代码常数很大,见谅。

#include
using namespace std;

#define int long long
const int maxn=4*1e6;
int sz;

int n,m;
int ans;
int tim;//操作时间
bool sta;//平推标记 注意:1表示没有平推
int sum[maxn+5];//平推若干秒后造成的伤害
int cnt[maxn+5],lst[maxn+5];//约等于临时变量,忽略
int c[maxn+5];//塔的血量

struct Tower{int mx,dl;};//塔的上限,恢复速度
Tower T[maxn+5];
struct Dino{int tim,hel;};//怪的出现时间,血量
Dino D[maxn+5];
struct Block{int id,l,r;};//分块编号,左右端点
Block B[maxn+5];

inline void Init()//初始化分块
{
	sz=sqrt(n);
	for(int i=1;i<=n;i++)
		c[i]=T[i].mx;
	for(int i=1;i<=n;i++)
	{
		B[i].id=(i-1)/sz+1;
		B[i].l=(B[i].id-1)*sz+1;
		B[i].r=min(B[i].id*sz,n);
	}
}

inline void Work1(int x,int y)//暴力扫
{
	int now=D[y].tim+B[x].l-1;//塔被攻击的时间
	sta=0;
	for(int i=B[x].l;i<=B[x].r;i++)
	{
		c[i]=min(T[i].mx,c[i]+(now-tim)*T[i].dl);
		if(D[y].hel>=c[i])
		{
			D[y].hel-=c[i],
			c[i]=0;
		}
		else
		{
			c[i]-=D[y].hel,
			D[y].hel=0,
			sta=1;
		}
	}
}
inline void Work2()
{
	for(int x=1;x<=n;x+=sz)
	{
		tim=-1e9;
		sta=1;
		memset(sum,0,sizeof(sum));
		for(int i=B[x].l;i<=B[x].r;i++)
		{
			cnt[i]=T[i].mx/T[i].dl,
			lst[i]=T[i].mx%T[i].dl,
			sum[1]+=T[i].dl,
			sum[cnt[i]+1]-=T[i].dl-lst[i],
			sum[cnt[i]+2]-=lst[i];
		}
		for(int i=1;i<=4e5;i++)
			sum[i]+=sum[i-1];
		for(int i=1;i<=4e5;i++)
			sum[i]+=sum[i-1];
		/*
		需要解释的是sum的处理,每一个塔随着时间能量先上升后不变,所以做第一次差分,又因为要统计所有塔的能量和,所以总共做两次前缀和得出答案。
		*/
		for(int i=1;i<=m;i++)
		{
			if(D[i].hel==0)
				continue;
			int now=D[i].tim+B[x].l-1;
			if(sta)
			{
				Work1(x,i);
			}
			else
			{
				if(D[i].hel<sum[now-tim])
				{
					// for(int j=B[x].l;j<=B[x].r;j++)
					// 	c[i]=0;
					Work1(x,i);
				}
				else
				{
					D[i].hel-=sum[now-tim];
					D[i].hel=max(D[i].hel,0ll);
				}
			}
			tim=now;
		}
	}
}

signed main()
{
	//	freopen("dinosaurs.in","r",stdin);
	//	freopen("dinosaurs.out","w",stdout);	
	
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)
		scanf("%lld%lld",&T[i].mx,&T[i].dl);
	scanf("%lld",&m);
	for(int i=1;i<=m;i++)
		scanf("%lld%lld",&D[i].tim,&D[i].hel);
	
	Init();
	Work2();
	
	for(int i=1;i<=m;i++)
		ans+=D[i].hel;
	
	printf("%lld\n",ans);
	
	return 0;
}

[NOI Online 2021 提高组] 愤怒的小 N

题目见洛谷。

正在学习拉格朗日插值法。

你可能感兴趣的:(信息学,比赛,算法,c++)