首页 > 其他分享 >CF2019D. Speedbreaker 题解

CF2019D. Speedbreaker 题解

时间:2024-09-29 15:13:06浏览次数:5  
标签:minn val int 题解 sum 拓展 CF2019D define Speedbreaker

介绍一种另解,以下称“征服”为“拓展”。

对于这些需要拓展,且拓展的时间有上界的题,我们通常都会有一个trick。那就是对于一个点 \(x\),用它可以拓展到的点 \(y\) 的时间上界把 \(x\) 的时间上界继续缩小。用到这种trick的题有 P9755 [CSP-S 2023] 种树[ABC304Ex] Constrained Topological Sort 等,以及本题。

关于这个trick在本题的具体体现,不妨设 \(t_i\) 为点 \(i\) 被拓展到的时间,\(a_i\) 为点 \(i\) 被拓展时间的上界。定义 \(L_i\) 为从点 \(i\) 拓展到点 \(1\),满足 \(\forall i\in[1,i],t_i\leq a_i\) 的最大的 \(t_i\)。则 \(L_i\) 的转移式为:

\[L_i=\begin{cases}a_i&i=1\\min(L_{i-1}-1,a_i)&i>1\end{cases} \]

这是因为从 \(i\) 拓展到 \(i-1\) 需要 \(1\) 的时间,且也要满足 \(t_i\leq a_i\)。

同理,定义 \(R_i\) 为从点 \(i\) 拓展到点 \(n\),满足 \(\forall i\in[i,n],t_i\leq a_i\) 的最大的 \(t_i\)。则:

\[R_i=\begin{cases}a_i&i=n\\min(R_{i+1}-1,a_i)&i<n\end{cases} \]

容易发现 \(L_i\) 是单调递减的,\(R_i\) 是单调递增的。

求出 \(L_i\) 和 \(R_i\) 有什么用呢?考虑当前枚举了一个起始点 \(P\)。对于除了 \(P\) 外的所有点,设其权值为

\[val_i=\begin{cases}L_i&i<P\\R_i&i>P\end{cases} \]

贪心的想,要想满足每个点的时间上界,我们可以优先拓展那些 \(val\) 值小的点。那这样思路就很明显了,就像 dijkstra 一样,每次把当前可以拓展的点扔进一个小根堆里,按 \(val_i\) 排序,每次从堆顶取出一个点就相当于拓展了这个点,并把时间计数器 \(cnt+1\),接着再把这个点可以拓展到且还未被拓展的点扔进堆里。判无解就是判当一个点被从堆顶取出时,当前的时间计数器是否满足 \(cnt\leq val_i\),如果不满足,则说明点 \(P\) 不能作为起始点。这里贴一份代码:

#include<bits/stdc++.h>
#define int long long
#define fr first
#define sc second
using namespace std;

int Rana;

int n,a[200005],L[200005],R[200005],ans;

struct node{
	int x,w;
};
inline bool operator <(const node A,const node B){
	return A.w>B.w;
}
int vis[200005],val[200005];

inline bool solve(int P){
	priority_queue <node> q;
	for(int i=1;i<=n;i++){
		val[i]=(i<P?L[i]:R[i]),vis[i]=0;//初始化val 
	}
	q.push({P,val[P]});
	int cnt=0;//时间计数器 
	while(q.size()){
		int x=q.top().x;
		q.pop(),cnt++,vis[x]=1;
		if(cnt>val[x])return false;//P不能作为起始点
		if(!vis[x-1]&&x-1>=1)q.push({x-1,val[x-1]});//往左拓展 
		if(!vis[x+1]&&x+1<=n)q.push({x+1,val[x+1]});//往右拓展 
	}
	return true;
}

inline int read(){
	register int x(0),t(0);
	static char ch=getchar();
	while(!isdigit(ch)){t|=(ch=='-'),ch=getchar();}
	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48),ch=getchar();}
	return t?-x:x;
}

signed main(){
    Rana=read();while(Rana--){

    n=read(),ans=0;
    for(int i=1;i<=n;i++){
        a[i]=read();
    }
    L[1]=a[1];
    for(int i=2;i<=n;i++){
        L[i]=min(L[i-1]-1,a[i]);
    }
    R[n]=a[n];
    for(int i=n-1;i>=1;i--){
        R[i]=min(R[i+1]-1,a[i]);
    }
    for(int i=1;i<=n;i++){
        ans+=solve(i);
    }
    cout<<ans<<'\n';

    }return 0;
}

但这样做是 \(O(n^2)\) 的,考虑如何优化。容易发现,对于一个起始点 \(P\)

\[t_i=\begin{cases}P-i+1+\sum\limits_{j=P+1}^{n}[R_j<=L_i]&i<P\\i-P+1+\sum\limits_{j=1}^{P-1}[L_j<=R_i]&i>P\end{cases} \]

这是为什么?考虑 \(i<P\) 的情况。对于 \(\sum\) 前面那一部分,如果想要拓展到 \(i\),则需先将 \([i+1,P+1]\) 的点扩展,因为开始在 \(P\) 时间就为 \(1\),所以要加 \(1\)。对于 \(\sum\) 的部分,则表示在点 \(P\) 右边的需要比 \(i\) 先拓展的点的个数,因为 \(R_i\) 单调递增的性质,可以发现这些点为以 \(P+1\) 为开头的一段前缀,正确性显然。\(R_i\) 同理。

但是若是有 \(i<P,j>P,L_i=R_j\) 的情况呢?这样的情况放在暴力代码中,\(val_i\) 是等于 \(val_j\) 的,且它们一定是先拓展一个,再拓展另一个。也就是说,\(t_i+1=t_j\) 或者 \(t_j+1=t_i\),判断是否可行则是看 \(\max(t_i,t_j)\) 是否小于 \(val_i\)。而在上面的式子中,\(t_i\) 和 \(t_j\) 都会是 \(\max(t_i,t_j)\),而这对可行判断是没有影响的。

有了这个式子,我们如何快速判断可不可行?我们可以将 \(val_i\) 减去 \(t_i\)。若是不可行,则 \(\min\limits_{i=1\land i\ne P}^{n}(val_i-t_i)<0\),其实就是看存不存在 \(t_i>val_i\)。

然后用数据结构维护就行了,我这里用了两棵平衡树,一颗维护 \(i<P\) 的点,以 \(L_i\) 作为排序的标准,一颗维护 \(i>P\) 的点,以 \(R_i\) 为排序的标准。一个平衡树的节点 \(i\) 要维护它排序的标准, \(val_i\);\(val_i-t_i\) 的值,\(sum_i\);以及它所在子树的 \(\min\limits_{j\in subtree_i} sum_j\),\(minn_i\)。设它们为 \(T_0,T_1\)。枚举 \(P\) 时,每次 \(P\leftarrow P+1\) 时,将 \(P+1\) 从 \(T_1\) 中删除,并将 \(P\) 加入 \(T_0\)。同时,将 \(T_1\) 整棵树的 \(sum\) 减 \(1\),\(T_0\) 整棵树的 \(sum\) 加 \(1\)。还要将 \(P+1\) 在 \(T_1\) 时对 \(T_0\) 的贡献抹去,\(P\) 在 \(T_0\) 时对 \(T_1\) 的贡献加上,具体表现为将 \(T_0\) 中 \(val\) 大于等于 \(R_{P+1}\) 的节点的 \(sum\) 加 \(1\),\(T_1\) 中 \(val\) 大于等于 \(L_{P}\) 的节点的 \(sum\) 减 \(1\)。

代码如下:

#include<bits/stdc++.h>
#define int long long
#define fr first
#define sc second
using namespace std;

const int inf=0x3f3f3f3f3f3f3f3f;

mt19937 rnd(time(0));

struct Treap{
    int val,sum,minn,siz,rnk,son[2],tag;
};

struct FHQ{
    #define ls t[k].son[0]
    #define rs t[k].son[1]
	int rt=0,cnt=0;
	Treap t[200005];
	inline void calc(int k,int val){
        if(k>0)t[k].minn+=val,t[k].sum+=val,t[k].tag+=val;return;
    }
    inline void pushup(int k){
		t[k].minn=min({t[k].sum,t[ls].minn,t[rs].minn}),t[k].siz=t[ls].siz+t[rs].siz+1;return;
	}
    inline void pushdown(int k){
        if(t[k].tag!=0){
            calc(ls,t[k].tag),calc(rs,t[k].tag);
        }t[k].tag=0;return;
    }
	inline int create(int val,int sum){
		t[++cnt]={val,sum,sum,1,rnd(),{0,0},0};return cnt;
	}
	inline void split(int val,int k,int &L,int &R){
		if(!k)L=R=0;
		else{
            pushdown(k);
			if(t[k].val<=val)L=k,split(val,rs,rs,R);
			else R=k,split(val,ls,L,ls);
			pushup(k);
		}return;
	}
	inline int merge(int L,int R){
		pushdown(L),pushdown(R);
		if(!L||!R){
			return L+R;
		}
		if(t[L].rnk<t[R].rnk){
			t[L].son[1]=merge(t[L].son[1],R),pushup(L);
			return L;
		}else{
			t[R].son[0]=merge(L,t[R].son[0]),pushup(R);
			return R;
		}
	}
	inline void insert(int val,int sum){
		int L,R;split(val,rt,L,R);
		rt=merge(merge(L,create(val,sum)),R);return;
	}
    inline void delet(int val){
		int L,R,mid;
		split(val,rt,L,R),split(val-1,L,L,mid);
        rt=merge(L,R);return;
	}
    inline int query(int val){
        int L,R,res;split(val,rt,L,R);
        res=t[L].siz,rt=merge(L,R);return res;
    }
    inline void updata(int val,int upd){
        int L,R;split(val-1,rt,L,R);
        calc(R,upd),rt=merge(L,R);return;
    }
    #undef ls
    #undef rs 
}T[2];

int Rana;

int n,a[200005],L[200005],R[200005],ans;

inline int read(){
	register int x(0),t(0);
	static char ch=getchar();
	while(!isdigit(ch)){t|=(ch=='-'),ch=getchar();}
	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48),ch=getchar();}
	return t?-x:x;
}

signed main(){
    Rana=read();while(Rana--){

    n=read(),ans=0;
    for(int i=1;i<=n;i++){
        a[i]=read();
    }
    L[1]=a[1];
    for(int i=2;i<=n;i++){
        L[i]=min(L[i-1]-1,a[i]);
    }
    R[n]=a[n];
    for(int i=n-1;i>=1;i--){
        R[i]=min(R[i+1]-1,a[i]);
    }
    
    T[0].rt=T[1].rt=T[0].cnt=T[1].cnt=0;
    T[0].t[0].minn=inf,T[1].t[0].minn=inf;
	for(int i=1;i<=n;i++){
        T[1].insert(R[i],R[i]-i-1);
    }
    for(int i=1;i<=n;i++){
        T[1].calc(T[1].rt,1),T[1].delet(R[i]);
		T[0].updata(R[i],1),T[0].calc(T[0].rt,-1);
        if(i>1){
            T[0].insert(L[i-1],L[i-1]-T[1].query(L[i-1])-2);
            T[1].updata(L[i-1],-1);
        }
        ans+=(T[0].t[T[0].rt].minn>=0&&T[1].t[T[1].rt].minn>=0);
    }
    cout<<ans<<'\n';

    }return 0;
}

标签:minn,val,int,题解,sum,拓展,CF2019D,define,Speedbreaker
From: https://www.cnblogs.com/PNNNN/p/18440000

相关文章

  • CF1648D Serious Business题解
    题目链接关键:DP状态的设计\(dp[i]\)表示走到\((2,i)\)的最小价值。转移分类讨论只用一个区间\(i\)从\([li,x]\)选择位置向下拐\(dp[i]=max_{li\lek\lex}(sum[1][k]+sum[2][x]-sum[2][k-1]+v[i])\)使用别的区间显然转移点小于\(li\),不然用一个区间即可。\(dp[i]=max_......
  • Codeforces Round 975 (Div. 2) A~F 题解(Div.1 A~D)
    CodeforcesRound975(Div.2)A~F题解(Div.1A~D)也是补完整场了。A.MaxPlusSize枚举最大值的位置,使长度最长,更新答案。B.AllPairsSegments每个线段内部的点的答案都是一样的,在处理一下线段两边边界的点。被包含次数就是左边\(\times\)右边。用\(map\)记录答案......
  • pbootcms出现重复的两篇文章问题解决(实际只发布一篇)
    当遇到PBootCMS后台列表中只有一篇文章,但在前端却显示了两条的情况时,问题很可能出在数据库中的 ay_content_ext 表中存在两条重复的关联数据。以下是详细的解决方案步骤:解决方案步骤确定文章ID:在后台找到该文章的ID,假设ID为13。打开数据库工具:使用Navicat......
  • ABC373F 题解
    容易发现这是一个完全背包问题,我们设状态\(f_{i,j}\)表示前\(i\)个物品使用了\(j\)个容量的最大价值。容易写出转移方程式:\(f_{i,j}=\max\limits_{k=0}^{\lfloor\frac{j}{w}\rfloor}f_{i-1,j-kw}+kv-k^2\)直接dp是\(O(n^3)\)。考虑对这个dp进行优化。上面的方程容......
  • 题解:P9947 [USACO20JAN] Photoshoot B
    P9947[USACO20JAN]PhotoshootB题解纯模拟!在\(a_{1}\)算出来之后,我们就可以通过\(b\)数组可以求出以后\(a\)数组的所有元素。要判断是否为排列,我们可以使用一个\(vis\)做桶,为了保证字典序最小,我们可以从\(1\)开始枚举,每次枚举前要清空一下\(vis\)数组,最后使用......
  • 题解:Luogu CF548A Mike and Fax
    CF548AMikeandFax题解题面翻译给定一个字符串和一个整数\(k\),问是不是恰好存在\(k\)个子字符串是回文串,并且所有子字符串的长度一样长。题目上说有\(k\)个子字符串,我们就可以把字符串分成\(k\)份,如果分不成\(k\)份(也就是说长度不是\(k\)的倍数)的话,直接输出NO。......
  • 【C语言】手把手带你拿捏指针(完)(指针笔试、面试题解析)
    文章目录一、sizeof和strlen的对⽐1.sizeof2.strlen3.sizeof与strlen对比二、数组和指针笔试解析1.一维数组2.字符、字符串数组和字符指针代码1代码2代码3代码4代码5代码63.二维数组4.总结三、指针运算笔试题解析代码1代码2代码3代码4代码5代码6一、sizeof和strl......
  • VS2008 应用程序配置不正确,未能启动该应用程序。重新安装程序可以修复此问题解决方法
    有时候我们把自己编译好的exe直接拷贝到别的电脑上使用时,如果那台电脑没装vs,一般程序无法运行提示:应用程序配置不正确,未能启动该应用程序。重新安装程序可以修复此问题。这是由于一般我们编译的程序都是使用的共享DLL,所以不一定保证其他机器上都有。如果使用静态DLL的话生......
  • 题解 ABC373G【No Cross Matching】/ POJ3565【Ants】
    题目描述年轻的自然主义者比尔在学校里研究蚂蚁。他的蚂蚁以生活在苹果树上的蚜虫为食。每个蚂蚁群需要自己的苹果树来养活自己。比尔有一张地图,上面标有\(n\)个蚂蚁群和\(n\)棵苹果树的坐标。他知道蚂蚁从它们的蚂蚁群到它们的取食地点,然后返回蚂蚁群,都是使用化学标记的路线......
  • 题解 CF407D【Largest Submatrix 3】/ SS240928C【c】
    题目描述给定一个\(n\timesm\)的正整数矩阵,求其中最大的满足其中不存在两个位置数值相等的子矩阵大小。\(1\leqn,m\leq400\)。本题有多种做法,而你需要寻找常数最小的做法才能通过本题。solution链表+双指针枚举上边界,逐渐下移下边界,枚举左边界,尝试双指针获得右边界......