8-18模拟赛解题报告

T1:蓝蓝的棋盘

题意:两人互相将一棋子从(0)右移到(n),每次移动到位置(i)时会得到(a[i])的分数,移动距离最远不能超过(m),求两人都在最优策略下先手比后手分数高多少。

这题整体来说不难,只要会博弈论DP其实就能A,但这题没考虑到逆推,让我考场上想了挺久。

直接上解法吧!

用逆推,(f[i][2])表示从(i)节点开始移动(不包括(a[i])在内)得到的分数减去对方的分数最高为多少。

有了状态,转移就简单了,到下一个节点时会得到那个节点的分数,同时变成了在那个节点对方先手移动的最优解,所以每次从(i+1)~(i+m)转移过来,即

[f[i][0]=maxleft{a[j]-f[j][1] ight} ]

其中(i+1le jle min(i+m,n))

(f[i][1])则与之相反,直到转移到(f[0][0]),而不存在(f[0][1]),因为后手不可能从(0)开始,同时先手也不可能从(1)开始,所以也没有(f[1][0])

因为先后手开始的转移方程相同,或者转移之后,其实可以把(f[i][0/1])输出来看看,其实在(i>1)时,(f)值其实都是相同的。所以我们可以见小一维,同时减小代码量,所以整体时间复杂度为(O(nm)),可以过掉(80)分。

现在开始考虑优化,因为每次都是从后(m)个数转移过来,转移方程也不涉及(i),很容易想到单调队列优化,把时间优化为(O(n)),可以AC。

小结:

解题关键:想到逆推博弈论DP与单调队列优化。

芝士点:单调队列优化DP,逆推博弈论DP

整体来说不算太难,挺适合作D2T1,所以

手起,码落:

#include<bits/stdc++.h>
#define re register
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define inf 1e18
using namespace std;
const int N=500005;
inline int read()
{
	re int x=0,f=1;
	re char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar())if(ch=='-')f*=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
	return x*f;
}
int n,m,a[N],l,r,st[N];
long long f[N],mi,que[N];
int main()
{
	n=read(),m=read();
	memset(f,-63,sizeof(f));
	for(re int i=1;i<=n;i++) a[i]=read();
	r=0,l=1;que[++r]=a[n],st[r]=n;
	for(re int i=n-1;i>=0;i--)
	{
		if(st[l]-i>m)l++;
		f[i]=que[l];
		for(;l<=r&&que[r]<=a[i]-f[i];r--);
		que[++r]=a[i]-f[i],st[r]=i;
	}
	printf("%lld",f[0]);
	return 0;
}

T2:淘淘的集合

题意:给出一个队列,开始每个点都属于自己的那个集合,现在有四种操作:

  • 合并两个集合

  • 将一个集合中的数加上(v)

  • (l)~(r)区间归零

  • 查询(l)~(r)区间的和

这题有(30)分的暴力分和(80)分的部分分,考场上不愿打,所以就拿了(30)的暴力分,后果就是(DeBug)一下午没(De)出来。。

本题有一个很有用的限制:操作(4)的查询节点总数(le 10^7)

先回顾一下部分分写法:

  • (10\%):没有操作(1),也就是说没有集合合并,操作(2)也就变成了单点修改,再加上操作(3)的区间修改与操作(4)的区间查询,很容易想到线段树维护

  • (20\%+20\%):没有操作(3)和操作(3)的修改节点总数(le 10^7),因为操作(3),数据量比较小,可以暴力搞,加时间戳和并查集维护合并操作,同时还需要启发式合并,把时间复杂度降到(O(nlogn))。主要操作也就是合并区间时暴力把小的那一边的值全部加上,再合并,因为每次合并区间大小至少都变成原来的两倍,所以保证了时间复杂度。

开始正解:在之前的基础上,当操作(3)没有限制时,可以用分块维护区间同时加上时间戳,归零可以看做成减去当前的值,可以在之后查询时一起加上。讲起来简单,代码细节很多,可以仔细看看。

小结:

解题关键:标准码农题,想到用正确的数据结构维护并且打代码时不粗心,不然写得出来也调不出来。

芝士点:并查集,分块,启发式合并。

手起,码落:

#include<bits/stdc++.h>
#define re register
using namespace std;
inline int read()
{
	re int x=0,f=1;
	re char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar())if(ch=='-')f*=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
	return x*f;
}
const int N=200005,M=500005,Q=10000005;
struct ASK{int l,r,tim,i;}ask[M];
struct Query{int i,tim,l;}mid_ask[Q];
vector <Query> q[M];
vector <int> p[N];
int fk_lazy[N],num,asknum,askall,n,m,a_x[N],a_y[N],a_type[N],fkt[N],id[N],idl[N],idr[N],fa[N];
long long val[N],ans[M],dis_lazy[N];
inline void fk_first()
{
	num=sqrt(n)+1;
	for(re int i=1;i<=n;i++)id[i]=(i-1)/num+1;
	for(re int i=1;i<=num;i++)idl[i]=(i-1)*num+1,idr[i]=min(n,i*num),fk_lazy[i]=-1;
}
inline int whe_cover(int x){return fk_lazy[id[x]]==-1?fkt[x]:fk_lazy[id[x]];}
inline void Recover(int x)
{
	if(fk_lazy[x]==-1)return ;
	for(re int i=idl[x];i<=idr[x];i++)fkt[i]=fk_lazy[x];
	fk_lazy[x]=-1;
}
inline void fk_add(int l,int r,int tim)
{
	re int fl=id[l],fr=id[r];
	if(fl==fr)
	{
		Recover(fl);
		for(re int i=l;i<=r;i++)
			fkt[i]=tim;
	}
	else
	{
		Recover(fl);
		for(re int i=l;i<=idr[fl];i++)fkt[i]=tim;
		for(re int i=fl+1;i<fr;i++)fk_lazy[i]=tim;
		Recover(fr);
		for(re int i=idl[fr];i<=r;i++)fkt[i]=tim;
	}
}
inline void dis_first()
{
	for(re int i=1;i<=n;i++)
	{
		fa[i]=i;
		p[i].push_back(i); 
	}
}
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline void concer(int x,int y)
{
	re int fx=find(x),fy=find(y);
	if(fx==fy)return ;
	if(p[fx].size()<p[fy].size())swap(fx,fy);
	for(re int i=0;i<p[fy].size();i++)val[p[fy][i]]+=dis_lazy[fy];
	dis_lazy[fy]=0;fa[fy]=fx;
	for(re int i=0;i<p[fy].size();i++)val[p[fy][i]]-=dis_lazy[fx],p[fx].push_back(p[fy][i]);
	p[fy].clear();
}
inline long long query(int x){return val[x]+dis_lazy[find(x)];}
int main()
{
	n=read(),m=read();
	fk_first();
	for(re int i=1;i<=m;i++)
	{
		a_type[i]=read(),a_x[i]=read(),a_y[i]=read();
		if(a_type[i]==3)fk_add(a_x[i],a_y[i],i);
		else if(a_type[i]==4)
		{
			ask[++asknum].l=a_x[i],ask[asknum].r=a_y[i],ask[asknum].tim=i,ask[asknum].i=asknum;
			for(re int j=a_x[i];j<=a_y[i];j++)
			{
				if(!whe_cover(j))continue;
				mid_ask[++askall].i=asknum,mid_ask[askall].tim=whe_cover(j),mid_ask[askall].l=j;
				q[mid_ask[askall].tim].push_back(mid_ask[askall]);
			}
		}
	}
	re int now=1;
	dis_first();
	for(re int i=1;i<=m;i++)
	{
		if(a_type[i]==1)concer(a_x[i],a_y[i]);
		else if(a_type[i]==2)dis_lazy[find(a_x[i])]+=a_y[i];
		for(re int j=0;j<q[i].size();j++)ans[q[i][j].i]-=query(q[i][j].l);
		for(;now<=asknum&&ask[now].tim<=i;)
		{
			for(re int j=ask[now].l;j<=ask[now].r;j++)
				ans[ask[now].i]+=query(j);
			now++;
		}
	}
	for(re int i=1;i<=asknum;i++)printf("%lld
",ans[i]);
	return 0;
}

T3:蓝蓝的程序

非传统提交答案题???完全没做过!!!模拟赛直接爆(0),下午听了讲解才有点经验,就是想到考场上要一边写题一边看这边有没有运行完让我很难受。。

原文地址:https://www.cnblogs.com/jkzcr01-QAQ/p/13574099.html