CQBZ Round 12

首先,在本次考试中,我深刻认识到了常数优化的重要性。

我决心改掉 #define int long long 的坏习惯。

我们不能保证评测机的速度,我们只能保证自己代码的优秀。`

同时我要下定决心更改自己的思维方式。

我发现自身在做题时应合理利用时间,创造价值,不能出现T6这种写2.5h没拿一分的情况了。

我的思维方式是存在问题的,每当想到了复杂度正确的做法就开写,没有考虑到实现的细节以及复杂度是否真正正确。

导致实现+调试时遇到巨大困难。

magic

注意到 \(1\le a,b\le 10^{10}\),而限制条件一要求 \(x=y^2\),完全平方数只有 \(10^5\) 个。完全可以暴力枚举每一个完全平方数,然后 \(O(\log_{10} b)\) 判断即可。

复杂度 \(O(\sqrt b\log_{10}b)\)

linear

\(a_0=a_1-1\),即求:

\[\min\lbrace\sum_{i=1}^n|a_0+i-b_i|\rbrace \]

\(c_i=i-b_i\),则根据中位数的性质,\(a_0\)\(c\) 的中位数即可。

bracket

坑题。数据很小,我们可以在 \(O(n^3)\) 去处理一个括号,这里采用一对一对拆括号的形式。

先用栈处理出每个括号的对应位置。

然后枚举每一对括号进行拆解。这里我们枚举括号的右端点,秉承先拆小括号的原则。

将没有被拆的括号看作一个整体,也即一个字符

那么一个括号不能被拆的充要条件是:

  1. 左括号前有 *,/
  2. 括号内含有加减运算,注意我们将一个未被拆的括号看作一个元素

变号,前方是减号就变正负号,是除号就变乘除号。

void solve(){
	cin>>a+1;
	int n=strlen(a+1);
	for(int i=1;i<=n;i++){
		pos[i]=vis[i]=0;
		if(a[i]=='(')s.push(i);
		if(a[i]==')')pos[i]=s.top(),pos[s.top()]=i,s.pop();
	}
	for(int i=1;i<=n;i++){
		if(a[i]==')'){
			int tag=0;
			for(int j=pos[i]+1;j<i;j++){
				if(a[j]=='('&&vis[j]==0)j=pos[j]+1;
				if(a[j]=='+'||a[j]=='-')tag=1;
			}
			if(a[i+1]=='*'||a[i+1]=='/'||a[pos[i]-1]=='*'||a[pos[i]-1]=='/'){
				if(tag)continue;
			}
			vis[i]=vis[pos[i]]=1;
			if(a[pos[i]-1]=='-'){
				for(int j=pos[i]+1;j<i;j++){
					if(a[j]=='('&&vis[j]==0)j=pos[j]+1;
					if(a[j]=='+')a[j]='-';
					else if(a[j]=='-')a[j]='+';
				}
			}
			if(a[pos[i]-1]=='/'){
				for(int j=pos[i]+1;j<i;j++){
					if(a[j]=='('&&vis[j]==0)j=pos[j]+1;
					if(a[j]=='*')a[j]='/';
					else if(a[j]=='/')a[j]='*';
				}
			}
		}
	}
	for(int i=1;i<=n;i++){
		if(!vis[i])cout<<a[i];
	}
	cout<<"\n";
} 

treasure

简单的问题,显然我们是贪心地将路径的前面改为 a。我们计算 \(s_{i,j}\) 表示 \((1,1)\rightarrow (i,j)\) 使得路径上全是 a 的操作次数。

\(s_{1,1}=[a_{1,1}\neq \text{a}],s_{i,j}=\min(s_{i-1,j}(i>1),s_{i,j-1}(j>1))+[a_{i,j}\neq \text{a}]\)

然后 \(\forall i,j,s_{i,j}\le k,a_{i,j}\leftarrow\text{a}\)

成功将问题化为 \(k=0\) 的情况。

现在考虑求解这个问题。朴素的想法的BFS,虽然数据没卡,但可以被卡——复杂度是错的。

有一个显然的性质是,对于所有字符串的第 \(k\) 个字符,其必然是由某个点 \((x,y),x+y=k+1\) 跑来的。

这启发我们依次拼出每个字符。可以动态维护最优集合,设 \(f_{i,j}\) 表示当前点 \((i,j)\) 是否在最优集合中。

设当前在拼第 \(k\) 位,我们对于所有 \((x,y)\),满足 \(f_{x-1,y}|f_{x,y-1}\) 为真的点取字典序最小的字符,将这些更新,加入新的决策集合。

这样我们就可以 \(O(n^2)\) 地得到最优解了。

//We can make the k become 0 easily.
//How to do it when the k is equal to 0?
/*
One naive views is to use DP.But comparing two string needs O(n). 
Can we speed it?
Maybe we can use the MIM.Let it meet in the middle.
Through it,we can make the n reduce to the middle of before.
But the cost?
However,any act only be the (i+j-1)th.
So,We can make the set S to achieve it.
*/ 
void init(){
	cin>>n;int k;cin>>k;
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)cin>>a[i][j];
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){
		s[i][j]=0x3f3f3f3f;
		if(i==1&&j==1){
			s[i][j]=(a[1][1]!='a');
			if(s[i][j]<=k)a[i][j]='a'; continue; 
		} 
		if(i>1)s[i][j]=min(s[i][j],s[i-1][j]);
		if(j>1)s[i][j]=min(s[i][j],s[i][j-1]);
		s[i][j]+=(a[i][j]!='a');
		if(s[i][j]<=k)a[i][j]='a'; 
	} 
	f[1][1]=1;ans[2]=a[1][1];
	for(int k=3;k<=(n<<1);k++){
		char x='z';
		for(int i=1;i<k;++i){
			if(k-i>n)continue;
			if(i>n)continue;
			if(f[i-1][k-i]|f[i][k-i-1])x=min(x,a[i][k-i]);
		}
		for(int i=1;i<k;++i){
			if(k-i>n)continue;
			if(i>n)continue;
			if(a[i][k-i]==x)f[i][k-i]=(f[i-1][k-i]|f[i][k-i-1]);
		}
		ans[k]=x;
	}
	for(int i=2;i<=(n<<1);i++)cout<<ans[i];
	cout<<"\n";
}

summation

其实是一个蛮简单的数位DP,可惜我考场上选择了开“更好想”的T6。。。。

考场做T6的2h+应该是可以做出此题的。。。

最精妙的地方:对于每一位数字统计贡献

我们考虑统计数字 \(d\) 的贡献,这里仍然采用填数的过程。

首先考虑没有最高位限制的情况:

\(f_{i}\) 为填了前 \(i\) 位的总贡献,设 \(g_{i}\) 为第 \(i+1\) 位填 \(d\)\(d\) 这一位造成的贡献(本质上是 \(d\) 在每一位上出现次数再乘上 \(10^k\))。

那么显然,我们枚举当前位填 \(k\),那么对于数字 \(d\) 只有三种影响:

  • \(k<d\),此时它排序后在 \(d\) 前面,则对 \(d\) 的贡献无影响,固有 \(f_{i+1}\leftarrow f_i,g_{i+1}\leftarrow g_{i}\)
  • \(k=d\),此时再填一个 \(d\) 的贡献也同样毫无变化,\(g_{i+1}\leftarrow g_{i}\),但对于 \(f\) 而言,之前的所有 \(d\) 都向高位移动了一格,且加上了这个 \(d\),固有 \(f_{i+1}\leftarrow 10f_{i}+g_{i}\)
  • \(k>d\),此时排序后在 \(d\) 后面,会导致 \(d\) 全部向高位移动一格,固有 \(f_{i+1}\leftarrow 10f_{i},g_{i+1}\leftarrow 10g_{i}\)

再考虑一下最高位限制,我们只需要对 \(f,g\) 再开一维,同时转移时候注意转移到的版本即可。

初始化:有最高位版本限制的 \(g_0=d\),其他都是0。这是为了保证没有最高位限制的的正确计算。

void solve(int d){
	g[0][1]=d;
	for(int i=0;i<n;i++){
		f[i+1][0]=f[i+1][1]=g[i+1][0]=g[i+1][1]=0;
		int x=a[i+1]-'0';
		for(int j=0;j<2;j++){
			for(int k=0;k<10;k++){
				if(j&&k>x)break;
				int s=(j&(k==x));
				if(k<d)f[i+1][s]=(f[i+1][s]+f[i][j])%p,g[i+1][s]=(g[i+1][s]+g[i][j])%p; 
				if(k==d)f[i+1][s]=(f[i+1][s]+10ll*f[i][j]+g[i][j])%p,g[i+1][s]=(g[i+1][s]+g[i][j])%p;
				if(k>d)f[i+1][s]=(f[i+1][s]+10ll*f[i][j])%p,g[i+1][s]=(g[i+1][s]+10ll*g[i][j])%p;
			}
		}
	}
	ans=(ans+f[n][1]+f[n][0])%p;
}

mid

注意到 \(m\) 远小于 \(n\) ,这启发我们用较小的时间预处理,用较长的时间进行回答。

这是区间第 \(k\) 大问题的变种。

求解区间第 \(k\) 大有两个通用的思路,一个是进行二分,根据排名进行判断,一个是使用主席树上二分+差分。

显然这个区间端点可移动是很恶心的。

根据最初的分析,不难想到二分这个第 \(k\) 大,判断它是否可行。

在这里,我最初的思想是,由于 \([b,c]\) 是必选,先求出在 \([b,c]\) 中小于 \(mid\) 的数的个数。然后考虑在 \([a,b-1],[c+1,d]\) 中选数。

这时候,问题变为贪心地找到一个区间 \([l,b-1]\),使得 \(cnt_{x\ge mid}-cnt_{x<mid}\) 最大,再找到一个区间 \([c+1,r]\),使得 \(cnt_{x\ge mid}-cnt_{x<mid}\) 最大。

这三个值加起来,如果大于了 \(\lfloor\frac{c-b+1}{2}\rfloor\) 就有解。

在代码上我是考虑对于每一个值域开主席树维护,标记永久化,具体做法就不详细说了,因为我挂了。

这引申出了一个重要的 Trick。

对于维护区间 \([l,r]\) 内满足某类限制的数的个数 \(c\) 与区间长度进行操作的问题,也即维护与 \(r-l+1-c\) 有关的问题,可以将满足该类限制的数看做 1,不满足的看做0.

在这里,将 \(x\ge mid\) 看做 1,其余看做 -1,问题变成了选出两个区间 \([l,b-1],[c+1,r],(l\ge a,r\le d)\) ,使得他们的和最大。

对于固定左/右端点的最大区间和问题,就是维护最大子段和里的向左向右子段和。

可以在主席树里进行维护。那么只需要 \(rmax_{[a,b-1]}+sum_{[b,c]}+lmax_{[c+1,d]}\ge 0\) 就说明区间中小于它的数字较少,中位数大于等于 \(mid\)

而对于这个怎么维护呢?开一颗主席树,最初所有点权值都是1,进行离散化,然后按照权值进行插入,在版本 \(i\) 中将值为 \(i-1\) 的数全部单点修改为 -1.

时间复杂度为 \(O(n\log n+q\log^2 n)\)

细节很多,这里给出代码进行参考.

#include<bits/stdc++.h>
using namespace std;
#define N 500500
#define int long long
int id,rt[N<<1],lc[N<<5],rc[N<<5],lx[N<<5],s[N<<5],rx[N<<5],a[N],b[N],c[N],n,m,f;
void build(int &x,int l,int r){
	x=++id;lx[x]=rx[x]=r-l+1;
	s[x]=r-l+1;
	if(l==r)return ;int mid=l+r>>1;
	build(lc[x],l,mid);
	build(rc[x],mid+1,r);
}
void pushup(int x){
	lx[x]=max(lx[lc[x]],s[lc[x]]+lx[rc[x]]);
	rx[x]=max(rx[rc[x]],s[rc[x]]+rx[lc[x]]);
	s[x]=s[lc[x]]+s[rc[x]];
}
void updata(int &x,int p,int l,int r,int pos,int k){
	x=++id;lc[x]=lc[p],rc[x]=rc[p],s[x]=s[p],lx[x]=lx[p],rx[x]=rx[p];
	if(l==r){
		s[x]=lx[x]=rx[x]=k;
		return ;
	}
	int mid=l+r>>1;
	if(pos<=mid)updata(lc[x],lc[p],l,mid,pos,k);
	else updata(rc[x],rc[p],mid+1,r,pos,k);
	pushup(x); 
}
struct node{
	int lx,rx,s;
};
node merge(node a,node b){
	node c;c.s=a.s+b.s;
	c.lx=max(a.lx,a.s+b.lx);
	c.rx=max(b.rx,b.s+a.rx);return c;
}
node find(int x,int l,int r,int L,int R){
//	cout<<"find : "<<l<<" "<<r<<" "<<lx[x]<<" "<<s[x]<<" "<<rx[x]<<"\n"; 
	if(L<=l&&r<=R)return (node){lx[x],rx[x],s[x]};
	int mid=l+r>>1;
	if(L<=mid&&mid<R)return merge(find(lc[x],l,mid,L,R),find(rc[x],mid+1,r,L,R));
	if(L<=mid)return find(lc[x],l,mid,L,R);
	return find(rc[x],mid+1,r,L,R); 
}
vector<int>g[N];
void init(){
	cin>>f>>n;
	for(int i=1;i<=n;i++)cin>>a[i],b[i]=a[i];
	sort(b+1,b+n+1);m=unique(b+1,b+n+1)-b-1;
	for(int i=1;i<=n;i++)c[i]=lower_bound(b+1,b+m+1,a[i])-b;
//	for(int i=1;i<=n;i++)cout<<c[i]<<" ";cout<<"\n";
	for(int i=1;i<=n;i++)g[c[i]].push_back(i);
	build(rt[1],1,n);
	int pq=m;
	for(int i=2;i<=m;i++){
		++pq;rt[pq]=rt[i-1];
		for(auto x:g[i-1]){
			updata(rt[pq+1],rt[pq],1,n,x,-1);++pq;
		}
		rt[i]=rt[pq];
	}
}
int query(int a,int b,int c,int d){
//	cout<<a<<" "<<b<<" "<<c<<" "<<d<<"\n"; 
	++a,++b,++c,++d;
	int l=1,r=m;
	while(l<r){
		int mid=l+r+1>>1;
		node you=find(rt[mid],1,n,a,b-1),arenot=find(rt[mid],1,n,b,c),alone=find(rt[mid],1,n,c+1,d);
//		cout<<mid<<" "<<you.rx<<" "<<arenot.s<<" "<<alone.lx<<"\n";
		you.rx=max(0ll,you.rx);alone.lx=max(0ll,alone.lx);
		if(you.rx+arenot.s+alone.lx>=0){
			l=mid;
		}
		else r=mid-1;
	}
	return l;
}
int in[4];
signed main(){
//	freopen("data.in","r",stdin);
//	freopen("data.out","w",stdout);
	ios::sync_with_stdio(false);
	init();int lst=0;
	int q;cin>>q;
	while(q--){
		for(int i=0;i<4;i++)cin>>in[i],in[i]=(in[i]+lst*f)%n;
		sort(in,in+4);
		lst=b[query(in[0],in[1],in[2],in[3])];
		cout<<lst<<"\n";
	}
}
posted @ 2023-09-11 21:33  spdarkle  阅读(11)  评论(0编辑  收藏  举报