• 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),下午听了讲解才有点经验,就是想到考场上要一边写题一边看这边有没有运行完让我很难受。。

  • 相关阅读:
    20172303 2017-2018-2 《程序设计与数据结构》实验四报告
    20172303 2017-2018-2 《程序设计与数据结构》实验三报告
    背景图实验
    20172322 2017-2018-2 《程序设计与数据结构》实验五报告
    20172322 2017-2018-2 《程序设计与数据结构》第十一周学习总结
    172322 2017-2018-2 《程序设计与数据结构》实验四报告
    172322 2017-2018-2 《程序设计与数据结构》实验三报告
    20172322 2017-2018-2 《程序设计与数据结构》第十周学习总结
    20172303 20172322 2017-2018-2 《程序设计与数据结构》结对编程项目-四则运算 项目总结
    20172322 2017-2018-2 《程序设计与数据结构》第九周学习总结
  • 原文地址:https://www.cnblogs.com/jkzcr01-QAQ/p/13574099.html
Copyright © 2020-2023  润新知