[省选集训2022] 模拟赛4

A

题目描述

给定一个长度为 \(n\) 的数列 \(a\) 和常数 \(c\),将其划分成若干段,设 \(b_i\) 表示第 \(i\) 段的最大值:

\[\sum_{i=2}^m (b_i-b_{i-1})^2+c \]

特别地,如果只划分出一段(\(m=1\))则答案为 \(0\)

\(2\leq n\leq 10^6,1\leq a_i\leq 10^6,0\leq c\leq 10^{10}\)

解法

\(dp[i]\) 表示 \(a_i\) 为最后一段的最大值的答案,转移枚举上一段的最大值点 \(j\)

\[dp[i]=\max\{dp[j]+(a_i-a_j)^2+c\} \]

转移的条件是 \([j,i]\) 可以被划分成两段使得 \(a_j,a_i\) 分别是这两段的最大值。考虑 \(k\in[i,j]\),如果 \(\exist\ a_k>a_i\and a_k>a_j\),那么肯定是划分不动的,但是考虑此时\(k\) 转移一定比 \(j\),所以不用考虑 \(j\) 合不合法,因为如果 \(j\) 不合法那么一定没用。

那么直接斜率优化即可,时间复杂度 \(O(n\log n)\),所以你按照 \(a_i\) 单增去打这题就可以过掉。

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 1000005;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,c,ans;
struct node
{
	int k,b;
	node(int K=0,int B=-9e18) : k(K) , b(B) {}
	int ask(int x) {return k*x+b;}
}t[M<<2];
void ins(int i,int l,int r,node nw)
{
	int mid=(l+r)>>1;
	if(nw.ask(mid)>t[i].ask(mid)) swap(t[i],nw);
	if(l==r) return ;
	if(nw.ask(l)>t[i].ask(l)) ins(i<<1,l,mid,nw);
	if(nw.ask(r)>t[i].ask(r)) ins(i<<1|1,mid+1,r,nw);
}
int ask(int i,int l,int r,int x)
{
	int res=t[i].ask(x),mid=(l+r)>>1;
	if(l==r) return res;
	if(mid>=x) return max(ask(i<<1,l,mid,x),res);
	return max(ask(i<<1|1,mid+1,r,x),res);
}
signed main()
{
	freopen("array.in","r",stdin);
	freopen("array.out","w",stdout);
	n=read();c=read();m=1e6;
	for(int i=1;i<=n;i++)
	{
		int x=read();
		int dp=max(0ll,ask(1,1,m,x)+x*x+c);
		ans=max(ans,dp);
		ins(1,1,m,node(-2*x,dp+x*x));
	}
	printf("%lld\n",ans);
}

B

题目描述

这是一道交互题,交互库有下列三个可调用的函数:

int qry1(int x);//返回 a[x]
vector<int> qry2(vector<int> &s);//返回内含 |a[s[i]]-a[s[j]]| (i>j) 的无序集合
void answer(vector<int> &ans);//给出交互答案,调用该函数即意味着结束程序

有长度为 \(n\) 的数组 \(a\),满足 \(a_i\) 互不相同,请通过交互求出这个数组。你不需要实现主函数,你只需要实现下列函数,并且要求 \(\tt qry1\) 的调用次数不超过 \(m_1\) 次,\(\tt qry2\) 的调用次数不超过 \(m_2\) 次,\(\tt answer\) 的调用次数恰好为一次。

void find(int n,int m1,int m2);

对于最大范围的数据满足:\(n\leq 250,m_1=2,m_2=30\)

解法

这种确定序列的题目有常规做法,那就是先找关键点,然后再确定整个序列

有一个 \(\tt observation\):我们从 询问 \(\{s\cup x\}\) 得到的集合 中除掉 询问 \(\{s\}\) 得到的集合,可以得到 \(s\) 中所有数和 \(x\) 的距离,那么我们的关键点可以设置成最大值或者最大值。

先问一次可以得到序列的极差,那么我们二分一个前缀是否包含极差,就可以得到位置 \(x\)(它是最大值或者最小值,但是暂时不清楚),这部分的花费是 \(1\log n\) 的。

然后我们确定整个序列,可以二进制分组(或者直接分治,原理都是一致的),因为每个距离都可以唯一代表一个值,我们只需要确定距离对应的位置。那么可以把包含 \(0,1,2...\log n\) 这些二进制位的位置都取出来,然后直接询问这个集合 \(\{s\}\) 和集合 \(\{s\cup x\}\),把得到的距离的 \(pos\) 都加上 \(2^i\)

那么最后的 \(pos\) 就是这个距离对应着的位置,小细节是这样做必须从 \(1\) 开始编号最好。并且我们可以得到另一个最值的位置,把它们两个都问一下就可以得到 \(x\) 是最大值还是最小值了。这一部分的消耗是 \(2\log n\) 的,所以总共的消耗是 \(3\log n\),足以通过本题。

#include "difference.h"
#include <vector>
#include <algorithm>
#include <map>
using namespace std;
#define pb push_back
#define vi vector<int>
void find(int n,int M1,int M2)
{
	vi A,B,ans;map<int,int> mp;
	for(int i=0;i<n;i++) A.pb(i);B=qry2(A);
	int m=*max_element(B.begin(),B.end());
	int l=1,r=n-1,x=0,t=0,p[505]={};
	while(l<=r)
	{
		int mid=(l+r)>>1;A.clear();
		for(int i=0;i<=mid;i++) A.pb(i);
		B=qry2(A);t=*max_element(B.begin(),B.end());
		if(t==m) r=mid-1,x=mid;
		else l=mid+1;
	}
	for(int i=0;(1<<i)<=n;i++)
	{
		A.clear();
		for(int j=0;j<n;j++)
			if(((j+1)&(1<<i)) && j!=x) A.pb(j);
		vi C=qry2(A);map<int,int> nw;
		A.pb(x);B=qry2(A);
		for(auto x:B) nw[x]++;
		for(auto x:C) nw[x]--;
		for(auto x:nw) if(x.second)
			mp[x.first]+=(1<<i);
	}
	auto it=mp.end();it--;int u=it->second;
	p[u]=qry1(u-1);p[x+1]=qry1(x);
	int fl=(p[u]>=p[x+1])?1:-1;
	for(auto t:mp) p[t.second]=t.first*fl+p[x+1];
	for(int i=1;i<=n;i++) ans.pb(p[i]);
	answer(ans);
}
posted @ 2022-02-08 17:40  C202044zxy  阅读(222)  评论(0编辑  收藏  举报