NOIP2021 sol

发布时间 2023-12-21 19:42:59作者: H_W_Y

20231201-20231221

NOIP2021 sol

A. [NOIP2021] 报数

[NOIP2021] 报数

\(p(x)\) 表示 \(x\) 的十进制表示中是否含有数字 \(7\),若含有则 \(p(x) = 1\),否则 \(p(x) = 0\)。则一个正整数 \(x\) 不能被报出,当且仅当存在正整数 \(y\)\(z\) ,使得 \(x = yz\)\(p(y) = 1\)

现在小 r 的上一个数报出了 \(x\),小 z 想快速算出他下一个数要报多少,不过他很快就发现这个游戏可比原版的游戏难算多了,于是他需要你的帮助。当然,如果小 r 报出的 x 本身是不能报出的,你也要快速反应过来小 r 输了才行。

由于小 r 和小 z 玩了很长时间游戏,你也需要回答小 z 的很多个问题。

\(1 \le T \leq 2 \times {10}^5\)\(1 \le x \leq {10}^7\)

看完题愣了一下,一直一维直接筛过不了。

但事实证明跑得飞快,这种情况一定要去试一下,

于是直接类似于埃氏筛法去筛就可以了。

#include <bits/stdc++.h>
using namespace std;

const int N=1e7+5;
int n,cnt=0,a[N];
bool vis[N];

bool chk(int x){
  if(vis[x]) return true;
  int nw=0;
  while(x){
  	nw=x%10,x/=10;
  	if(nw==7) return true;
  }
  return false;
}

void init(){
  for(int i=1;i<N;i++){
    if(chk(i)) vis[i]|=1;
    if(!vis[i]) a[++cnt]=i;
    else for(int j=2;i*j<N;j++) vis[i*j]=true;  	
  }
}

int main(){
  /*2023.12.20 H_W_Y P7960 [NOIP2021] 报数 模拟*/
  ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
  init();
  int _;cin>>_;
  while(_--){
  	int x;cin>>x;
  	if(vis[x]) cout<<"-1\n";
  	else cout<<a[(upper_bound(a+1,a+cnt+1,x)-a)]<<'\n';
  }
  return 0;
}

B. [NOIP2021] 数列

[NOIP2021] 数列

给定整数 \(n, m, k\),和一个长度为 \(m + 1\) 的正整数数组 \(v_0, v_1, \ldots, v_m\)

对于一个长度为 \(n\),下标从 \(1\) 开始且每个元素均不超过 \(m\) 的非负整数序列 \(\{a_i\}\),我们定义它的权值为 \(v_{a_1} \times v_{a_2} \times \cdots \times v_{a_n}\)

当这样的序列 \(\{a_i\}\) 满足整数 \(S = 2^{a_1} + 2^{a_2} + \cdots + 2^{a_n}\) 的二进制表示中 \(1\) 的个数不超过 \(k\) 时,我们认为 \(\{a_i\}\) 是一个合法序列。

计算所有合法序列 \(\{a_i\}\) 的权值和对 \(998244353\) 取模的结果。

\(1 \leq k \leq n \leq 30\)\(0 \leq m \leq 100\)\(1 \leq v_i < 998244353\)

首先容易想到我们按照 \(a\) 去排个序一定不劣,最后乘上组合数就可以了。

但是可能因为前几天做过一道类似的题目,它用的算组合数的方法在这道题上面不能用,我卡了挺久。。。


而对于这种二进制进位问题,很容易想到常见的套路,维护一下进位数和低位 \(1\) 的个数,

于是我们就可以很容易得到一个 dp,设 \(f[u][i][j][k]\) 表示当前枚举了到了 \(v_u\),序列已经有了 \(i\) 个元素,前 \(i\) 位的进位为 \(j\)\(1\) 的个数为 \(k\)

这样只要我们枚举一下当前的 \(v_u\) 选多少个就可以了,设选了 \(x\) 个,

那么

\[f[u][i][j][k]\times \binom{n-i}{x} \times {v_u}^x \to f[u+1][i+x][\lfloor \frac{j+x}{2} \rfloor][k+(j+x)\%2] \]


于是就做完了,算答案就枚举一下合法的 \(j,k\) 就可以了,时间复杂度 \(\mathcal O(n^4m)\)

#include <bits/stdc++.h>
using namespace std;
#define ll long long

const ll mod=998244353;
const int N=35,M=105;
int n,m,K;
ll c[N][N],a[M][N],f[M][N][N][N],ans=0;

void add(ll &x,ll y){x=(x+y)%mod;}
int chk(int x){
  int res=0;
  while(x) res+=x%2,x/=2;
  return res;
}

int main(){
  /*2023.12.20 H_W_Y P7961 [NOIP2021] 数列 dp*/
  ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
  cin>>n>>m>>K;
  for(int i=0;i<=m;i++){
  	cin>>a[i][1];a[i][0]=1ll;
  	for(int j=2;j<=n;j++) a[i][j]=a[i][j-1]*a[i][1]%mod;
  }
  for(int i=0;i<=n;i++){
  	c[i][0]=1ll;
  	for(int j=1;j<=i;j++) c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
  }
  f[0][0][0][0]=1ll;
  for(int u=0;u<=m;u++)
  	for(int i=0;i<=n;i++)
  	  for(int j=0;j<=i;j++)
  	    for(int k=0;k<=n;k++)
  	      for(int x=0;i+x<=n;x++)
  	        add(f[u+1][i+x][(j+x)/2][k+(j+x)%2],f[u][i][j][k]*c[n-i][x]%mod*a[u][x]%mod);
  for(int j=0;j<=n;j++)
    for(int k=0;k<=n;k++)
      if(chk(j)+k<=K) add(ans,f[m+1][n][j][k]);
  cout<<ans<<'\n';
  return 0;
}

C. [NOIP2021] 方差

C. [NOIP2021] 方差

给定长度为 \(n\) 的非严格递增正整数数列 \(1 \le a_1 \le a_2 \le \cdots \le a_n\)。每次可以进行的操作是:任意选择一个正整数 \(1 < i < n\),将 \(a_i\) 变为 \(a_{i - 1} + a_{i + 1} - a_i\)。求在若干次操作之后,该数列的方差最小值是多少。请输出最小值乘以 \(n^2\) 的结果。

其中方差的定义为:数列中每个数与平均值的差的平方的平均值。更形式化地说,方差的定义为 \(D = \frac{1}{n} \sum_{i = 1}^{n} {(a_i - \bar a)}^2\),其中 \(\bar a = \frac{1}{n} \sum_{i = 1}^{n} a_i\)

\(1 \le n \le {10}^4\)\(1 \le a_i \le 600\)

首先答案可以转化成

\[n \sum_{i=1}^n {a_i}^2 - (\sum_{i=1}^n a_i)^2 \]

然后在考虑模拟一下每一次的操作,

发现这其实就是差分数组的交换位置,那么问题就变成了如何排列这个差分数组使得答案最小。(由于求的是方差,首先 \(a_1\) 是多少不重要,所以不用管它,就当作是 \(0\),因为我们需要的只是差分数组)


通过打表可以发现(或者推式子),取得最小值时的 \(d\) 数组的长相一定是一个单谷的。

感觉还是比较好理解,感性理解也很显然。


那么问题现在就变成了我们如何确定一个数实在这个谷的左边还是右边?

于是考虑 dp,这种分成了两部分求最小值的东西我们可以考虑把它们其中一个变成状态,另外一个求最小/大值。

这样就是设 \(f[i][x]\) 表示考虑前 \(i\) 小的 \(d\)\(\sum_{i=1}^n a_i =x\) 时,\(\sum_{i=1}^n {a_i}^2\) 的最小值。


发现这样的 dp 是极其好转移的,我们就只需要讨论是放在左边还是右边:

  • 放左边,\(f[i][x]+i\times {d_i}^2 + 2xd_i \to f[i+1][x+i\times d_i]\),因为 \(d_i\) 对后面谷中的 \(i\) 个都有贡献。
  • 放右边,\(f[i][x]+{s_i}^2 \to f[i+1][x+s_i]\),其中 \(s_i = \sum_{j=1}^i d_i\)

于是直接做就可以了,还可以用滚动数组优化一下,从而做到空间 \(\mathcal O(nV)\)


这样的复杂度是 \(\mathcal O(n^2V)\) 的,极限数据还是过不了。

但是当 \(n\) 很大的时候 \(a_i\) 很小,这就意味着 \(d_i\) 有很多个都是 \(0\)

于是我们对于 \(0\) 就直接 continue 掉就可以了,这样时间复杂度就变成了 \(\mathcal O(nV)\) 的了。

#include <bits/stdc++.h>
using namespace std;
#define ll long long 

const ll inf=1e18;
const int N=1e4+5,M=5e5+5;
int a[N],d[N],s[N],n,mx;
ll f[M],ans;

void chkmn(ll &x,ll y){if(y<x) x=y;}

int main(){
  /*2023.12.21 H_W_Y P7962 [NOIP2021] 方差 dp*/
  ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
  cin>>n>>a[1];mx=a[1];
  for(int i=2;i<=n;i++) cin>>a[i],d[i-1]=a[i]-a[i-1],mx=max(mx,a[i]);
  for(int i=0;i<=n*mx;i++) f[i]=inf;
  f[0]=s[0]=mx=0;
  sort(d+1,d+n);
  for(int i=1;i<=n;i++){
  	s[i]=s[i-1]+d[i];
  	if(d[i]==0) continue;
  	for(int x=mx;x>=0;x--){
  	  if(f[x]==inf) continue;
  	  chkmn(f[x+i*d[i]],f[x]+1ll*i*d[i]*d[i]+2ll*x*d[i]);
  	  chkmn(f[x+s[i]],f[x]+1ll*s[i]*s[i]);
  	  mx=max(mx,max(x+i*d[i],x+s[i]));
  	  f[x]=inf;
  	}
  }
  ans=inf;
  for(int x=0;x<=mx;x++) if(f[x]<inf) chkmn(ans,1ll*n*f[x]-1ll*x*x);
  cout<<ans<<'\n';
  return 0;
}

D. [NOIP2021] 棋局

D. [NOIP2021] 棋局

前三题的代码为什么那么短?因为第四题代码长啊。(CCF 还怪会安排的呢

给定 \(n \times m\) 的棋盘,棋盘上有 \(4\) 种类型的边,分别代表不可通行、只能走一步、只能一直沿一个方向往前走和可以任意走。棋子有两种颜色和等级,棋子间可以吃子,规定只能吃颜色不同且等级不高于自己的棋子,且吃完子后不能继续向前走。同时规定每次走子时经过的边类型必须相同。初始棋盘是空的,有 \(q\) 次操作,每次往棋盘上放一个棋子,问这个棋子能走到多少个格子。

\(1 \le T \le 5\)\(2 \le n, m \le {10}^5\)\(4 \le n \times m \le 2 \times {10}^5\)\(1 \le q \le \min \{ {10}^5, n \times m \}\)\(1 \le \mathit{lv}_i \le q\)\(1 \le x_i \le n\)\(1 \le y_i \le m\)\(\mathit{col}_i \in \{ 0, 1 \}\)

题目读起来就挺复杂的,但属实有点意思。


首先直接写一个 bfs 会得到 \(32\) 分的高分,你再乱搞一下就有了 \(56\) 分(这里只线段树合并)。

我只写了 \(32\) 分的,中间还 RE 了好久。(if 的顺序写错是这样的)

#include <bits/stdc++.h>
using namespace std;
#define pii pair<int,int> 
#define fi first
#define se second

int read(){
  int x=0,f=1;char ch=getchar();
  while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
  while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}

void prt(int x){
  int p[15],tmp=0;
  if(x==0) putchar('0');
  if(x<0) putchar('-'),x=-x;
  while(x) p[++tmp]=x%10,x/=10;
  for(int i=tmp;i>=1;i--) putchar(p[i]+'0');
  putchar('\n');
}

const int N=5e5+5,fx[4]={-1,0,1,0},fy[4]={0,1,0,-1};
int n,m,q,d[N][4],ans=0;
struct node{int col,lv;}v[N],nw;
bool vis[N],b[N];

int id(int i,int j){return (i-1)*m+j;}
bool chk(node i,node j){
  if(j.col==-1) return true;
  return i.col!=j.col&&i.lv>=j.lv;
}
int nxt(int x,int y,int i){
  if(i==0) return id(x-1,y);
  if(i==1) return id(x,y+1);
  if(i==2) return id(x+1,y);
  return id(x,y-1);
}

void init(){
  memset(d,0,sizeof(d));
  for(int i=1;i<=n*m;i++) v[i]={-1,0};
}
queue<pii> Q;

void bfs(int x,int y){
  Q.push({x,y});b[id(x,y)]=true;
  while(!Q.empty()){
    x=Q.front().fi,y=Q.front().se;Q.pop();
    int num=id(x,y);
    if(!vis[num]) vis[num]=true,++ans;
    if(v[num].col!=-1) continue;
    for(int i=0;i<4;i++){
      if(d[num][i]!=3||b[nxt(x,y,i)]||!chk(nw,v[nxt(x,y,i)])) continue;
      Q.push({x+fx[i],y+fy[i]});b[nxt(x,y,i)]=true;
    }
  }
}

int main(){
  int _=read();
  while(_--){
  	n=read();m=read();q=read();
    init();
    for(int i=1;i<=n;i++) 
      for(int j=1;j<m;j++){
      	char ch=getchar();
      	while(!isdigit(ch)) ch=getchar();
      	d[id(i,j)][1]=d[id(i,j+1)][3]=(int)(ch-'0');
      }
    for(int i=1;i<n;i++)
      for(int j=1;j<=m;j++){
      	char ch=getchar();
      	while(!isdigit(ch)) ch=getchar();
      	d[id(i,j)][2]=d[id(i+1,j)][0]=(int)(ch-'0');
      }
    while(q--){
      nw.col=read(),nw.lv=read();
      int x=read(),y=read();ans=0;
      if(n*m<=5000){for(int i=0;i<=n*m;i++) vis[i]=b[i]=false;}
      int num=id(x,y);vis[num]=true;
      for(int i=0;i<4;i++){
      	if(!d[num][i]||!chk(nw,v[nxt(x,y,i)])) continue;
      	if(d[num][i]==1) ++ans,vis[nxt(x,y,i)]=true;
      	if(d[num][i]==2){
      	  int xx=x,yy=y;
		  while(d[id(xx,yy)][i]==2&&chk(nw,v[nxt(xx,yy,i)])){
		  	xx+=fx[i];yy+=fy[i];
		  	vis[id(xx,yy)]=true;++ans;
		  	if(v[id(xx,yy)].col!=-1) break;
		  }	
		}
      }
      bfs(x,y);v[num]=nw;
      prt(ans);
    }
  }
  return 0;
}

现在再来考虑正解。

发现 \(3\) 道路可以到达的点很类似于一个并查集维护的连动块,

但是依次加点取分裂它很明显不可做。


这个时候就用到了一个挺套路的东西,离线下来倒着删点。

于是这样其实就是只用维护合并了,就变得没有什么思维难度了。


具体来说,每一个块往外走到有棋子的位置的权值是肯定要维护的,

容易发现合并的时候有可能有重复,所以我们需要先提前处理一下,即让每一个棋子的 \(lv\) 都不同。

我们用两棵线段树分别维护每一个连动块为 \(0/1\) 颜色的棋子。

这样直接做其实就是上面所说的 \(56\) 分中所会用到的线段树合并。


现在问题就在于 \(2\) 道路上的很有可能和 \(3\) 的重合,这样怎么办呢?

容易发现,我们这些点都是连续的,用两个并查集分别维护 \(2\) 的横/竖两个方向到达的 \(\min,\max\)

于是我们只需要减去 \(3\) 已经到达的点中在这个区间里面的个数。

做法就呼之欲出,我们维护两棵线段树分别记录一个连动块内部的 \((x-1)\times m+y\)\((y-1) \times n +x\)

那么上面就变成了一个线段树上面的区间查询。


而对于 \(1\) 的道路,我们直接暴力枚举一下就可以了,

这样一共会用到 \(4\) 棵线段树(每个连动块 \(0/1\) 两种颜色的 \(lv\),每个连动块内部点 \((x-1)\times m +y\)\((y-1)\times n+x\))和 \(3\) 个并查集就可以完成,

细节不多,就是有点长,写的时候一定要专注,以免犯下那种小错误一直调不出来。

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define pii pair<int,int>
#define fi first
#define se second

int read(){
  int x=0,f=1;char ch=getchar();
  while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
  while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}

void prt(int x){
  int P[15],tmp=0;
  if(x==0) putchar('0');
  if(x<0) putchar('-'),x=-x;
  while(x) P[++tmp]=x%10,x/=10;
  for(int i=tmp;i>=1;i--) putchar(P[i]+'0');
  putchar('\n');
}

const int N=2e5+5,fx[4]={-1,0,1,0},fy[4]={0,1,0,-1};
int n,m,q,d[N][4],val[N],ans[N];
struct node{int col,lv,x,y,id;}a[N];
bool cmp1(node x,node y){return (x.lv==y.lv)?x.id<y.id:x.lv<y.lv;}
bool cmp2(node x,node y){return x.id<y.id;}

int id(int x,int y){return (x-1)*m+y;}
int id2(int x,int y){return (y-1)*n+x;}
pii rev(int x){return {(x-1)/m+1,(x-1)%m+1};}

struct SGT{
  struct sgt{int lc,rc,v;}tr[N*40];
  int rt[N],idx=0;
  
  #define lc(p) tr[p].lc
  #define rc(p) tr[p].rc
  #define mid ((l+r)>>1)
  
  void init(){
    memset(rt,0,sizeof(rt));idx=0;
    for(int i=0;i<N*40;i++) tr[i].lc=tr[i].rc=tr[i].v=0;
  }
  
  void pu(int p){tr[p].v=tr[lc(p)].v+tr[rc(p)].v;}
  
  int upd(int l,int r,int p,int x,int fl=1){
  	if(!p) p=++idx;
  	if(l==r){
  	  if(fl==-1) return 0;
  	  if(!tr[p].v) ++tr[p].v;
  	  return p;
  	}
  	if(x<=mid) tr[p].lc=upd(l,mid,lc(p),x,fl);
  	else tr[p].rc=upd(mid+1,r,rc(p),x,fl);
  	pu(p);return (!tr[p].v)?0:p;
  }
  
  int merge(int p1,int p2,int l,int r){
  	if(!p1||!p2) return p1+p2;
  	if(l==r) return tr[p1].v=min(tr[p1].v+tr[p2].v,1),p1;
    tr[p1].lc=merge(lc(p1),lc(p2),l,mid);
    tr[p1].rc=merge(rc(p1),rc(p2),mid+1,r);
    pu(p1);return p1;
  }
  
  int qle(int l,int r,int p,int x){//query_le 
  	if(!p||!x) return 0;
    if(l==r) return tr[p].v;
    return (x<=mid)?qle(l,mid,lc(p),x):(tr[lc(p)].v+qle(mid+1,r,rc(p),x));
  }
  
  int qin(int l,int r,int p,int x){//query_in?
  	if(!p||!x) return 0;
  	if(l==r) return tr[p].v;
  	return (x<=mid)?qin(l,mid,lc(p),x):qin(mid+1,r,rc(p),x);
  }
  
  int qlr(int p,int l,int r){return qle(1,n*m,rt[p],r)-qle(1,n*m,rt[p],l-1);}
}T[4];
//T[0/1] 有关颜色,T[2] 是列,T[3] 是行

void mg(int u,int v){
  for(int i=0;i<2;i++) T[i].rt[u]=T[i].merge(T[i].rt[u],T[i].rt[v],1,q);
  for(int i=2;i<4;i++) T[i].rt[u]=T[i].merge(T[i].rt[u],T[i].rt[v],1,n*m);
}

struct DSU{
  int fa[N],sz[N],mx[N],mn[N];
  
  int find(int x){return (fa[x]==x)?x:fa[x]=find(fa[x]);}
  
  void merge(int u,int v,int fl=0){
  	u=find(u);v=find(v);
  	if(u==v) return;
  	if(sz[u]<=sz[v]) swap(u,v);
  	fa[v]=u;sz[u]+=sz[v];
  	mx[u]=max(mx[u],mx[v]);
  	mn[u]=min(mn[u],mn[v]);
  	if(fl) mg(u,v);
  }
  
  void init(int lim){for(int i=1;i<=lim;i++) fa[i]=mx[i]=mn[i]=i,sz[i]=1;}
  
  int gmx(int x){return mx[find(x)];}
  int gmn(int x){return mn[find(x)];}
  int gsz(int x){return sz[find(x)];}
}t[3];
//t[0] 是 3 的,t[1] 是列,t[2] 是行

bool chk(int x,int y){return y&&(x>y)&&a[x].col!=a[y].col&&a[x].lv>=a[y].lv;}

void init(){
  for(int i=0;i<4;++i) T[i].init();
  for(int i=0;i<3;++i) t[i].init(n*m);
  memset(d,0,sizeof(d));
  memset(val,0,sizeof(val));
  memset(ans,0,sizeof(ans));
}

int main(){
  /*2023.12.21 H_W_Y P7963 [NOIP2021] 棋局 线段树合并+ds*/
  int _=read();
  while(_--){
  	n=read();m=read();q=read();
  	init();
  	
  	for(int i=1;i<=n;i++)
  	  for(int j=1;j<m;j++){
  	  	char ch=getchar();
  	  	while(!isdigit(ch)) ch=getchar();
  	  	d[id(i,j)][1]=d[id(i,j+1)][3]=(int)(ch-'0');
  	  }
  	for(int i=1;i<n;i++)
  	  for(int j=1;j<=m;j++){
  	  	char ch=getchar();
  	  	while(!isdigit(ch)) ch=getchar();
  	  	d[id(i,j)][2]=d[id(i+1,j)][0]=(int)(ch-'0');
  	  }
  	for(int i=1;i<=q;i++) a[i].col=read(),a[i].lv=read(),a[i].x=read(),a[i].y=read(),a[i].id=i;
  	sort(a+1,a+q+1,cmp1);
  	for(int i=1;i<=q;i++) a[i].lv=i;
  	sort(a+1,a+q+1,cmp2);
  	for(int i=1;i<=q;i++) val[id(a[i].x,a[i].y)]=i;
  	
  	for(int i=1;i<=n;i++)
  	  for(int j=1;j<=m;j++){
  	  	int nw=id(i,j);
  	  	for(int k=0;k<4;k++){
  	  	  if(d[nw][k]<=1) continue;
  	  	  int nxt=id(i+fx[k],j+fy[k]);
  	  	  if(!val[nw]&&!val[nxt]) t[(d[nw][k]==3)?0:(k%2+1)].merge(nw,nxt);
  	  	}
  	  }
  	for(int i=1;i<=n;i++)
  	  for(int j=1;j<=m;j++){
  	  	int nw=id(i,j),fa=t[0].find(nw);
  	  	T[2].rt[fa]=T[2].upd(1,n*m,T[2].rt[fa],id2(i,j));
  	  	T[3].rt[fa]=T[3].upd(1,n*m,T[3].rt[fa],nw);
  	  	for(int k=0;k<4;k++) if(d[nw][k]==3){
  	  	  int nxt=id(i+fx[k],j+fy[k]);
  	  	  if(val[nxt]){
  	  	  	int col=a[val[nxt]].col;
  	  	  	T[col].rt[fa]=T[col].upd(1,q,T[col].rt[fa],a[val[nxt]].lv);
  	  	  }
  	  	}
  	  }
  	  
  	for(int i=q;i>=1;i--){
  	  int num=id(a[i].x,a[i].y),col=a[i].col;
  	  for(int j=0;j<4;j++) if(d[num][j]==3){//删除
  	  	int nxt=id(a[i].x+fx[j],a[i].y+fy[j]),fa=t[0].find(nxt);
  	  	T[col].rt[fa]=T[col].upd(1,q,T[col].rt[fa],a[i].lv,-1);
  	  }
  	  
  	  for(int j=0;j<4;j++) if(d[num][j]==3){//合并3
  	  	int nxt=id(a[i].x+fx[j],a[i].y+fy[j]);
  	  	if(val[nxt]&&val[nxt]<i) continue;
  	  	t[0].merge(num,nxt,1);
  	  }
  	  int idfa=t[0].find(num);
  	  ans[i]=t[0].gsz(num)+T[col^1].qle(1,q,T[col^1].rt[idfa],a[i].lv);
  	  
  	  for(int j=0;j<4;j++) if(d[num][j]==2){
  	  	int nxt=id(a[i].x+fx[j],a[i].y+fy[j]);
  	  	if(val[nxt]&&val[nxt]<i) continue;
  	  	t[j%2+1].merge(num,nxt);
  	  }
  	  int mx1=t[1].gmx(num),mx2=t[2].gmx(num),mn1=t[1].gmn(num),mn2=t[2].gmn(num);
  	  ans[i]+=mx2-mn2+1+(mx1-mn1)/m+1;
  	  int mx=id2(rev(mx1).fi,rev(mx1).se),mn=id2(rev(mn1).fi,rev(mn1).se);
  	  ans[i]-=T[2].qlr(idfa,mn,mx)+T[3].qlr(idfa,mn2,mx2);
  	  
  	  if(d[mn1][0]==2&&chk(i,val[mn1-m])&&!T[col^1].qin(1,q,T[col^1].rt[idfa],a[val[mn1-m]].lv)) ++ans[i];
  	  if(d[mx2][1]==2&&chk(i,val[mx2+1])&&!T[col^1].qin(1,q,T[col^1].rt[idfa],a[val[mx2+1]].lv)) ++ans[i];
  	  if(d[mx1][2]==2&&chk(i,val[mx1+m])&&!T[col^1].qin(1,q,T[col^1].rt[idfa],a[val[mx1+m]].lv)) ++ans[i];
  	  if(d[mn2][3]==2&&chk(i,val[mn2-1])&&!T[col^1].qin(1,q,T[col^1].rt[idfa],a[val[mn2-1]].lv)) ++ans[i];
  	  
  	  for(int j=0;j<4;j++) if(d[num][j]==1){
  	  	int nxt=id(a[i].x+fx[j],a[i].y+fy[j]);
  	  	if(val[nxt]&&val[nxt]<i){
  	  	  if(chk(i,val[nxt])&&!T[col^1].qin(1,q,T[col^1].rt[idfa],a[val[nxt]].lv)) ++ans[i];
  	  	}else if(t[0].find(nxt)!=t[0].find(num)) ++ans[i];
  	  }
  	  
  	  --ans[i];
  	}
  	
  	for(int i=1;i<=q;i++) prt(ans[i]);
  }
  return 0;
}

Conclusion

  1. 做题时一定要 手玩样例,遇到取最优情况的时候可以考虑 打表 找规律。(C. [NOIP2021] 方差)
  2. 求两个值相减的最值时,我们可以将一个值设成 dp 状态,从而维护另外一个值的最值。(C. [NOIP2021] 方差)