比赛:ZJCPC2026(重现赛)

时间:\(2026/7/20\)

赛时 \(6\) 题:

赛后 \(1\) 题:

以下按赛时过题顺序展开:

\(\text{I}\)

给定一矩阵,行内重排,最大化各列最小值之和。

显然各行按从小到大的顺序排序后是最优的,因为此时我们已经实现了对于任意 \(k\),各列最小值中第 \(k\) 大的最大化。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<set>
#include<map>
#include<queue>
#include<deque>
#include<vector>
#include<array>
#include<random>
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define mkp make_pair
#define pii pair<int,int>
#define cout cerr<<">> ",cout
using namespace std;
const int Mx=1005,p=998244353;
int read(){
	int Alice=0,Aya=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') Aya=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		Alice=(Alice<<3)+(Alice<<1)+(ch^48);
		ch=getchar();
	}
	return (Aya==1?Alice:-Alice);
}
int n,m;
int a[Mx][Mx];
void Solve(){
	n=read(),m=read();
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			a[i][j]=read();
		}
		sort(a[i]+1,a[i]+m+1);
	}
	int ans=0;
	for(int j=1;j<=m;j++){
		int res=1e18;
		for(int i=1;i<=n;i++) res=min(res,a[i][j]);
		ans+=res;
	}
	cout<<ans<<endl;
}
signed main(){
	int T=read();
	while(T--) Solve();
	return 0;
}
/*
Hello!!
Sample:

----------------
*/

\(\text{H}\)

给定若干个账号名,判断它是否形如 \(\texttt{ucup-teamXXXX}\)。其中 \(\texttt{XXXX}\) 必须是至少三位且无前导零的十进制正整数。

直接 Check 是否满足格式要求就完了。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<set>
#include<map>
#include<queue>
#include<deque>
#include<vector>
#include<array>
#include<random>
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define mkp make_pair
#define pii pair<int,int>
#define cout cerr<<">> ",cout
using namespace std;
const int Mx=1005,p=998244353;
int read(){
	int Alice=0,Aya=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') Aya=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		Alice=(Alice<<3)+(Alice<<1)+(ch^48);
		ch=getchar();
	}
	return (Aya==1?Alice:-Alice);
}
string s;
void Solve(){
	cin>>s;
	int n=s.length();
	if(n<=11){
		puts("NO");
		return;
	}
	string t=s.substr(0,9);
	if(t!="ucup-team"){
		puts("NO");
		return;
	}
	t=s.substr(9);
	//cout<<t<<endl;
	if(t[0]=='0'){
		puts("NO");
		return;
	}
	for(int i=0;i<(int)t.length();i++){
		if(t[i]-'0'>9||t[i]-'0'<0){
			puts("NO");
			return;
		}
	}
	puts("YES");
}
signed main(){
	int T=read();
	while(T--) Solve();
	return 0;
}
/*
Hello!!
Sample:
5
abcdeabcdeabc
ucup-team761x
ucup-team22xx
ucup-team01236
ucup-team1234567891234567x
----------------
*/

\(\text{G}\)

构造题,在一个长度为 \(n\) 的序列中每一个位置填入 \(X\)\(Y\),使得该序列不存在某一区间和为 \(p\)

首先 \(p\)\(X\)\(Y\) 的倍数就直接做完了,构造显然。

如果 \(n\) 足够小使得存在一种方案的所有项的和还到不了 \(p\),那也做完了,全填 \(X\)\(Y\) 的较小值即可。

然后就是 \(p\)\(X,Y\) 倍数的情况,然后大力构造:

直觉上,我们尝试让整个序列是循环的,且在循环节中值相同的都挨着,因为这样似乎区间和的种类比较少。

也就是说构造这种循环节:

\[XX\cdots XXYY\cdots YY \]

那整个序列就是这样:

\[XX\cdots XX\color{red}{YY\cdots YYXX\cdots XX}YY\cdots YY\color{black}XX\cdots XXYY\cdots YY\cdots \]

以下不妨设 \(X>Y\)

假设我们在设法构造为 \(p\) 的区间和时选中了红色的部分后再往两侧扩展,那我们可以尽可能地让这些东西的和很大使得一旦再往两边吞并 \(X\) 和就超过 \(p\) 了。

于是,对于循环节,我们构造 $\left \lfloor \dfrac{p}{X}-1 \right \rfloor $ 个 \(X\),$\left \lfloor \dfrac{X-1}{Y} \right \rfloor $ 个 \(Y\),去验证其是否能够达成目的。

造了 \(X,Y\le100\) 的数据发现有点问题,要先把 \(X,Y,p\) 都除以 \(X,Y\)\(\gcd\) 啊,然后又跑了一遍发现所有无解的情况几乎都与样例无解的情况一致,那不就对了嘛,敢猜敢写敢过。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<set>
#include<map>
#include<queue>
#include<deque>
#include<vector>
#include<array>
#include<random>
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define mkp make_pair
#define pii pair<int,int>
#define cout cerr<<">> ",cout
using namespace std;
const int Mx=1000005,p=998244353;
int read(){
	int Alice=0,Aya=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') Aya=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		Alice=(Alice<<3)+(Alice<<1)+(ch^48);
		ch=getchar();
	}
	return (Aya==1?Alice:-Alice);
}
int X,Y;
int n,k;
int w[Mx];
int pre[Mx];
void Solve(){
	X=read(),Y=read();
	n=read(),k=read();
	if(X>Y) swap(X,Y);
	if(k%X!=0||n*X<k){
		puts("YES");
		for(int i=1;i<=n;i++) printf("%lld ",X);
		puts("");
		return;
	}
	if(k%Y!=0){
		puts("YES");
		for(int i=1;i<=n;i++) printf("%lld ",Y);
		puts("");
		return;
	}
	int g=__gcd(X,Y);
	int X_=X/g,Y_=Y/g,k_=k/g;
	unordered_map<int,bool>vis;
	int A=k_/X_-1,B=(X_-1)/2;
	int D=k_/Y_-1,C=(Y_-1)/2;
//	cout<<A<<" "<<B<<endl;
//	cout<<C<<" "<<D<<endl;
	for(pii _:{mkp(A,B),mkp(C,D)}){
		int a=_.fi,b=_.se;
		for(int i=1;i<=a;i++) w[i]=X;
		for(int i=1+a;i<=a+b;i++) w[i]=Y;
		for(int i=a+b+1;i<=n;i++) w[i]=w[i-a-b];
		vis.clear();
		vis[0]=1;
		bool ok=1;
		for(int i=1;i<=n;i++){
			pre[i]=pre[i-1]+w[i];
			vis[pre[i]]=1;
			if(pre[i]>=k&&vis[pre[i]-k]){
				ok=0;
				break;
			}
		}
		if(ok){
			puts("YES");
			for(int i=1;i<=n;i++) printf("%lld ",w[i]);
			puts("");
			return;
		}
	}
	puts("NO");
	//if(X_<10&&Y_<10&&X_!=k_&&Y_!=k_&&!(X_==1&&Y_==2)) cout<<X_<<" "<<Y_<<" "<<n<<" "<<k_<<endl;
}
signed main(){
	//freopen("1.in","r",stdin);
	int T=read();
	while(T--) Solve();
	return 0;
}
/*
Hello!!
Sample:

----------------
*/

\(\text{J}\)

一个矩阵,支持插入行、列(插入的行、列的所有数均为每次给定的值),单点查询。

那行列拆开做不就完了嘛,看看这个位置最后一次是被行操作的还是被列操作的。

行或列拆开之后是单点插入,单点查询,写了块状链表,\(176\text{ms}\) 跑得飞快。

值得一提的是,这东西可以离线下来倒着处理,比如你最后一次插入时其在最终序列里的位置其实是容易确定的,那从后往前依次处理插入的元素在最终序列的位置就行了,然后回答询问用脚做,复杂度线性对数。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<set>
#include<map>
#include<queue>
#include<deque>
#include<vector>
#include<array>
#include<random>
#include<bits/stdc++.h>
#define fi first
#define se second
#define mkp make_pair
#define pii pair<int,int>
#define cout cerr<<">> ",cout
using namespace std;
const int Mx=500005,p=998244353;
int read(){
	int Alice=0,Aya=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') Aya=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		Alice=(Alice<<3)+(Alice<<1)+(ch^48);
		ch=getchar();
	}
	return (Aya==1?Alice:-Alice);
}
int q,C;
struct Opt{
	int opt,pos,v;
}op[Mx];
vector<Opt>_,l;
pii ans[Mx][2];
const int B=700;
int No_=0;
vector<pii>ov[Mx];
vector<int>ID;
void Insert(int id,pii v){
	int siz=0,O=-1,oid=0;
	for(int o:ID){
		oid++;
		if(siz+(int)ov[o].size()<id){
			siz+=ov[o].size();
			continue;
		}
		int pos=id-siz;
		ov[o].insert(ov[o].begin()+pos,v);
		O=o;
		break;
	}
	if(O==-1){
		++No_;
		ID.push_back(No_);
		ov[No_].push_back(v);
		return;
	}
	if(ov[O].size()>=B+B){
		++No_;
		ID.insert(ID.begin()+oid,No_);
		ov[No_].resize(B);
		int tot=B;
		for(int i=1;i<=B;i++){
			ov[No_][--tot]=ov[O].back();
			ov[O].pop_back();
		}
	}
}
pii Qry(int id){
	int siz=0;
	for(int o:ID){
		if(siz+(int)ov[o].size()<id){
			siz+=ov[o].size();
			continue;
		}
		int pos=id-siz;
		return ov[o][pos-1];
	}
	exit(114);
}
signed main(){
//	freopen("1.in","r",stdin);
//	freopen("1.out","w",stdout);
	q=read(),C=read();
	_.push_back({0,C,0});
	l.push_back({0,C,0});
	for(int I=1,opt,pos,v;I<=q;I++){
		opt=read(),pos=read(),v=read();
		op[I]={opt,pos,v};
	}
	Insert(0,mkp(C,0));
	
	for(int I=1,opt,pos,v;I<=q;I++){
		opt=op[I].opt,pos=op[I].pos,v=op[I].v;
		if(opt==1){
			Insert(pos,mkp(v,I));
		}
		if(opt==3){
			ans[I][0]=Qry(pos);
			//cout<<ans[I][0].fi<<" "<<ans[I][0].se<<endl;
		}
	}
	ID.clear();
	for(int i=1;i<=No_;i++) ov[i].clear();
	No_=0;
	Insert(0,mkp(C,0));
	for(int I=1,opt,pos,v;I<=q;I++){
		opt=op[I].opt,pos=op[I].pos,v=op[I].v;
		if(opt==2){
			Insert(pos,mkp(v,I));
		}
		if(opt==3){
			ans[I][1]=Qry(v);
		}
	}
	Insert(0,mkp(C,0));
	for(int I=1,opt;I<=q;I++){
		opt=op[I].opt;
		if(opt==3){
			if(ans[I][0].se<ans[I][1].se) printf("%d\n",ans[I][1].fi);
			else printf("%d\n",ans[I][0].fi);
		}
	}
	return 0;
}
/*
Hello!!
Sample:

----------------
*/

\(\text{F}\)

给定序列 \(a_i\) 和修改代价 \(c_i\),修改一个位置后可把它改成任意值。求最小代价使得每个前缀和都不是 \(p\) 的倍数。

我们首先发现如果有一个后缀的前缀和如果包含 \(0\sim p-1\) 的所有值那肯定就寄了,必须改点东西才行。而且改了东西之后,从改的这个位置到结尾就都自由了(也就是说,一定合法了),因为我们可以认为改这个位置的时候,其实是在约定到这个位置的前缀和,而因为这个位置我们可以随便改,那改到这个前缀和一定是可以实现的。

于是这启发我们设计 DP:

\(f_i\) 为在改 \(a_i\) 的基础上,使后缀 \([i,n]\) 合法的最小代价,那转移就是枚举合法的 \(j\) 使得 \(u\in [i,j]\) 对应的前缀和 \(pre_u\)\(p\) 的值不能覆盖 \(0\sim p-1\),那就有转移:

\[f_i\leftarrow f_{j+1}+c_i \]

显然合法的 \(j\) 连续,于是我们只需要找到 \(j\) 的上界即可。这个可以预处理。

然后就是区间最小值,使用你喜欢的数据结构维护即可。

一开始忘了判整个序列可以不通过修改直接合法了,因为我们设计状态时默认有一个位置要被修改了。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<set>
#include<map>
#include<queue>
#include<deque>
#include<vector>
#include<array>
#include<random>
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define mkp make_pair
#define pii pair<int,int>
using namespace std;
const int Mx=500005;
int read(){
	int Alice=0,Aya=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') Aya=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		Alice=(Alice<<3)+(Alice<<1)+(ch^48);
		ch=getchar();
	}
	return (Aya==1?Alice:-Alice);
}
int n,p;
int a[Mx];
int c[Mx];
int nxt[Mx];
int f[Mx];
int r[Mx];
void Solve(){
	n=read(),p=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=n;i++) c[i]=read();
	for(int i=1;i<=n;i++) (a[i]+=a[i-1])%=p;
	unordered_map<int,int>lst;
	set<int>s;
	for(int i=n;i>=1;i--){
		if(lst[a[i]]) s.erase(lst[a[i]]);
		lst[a[i]]=i,s.insert(lst[a[i]]);
		if((int)s.size()<p) r[i]=n;
		else{
			r[i]=(*prev(s.end()))-1;
		}
	}
//	for(int i=1;i<=n;i++){
//		cout<<i<<" "<<r[i]<<endl;
//	}
	f[n+1]=0;
	int R=n;
	multiset<int>t;
	t.insert(0);
	for(int i=n;i>=1;i--){
		int res=1e18;
		while(R>r[i]){
			t.erase(t.find(f[R+1]));
			R--;
		}
//		cout<<t.size()<<endl;
		res=*t.begin();
//		for(int j=i;j<=r[i];j++){
//			res=min(res,f[j+1]);
//		}
		f[i]=res+c[i];
		t.insert(f[i]);
		//cout<<i<<" : "<<f[i]<<endl;
	}
	int ans=1e18;
	for(int i=1;i<=n+1;i++){
		ans=min(ans,f[i]);
		if(a[i]==0) break;
	}
	cout<<ans<<endl;
}
signed main(){
	int T=read();
	while(T--) Solve();
	return 0;
}
/*
Hello!!
Sample:

----------------
*/

\(\text{L}\)

三维空间中有若干方块,角色每次先按给定向量传送,再在竖直方向自由落体到最高的可站立位置,求从起点到终点的最少传送次数。

这是暴力建图题,按照题意熟练使用 lower_boundupper_bound 把所有方块之间的走法处理出来(这一部分稍有细节,比如可能掉到目标方块上面的方块上或者够不到目标方块之类的),边权为 \(1\),跑 BFS 最短路即可。先实现一个支持三维坐标运算的结构体会方便一些。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<set>
#include<map>
#include<queue>
#include<deque>
#include<vector>
#include<array>
#include<random>
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define mkp make_pair
#define pii pair<int,int>
#define cout cerr<<">> ",cout
using namespace std;
const int Mx=500005,p=998244353,B=1e9+1;
int read(){
	int Alice=0,Aya=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') Aya=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		Alice=(Alice<<3)+(Alice<<1)+(ch^48);
		ch=getchar();
	}
	return (Aya==1?Alice:-Alice);
}
struct Coor{
	int x,y,z;
	Coor operator +(const Coor&o)const{
		int X=x+o.x,Y=y+o.y,Z=z+o.z;
		return Coor{X,Y,Z};
	}
	Coor operator -(const Coor&o)const{
		int X=x-o.x,Y=y-o.y,Z=z-o.z;
		return Coor{X,Y,Z};
	}
	bool operator ==(const Coor&o)const{
		return (x==o.x&&y==o.y&&z==o.z);
	}
	bool operator <(const Coor&o)const{
		return (x==o.x?y==o.y?z<o.z:y<o.y:x<o.x);
	}
}cS,cT,way[Mx],obs[Mx];
int n,m;
Coor down={0,0,-1};
int S,T;
int dis[Mx];
vector<int>e[Mx];
void Solve(){
	n=read(),m=read();
	for(int i=1;i<=n;i++) e[i].clear(),dis[i]=1e18;
	cS.x=read(),cS.y=read(),cS.z=read();
	cT.x=read(),cT.y=read(),cT.z=read();
	unordered_map<int,set<int> >obsh;
	for(int i=1;i<=n;i++){
		obs[i].x=read(),obs[i].y=read(),obs[i].z=read();
		obsh[obs[i].x*B+obs[i].y].insert(obs[i].z);
		if(cS+down==obs[i]) S=i;
		if(cT+down==obs[i]) T=i;
	}
	//cout<<S<<" "<<T<<endl;
	unordered_map<int,set<int> >mxh;
	unordered_map<int,bool>vis;
	for(int i=1;i<=m;i++){
		way[i].x=read(),way[i].y=read(),way[i].z=read();
		mxh[way[i].x*B+way[i].y].insert(way[i].z);
		vis[way[i].x*B+way[i].y]=1;
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			//i -> j
			Coor del=obs[j]-obs[i];
			if(!vis[del.x*B+del.y]) continue;
			auto it=mxh[del.x*B+del.y].lower_bound(del.z);
			if(it==mxh[del.x*B+del.y].end()) continue;
			int h=*it;
			if(h<del.z) continue;
			Coor now={obs[j].x,obs[j].y,obs[i].z+h};
			//cout<<i<<' '<<j<<" : "<<now.x<<" "<<now.y<<" "<<now.z<<endl;
			it=obsh[obs[j].x*B+obs[j].y].upper_bound(obs[j].z);
			if(it!=obsh[obs[j].x*B+obs[j].y].end()&&(*it)<=now.z+1){
				continue;
			}
			//cout<<i<<" -> "<<j<<endl;
			e[i].push_back(j);
		}
	}
	dis[S]=0;
	queue<int>q;
	q.push(S);
	while(!q.empty()){
		int u=q.front();
		//cout<<u<<" : "<<dis[u]<<endl;
		q.pop();
		for(int v:e[u]){
			if(dis[v]>dis[u]+1){
				dis[v]=dis[u]+1;
				q.push(v);
			}
		}
	}
	if(dis[T]>=1e18) dis[T]=-1;
	cout<<dis[T]<<"\n";
}
signed main(){
	int Tc=read();
	while(Tc--) Solve();
	return 0;
}
/*
Hello!!
Sample:
2
6 4
3 3 3 5 5 5
3 3 2
5 5 4
5 5 7
4 5 5
2 3 2
4 1 1
-1 0 3
2 -2 -1
1 4 9
1 0 0
3 2
0 0 1 1 1 1
0 0 0
1 1 0
1 1 2
1 1 1
1 1 2
----------------
*/

\(\text{M}\)

给定一个排列,对于每个前缀,求出最多能在末尾追加多少个实数,使得新序列元素两两不同,且满足:

\[\text{LIS}(B) = \text{LIS}(A)\\ \text{LDS}(B) = \text{LDS}(A) \]

和题解思路有点区别。

首先按照树状数组求 LIS 的思路把以 \(a_i\) 结尾的 LIS 和 LDS 长度求出来,分别设为 \(f_{a_i},g_{a_i}\),然后后面接上的那一段序列就有如下约束,形如:

  • 开头的数值 \(\ge i\) 的 LIS 长度不超过 \(\text{LIS}(A)-f_i\)
  • 开头的数值 \(\le i\) 的 LDS 长度不超过 \(\text{LDS}(A)-g_i\)

那我们的第一想法是分别枚举上下两个约束,然后把对应的长度相乘。因为 LIS 长度与 LDS 的长度之积是序列长度的上界。但是我们发现不对,因为这样可能会有其它约束没有考虑。但是我们可以经过大力猜测和构造得到答案。这是暴力计算答案的代码:

int Calc(){
	pre[0]=0;
	for(int i=1;i<=n;i++){
		pre[i]=max(pre[i-1],f[i]);
	}
	suf[n+1]=0;
	for(int i=n;i>=1;i--){
		suf[i]=max(suf[i+1],g[i]);
	}
	int res=0;
	for(int i=1;i<=n;i++){
		res+=(pre[n]-pre[i])*(suf[i]-suf[i+1]);
	}
	return res;
}

然后我们只需要去优化这个东西就可以了!容易发现 \(suf_i-suf_{i+1}\)\(0\)\(1\),那就可以用脚做了(并非)。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<set>
#include<map>
#include<queue>
#include<deque>
#include<vector>
#include<array>
#include<random>
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define mkp make_pair
#define pii pair<int,int>
using namespace std;
const int Mx=500005,p=998244353;
int read(){
	int Alice=0,Aya=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') Aya=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		Alice=(Alice<<3)+(Alice<<1)+(ch^48);
		ch=getchar();
	}
	return (Aya==1?Alice:-Alice);
}
int n;
int a[Mx];
int f[Mx],g[Mx];
struct BIT{
	int t[Mx];
	void Init(){
		for(int i=0;i<=n;i++) t[i]=0;
	}
	void ChkMax(int i,int k){
		for(;i<=n;i+=i&-i) t[i]=max(t[i],k);
	}
	int Qry(int i){
		int res=0;
		for(;i;i&=(i-1)) res=max(res,t[i]);
		return res;
	}
}I,D;
struct BITS{
	int t[Mx];
	void Init(){
		for(int i=0;i<=n;i++) t[i]=0;
	}
	void Add(int i,int k){
		if(i==0) return;
		for(;i<=n;i+=i&-i) t[i]+=k;
	}
	int Qry(int i){
		int res=0;
		for(;i;i&=(i-1)) res+=t[i];
		return res;
	}
}C;
int pre[Mx],suf[Mx];
set<int>s;
int Calc(){
	pre[0]=0;
	for(int i=1;i<=n;i++){
		pre[i]=max(pre[i-1],f[i]);
	}
	int res=0;
	for(int i:s){
		res+=pre[i];
	}
	return res;
}
int mxv[Mx],mnv[Mx];
void Solve(){
	n=read();
	I.Init(),D.Init(),C.Init();
	for(int i=1;i<=n;i++) f[i]=0,g[i]=0,mxv[i]=0,mnv[i]=n+1;
	for(int i=1;i<=n;i++) a[i]=read();
	int LIS=0,LDS=0;
	s.clear();
	for(int i=1;i<=n;i++) s.insert(0);
	int res=0;
	for(int i=1;i<=n;i++){
		f[a[i]]=I.Qry(a[i])+1;
		g[a[i]]=D.Qry(n-a[i]+1)+1;
		int v=mxv[g[a[i]]];
		int u=mnv[f[a[i]]];
		if(u>a[i]){
			res+=C.Qry(u-1);
			u=mnv[f[a[i]]]=a[i];
			res-=C.Qry(u-1);
		}
		if(v<a[i]){
			res-=upper_bound(mnv+1,mnv+n+1,v)-mnv-1;
			s.erase(v);
			C.Add(v,-1);
			v=mxv[g[a[i]]]=a[i];
			res+=upper_bound(mnv+1,mnv+n+1,v)-mnv-1;
			s.insert(v);
			C.Add(v,+1);
		}
		LIS=max(LIS,f[a[i]]);
		LDS=max(LDS,g[a[i]]);
		I.ChkMax(a[i],f[a[i]]);
		D.ChkMax(n-a[i]+1,g[a[i]]);
		//Calc();
		printf("%lld ",LIS*LDS-res);
	}
	puts("");
//	for(int i=1;i<=n;i++){
//		cout<<f[i]<<" "<<g[i]<<endl;
//	}
}
signed main(){
	int T=read();
	while(T--) Solve();
	return 0;
}
/*
Hello!!
Sample:

----------------
*/

\(\text{Summary}\)

总体符合预期,主要是策略上偏保守了,不喜欢吃罚时.jpg

而且那个构造题没有大胆猜测构造的形式,虽然思路是对的但是彷徨了很久,\(\text{L}\) 题写得太慢了,总感觉有一车细节没考虑到,实际上似乎并没有那么多。

\[k=A(A-1)/2+B \]

posted on 2026-07-20 22:32  Lavaloon  阅读(33)  评论(0)    收藏  举报