2021牛客暑期多校训练营10 E题: More Fantastic Chess Problem

E题: More Fantastic Chess Problem

原题链接:https://ac.nowcoder.com/acm/contest/11261/E

题目大意

k k k 维空间中下国际象棋,棋盘两角为 ( 1 , 1 , . . . , 1 ) (1,1,...,1) (1,1,...,1) ( a 1 , a 2 , . . . a k ) (a_1,a_2,...a_k) (a1,a2,...ak) ,棋子移动规则如下:

  • 国王 - 选择一个非空的维度子集,坐标在这些维度上加 1 1 1 或减 1 1 1 ;
  • 皇后 - 选择一个非空的维度子集和正整数 x x x ,坐标在这些维度上加 x x x 或减 x x x ;
  • 城堡 - 选择一个维度和正整数 x x x ,坐标在这个维度上加 x x x 或减 x x x ;
  • 主教 - 选择两个维度和正整数 x x x ,坐标在这些维度上加 x x x 或减 x x x ;
  • 骑士 - 选择两个维度,坐标在其中一个维度上加 2 2 2 或减 2 2 2 ,在另一个维度上加 1 1 1 或减 1 1 1 ;
  • 士兵 - 规则太复杂了,不考虑这个棋子;

现在有一个 k ( 1 ≤ k ≤ 3 ⋅ 1 0 5 ) k(1\le k\le 3·10^5) k(1k3105) 维,大小为 a 1 ⋅ a 2 ⋅ . . . ⋅ a k ( 1 ≤ a i ≤ 1 0 6 ) a_1·a_2·...·a_k(1\le a_i\le 10^6) a1a2...ak(1ai106) 的棋盘和一个棋子,将该棋子放在特定格子上,求其在一次移动中可达的不同单元格数量。
q ( 0 ≤ q ≤ 3 ⋅ 1 0 5 ) q(0\le q\le 3·10^5) q(0q3105) 次操作,每次操作会将棋子按规则移动到另一个位置,并需要再次求解可达的单元格数量。
答案很大,对 998244353 998244353 998244353 取模。

题解

(下文讲解顺序根据难度而定)

城堡

显然,城堡在第 i i i 维度上可移动达到位置数为 a i − 1 ( 去 掉 自 己 所 在 的 位 置 ) a_i-1(去掉自己所在的位置) ai1() ,累加求解即可。
答案: a n s = ∑ i = 1 k ( a i − 1 ) ans=\sum\limits^k_{i=1}(a_i-1) ans=i=1k(ai1)
修改:操作对答案无影响。

国王

国王在移动时对于每个维度有三种选择:加 1 1 1 ,不变,或减 1 1 1
我们可以对于每个维度进行计算选择数(若三种选择都可则为 3 3 3 ,加 1 1 1 或减 1 1 1 不可(贴边界 1 1 1 a i a_i ai )则为2,加 1 1 1 和减 1 1 1 都不可(即该维度无法移动,上下边界相等 a i = 1 a_i=1 ai=1 )则为1),然后根据乘法原理相乘即可。
注意不能所有维度均不变,所以最终答案还要减 1 1 1
答案: a n s = − 1 + ∏ i = 1 k ( 3 − ( b i = 1 ) − ( b i = a i ) ) ans=-1+\prod\limits^k_{i=1}(3-(b_i=1)-(b_i=a_i)) ans=1+i=1k(3(bi=1)(bi=ai))
修改:将累乘项除去(采用逆元改为乘法)原来的选择数,再乘上新的选择数即可。

骑士

我们求出骑士第 i i i 维度移动 1 1 1 的选择数(同国王计算),记为 s u m 1 i sum1_i sum1i ,移动 2 2 2 的选择数(同理)记为 s u m 2 i sum2_i sum2i ,根据乘法原理不难初步得到答案为 ( ∑ i = 1 k s u m 1 i ) ⋅ ( ∑ i = 1 k s u m 2 i ) (\sum\limits^k_{i=1}sum1_i)·(\sum\limits^k_{i=1}sum2_i) (i=1ksum1i)(i=1ksum2i) ,但显然,在同一维度进行移动 1 1 1 和移动 2 2 2 是非法的,我们减去这些情况。
答案: a n s = ( ∑ i = 1 k s u m 1 i ) ⋅ ( ∑ i = 1 k s u m 2 i ) − ∑ i = 1 k ( s u m 1 i ⋅ s u m 2 i ) ans=(\sum\limits^k_{i=1}sum1_i)·(\sum\limits^k_{i=1}sum2_i)-\sum\limits^k_{i=1}(sum1_i·sum2_i) ans=(i=1ksum1i)(i=1ksum2i)i=1k(sum1isum2i)
修改:对三个累加和分别开变量保存,然后修改第 i i i 维度时分别减去原先的 s u m 1 i / s u m 2 i / s u m 1 i + s u m 2 i sum1_i/sum2_i/sum1_i+sum2_i sum1i/sum2i/sum1i+sum2i ,再加上新的 s u m 1 i / s u m 2 i / s u m 1 i + s u m 2 i sum1_i/sum2_i/sum1_i+sum2_i sum1i/sum2i/sum1i+sum2i 即可。

皇后

皇后与国王类似,区别在于步长不一定。
我们写出皇后在第 i i i 维度时,对于不同步长 l e n len len 的选择数:
f ( i ) = { 3 ( 0 < l e n ≤ min ⁡ ( b i − 1 , a i − b i ) ) 2 ( m i n ( b i − 1 , a i − b i ) < l e n ≤ max ⁡ ( b i − 1 , a i − b i ) ) 1 ( max ⁡ ( b i − 1 , a i − b i ) < l e n ) f(i)= \left\{ \begin{array}{rcl} 3&(0<len\le \min(b_i-1,a_i-b_i))\\ 2&(min(b_i-1,a_i-b_i)< len\le \max(b_i-1,a_i-b_i))\\ 1&(\max(b_i-1,a_i-b_i)<len) \end{array} \right. f(i)=321(0<lenmin(bi1,aibi))(min(bi1,aibi)<lenmax(bi1,aibi))(max(bi1,aibi)<len)

不难发现,该分段函数的每一个值呈现出区间的形式。
我们考虑用线段树维护 l e n = 1 , 2 , . . . len=1,2,... len=1,2,... 时的选择数累乘积,对每个维度计算 min ⁡ \min min max ⁡ \max max ,然后进行分段区间乘法,最后区间求和即可。
最后记得减去区间总长度,因为每个 l e n len len 的方案中都有一种所有维度均不变的方案。
答案:好吧我直接写不出来建议看代码。
修改:区间除法(乘逆元)即可。

主教

主教与皇后类似,区别在于从选取区间子集变为只选取 2 2 2 个区间。
我们写出主教在第 i i i 维度时,对于不同步长 l e n len len 的选择数(只考虑移动):
f ( i ) = { 2 ( 0 < l e n ≤ min ⁡ ( b i − 1 , a i − b i ) ) 1 ( m i n ( b i − 1 , a i − b i ) < l e n ≤ max ⁡ ( b i − 1 , a i − b i ) ) 0 ( max ⁡ ( b i − 1 , a i − b i ) < l e n ) f(i)= \left\{ \begin{array}{rcl} 2&(0<len\le \min(b_i-1,a_i-b_i))\\ 1&(min(b_i-1,a_i-b_i)< len\le \max(b_i-1,a_i-b_i))\\ 0&(\max(b_i-1,a_i-b_i)<len) \end{array} \right. f(i)=210(0<lenmin(bi1,aibi))(min(bi1,aibi)<lenmax(bi1,aibi))(max(bi1,aibi)<len)

我们设 s u m 1 i sum1_i sum1i 表示 l e n = i len=i len=i 时选择数为 1 1 1 的维度数, s u m 2 i sum2_i sum2i 表示 l e n = i len=i len=i 时选择数为 2 2 2 的维度数。
每次选取两种选择组成一种移动方案,注意减去同时选取同一维度的非法情况。
答案: ∑ i = 1 k ( C s u m 1 i + 2 ⋅ s u m 2 i 2 − s u m 2 i ) \sum\limits^k_{i=1}(C^2_{sum1_i+2·sum2_i}-sum2_i) i=1k(Csum1i+2sum2i2sum2i)
修改:设 s i = s u m 1 i + s u m 2 i s_i=sum1_i+sum2_i si=sum1i+sum2i ,将组合数项 C s u m 1 i + 2 ⋅ s u m 2 i 2 C^2_{sum1_i+2·sum2_i} Csum1i+2sum2i2 写作 s i ( s i − 1 ) 2 = s i 2 − s i 2 \frac{s_i(s_i-1)}{2}=\frac{s_i^2-s_i}{2} 2si(si1)=2si2si ,分离常数 1 2 \frac{1}{2} 21 (可计算后乘逆元),线段树区间维护 s i s_i si 的平方和与累加和即可(操作时区间加法即可), ∑ i = 1 k s u m 2 i \sum\limits^k_{i=1}sum2_i i=1ksum2i 可预处理。

线段树维护区间平方和

首先我们考虑单项的修改,显然: a 2 → ( a + b ) 2 = a 2 + 2 a b + b 2 a^2\rightarrow (a+b)^2=a^2+2ab+b^2 a2(a+b)2=a2+2ab+b2
然后转化到区间:
a l 2 + a l + 1 2 + . . . + a r 2 → ( a l + b ) 2 + ( a l + 1 + b ) 2 + . . . + ( a r + b ) 2 = a l 2 + 2 a l b + b 2 + a l + 1 2 + 2 a l + 1 b + b 2 + . . . + a r 2 + 2 a r b + b 2 = ( a l 2 + a l + 1 2 + . . . + a r 2 ) + 2 b ( a l + a l + r + . . . + a r ) + ( r − l + 1 ) b 2 \begin{aligned} a^2_l+a^2_{l+1}+...+a^2_r\rightarrow& (a_l+b)^2+(a_{l+1}+b)^2+...+(a_r+b)^2\\ =&a^2_l+2a_lb+b^2+a^2_{l+1}+2a_{l+1}b+b^2+...+a^2_r+2a_rb+b^2\\ =&(a^2_l+a^2_{l+1}+...+a^2_r)+2b(a_l+a_{l+r}+...+a_r)+(r-l+1)b^2 \end{aligned} al2+al+12+...+ar2==(al+b)2+(al+1+b)2+...+(ar+b)2al2+2alb+b2+al+12+2al+1b+b2+...+ar2+2arb+b2(al2+al+12+...+ar2)+2b(al+al+r+...+ar)+(rl+1)b2

同时维护区间平方和与累加和即可(修改时先改平方和沿用旧累加和)

参考代码

#include<bits/stdc++.h>
#define For(i,n,m) for(int i=n;i<=m;i++)
#define p 998244353
using namespace std;
void read(int &x){int ret=0;char c=getchar(),last=' ';while(!isdigit(c))last=c,c=getchar();while(isdigit(c))ret=ret*10+c-'0',c=getchar();x=last=='-'?-ret:ret;}

const int MAXK=3e5+5,MAXN=1e6+5;
int k,q,invx[MAXK];//invx记录逆元
string ask;
int a[MAXK],b[MAXK];
struct node{
	int l,r,n,lazy;
}st[MAXN<<2];
struct Node{
	int l,r,_2,_1,lazy;
}segtree[MAXN<<2];
int inv(int x){
	if(invx[x])return invx[x];//访问时已计算则直接返回
	invx[x]=1;
	int xx=x;//记录原下标
	for(int b=p-2;b;x=1ll*x*x%p,b>>=1)if(b&1)invx[xx]=1ll*invx[xx]*x%p;
	return invx[xx];//计算完后返回
}

void solve_Rook()//城堡
{
	int ans=-k;//相当于k个-1
	For(i,1,k)ans=(1ll*ans+a[i])%p;//累加
	printf("%d\n",ans);
	int l,x,d;
	while(q--){
		read(l);
		while(l--)read(x),read(d);//修改时假装读入即可
		printf("%d\n",ans);
	}
}

int get_King(int i)//计算国王第i维度的操作选择数
{
	int ret=3;
	if(b[i]==1)ret--;//下边界
	if(b[i]==a[i])ret--;//上边界
	return ret;
}

void solve_King()//国王
{
	int ans=1;
	For(i,1,k)ans=1ll*ans*get_King(i)%p;//累乘
	printf("%d\n",ans-1);//注意-1
	int l,x,d;
	while(q--){
		read(l);
		while(l--){
			read(x),read(d);
			ans=1ll*ans*inv(get_King(x))%p;//乘逆元(即除法)
			b[x]+=d;//修改b[x]
			ans=1ll*ans*get_King(x)%p;//乘上新的选择数
		}
		printf("%d\n",ans-1);//注意-1
	}
}

int get_Knight(int i,int num){//计算骑士第i维度移动num的选择数
	int ret=2;
	if(b[i]<=num)ret--;
	if(b[i]>=a[i]+1-num)ret--;
	return ret;
}

void solve_Knight()//骑士
{
	int sum1=0,sum2=0,mul=0,_1,_2;
	For(i,1,k){
		_1=get_Knight(i,1);//第i维度移动1
		_2=get_Knight(i,2);//第i维度移动2
		sum1+=_1;//累加移动1
		sum2+=_2;//累加移动2
		mul+=_1*_2;//非法项
	}
	printf("%lld\n",(1ll*sum1*sum2%p-mul+p)%p);//注意减法+p再取模
	int l,x,d;
	while(q--){
		read(l);
		while(l--){
			read(x),read(d);
			_1=get_Knight(x,1);//原第x维度移动1
			_2=get_Knight(x,2);//原第x维度移动2
			sum1-=_1,sum2-=_2,mul-=_1*_2;//减去原先的
			b[x]+=d;//修改b[x]
			_1=get_Knight(x,1);//现第x维度移动1
			_2=get_Knight(x,2);//现第x维度移动2
			sum1+=_1,sum2+=_2,mul+=_1*_2;//更新
		}
		printf("%lld\n",(1ll*sum1*sum2%p-mul+p)%p);//注意减法+p再取模
	}
}

void pushup_Queen(int x){st[x].n=(1ll*st[x<<1].n+st[x<<1|1].n)%p;}//皇后_x区间更新

void pushdown_Queen(int x){//皇后_x区间传递标记
	st[x<<1].n=1ll*st[x<<1].n*st[x].lazy%p;
	st[x<<1].lazy=1ll*st[x<<1].lazy*st[x].lazy%p;
	st[x<<1|1].n=1ll*st[x<<1|1].n*st[x].lazy%p;
	st[x<<1|1].lazy=1ll*st[x<<1|1].lazy*st[x].lazy%p;
	st[x].lazy=1;//还原
}

void build_Queen(int x,int l,int r){//皇后_线段树建树
	st[x].l=l,st[x].r=r,st[x].lazy=1;//注意lazy标记累乘初始化为1
	if(l==r){//叶节点
		st[x].n=1;//累乘,初始为1
		return;
	}
	int mid=l+r>>1;
	build_Queen(x<<1,l,mid);
	build_Queen(x<<1|1,mid+1,r);
	pushup_Queen(x);//更新
}

void update_Queen(int x,int l,int r,int mul){//皇后_线段树更新
	if(st[x].l==l&&st[x].r==r){//找到区间
		st[x].n=1ll*st[x].n*mul%p;//更新数值
		st[x].lazy=1ll*st[x].lazy*mul%p;//更新lazy标记
		return;
	}
	pushdown_Queen(x);//传递标记
	int mid=st[x].l+st[x].r>>1;
	if(r<=mid)update_Queen(x<<1,l,r,mul);
	else if(l>mid)update_Queen(x<<1|1,l,r,mul);
	else update_Queen(x<<1,l,mid,mul),update_Queen(x<<1|1,mid+1,r,mul);
	pushup_Queen(x);//更新
}

void get_Queen(int i){//计算皇后第i维度的不同操作数对应区间,并更新线段树
	int mi=min(b[i]-1,a[i]-b[i]);
	int ma=a[i]-1-mi;
	if(mi)update_Queen(1,1,mi,3);
	if(mi<ma)update_Queen(1,mi+1,ma,2);
	//update_Queen(1,ma+1,a[i],1);//乘1相当于没有乘
}

void remove_Queen(int i){//移除皇后第i维度的不同操作数在线段树上对应区间的影响
	int mi=min(b[i]-1,a[i]-b[i]);
	int ma=a[i]-1-mi;
	if(mi)update_Queen(1,1,mi,inv(3));//乘逆元(即除法)
	if(mi<ma)update_Queen(1,mi+1,ma,inv(2));//乘逆元(即除法)
	//update_Queen(1,ma+1,a[i],inv(1));//乘1的逆元(除1)相当于没有乘
}

void solve_Queen()//皇后
{
	int ma=0;
	For(i,1,k)ma=max(ma,a[i]);//max(a[i])-1是皇后可能移动的最大步长(其实不-1也可以,不影响)
	build_Queen(1,1,ma-1);//建树,注意ma-1
	For(i,1,k)get_Queen(i);//初始化
	printf("%d\n",(1ll*st[1].n-(ma-1)+p)%p);//注意ma-1
	int l,x,d;
	while(q--){
		read(l);
		while(l--){
			read(x),read(d);
			remove_Queen(x);//移除原先该维度
			b[x]+=d;//修改b[x]
			get_Queen(x);//更新当前该维度
		}
		printf("%d\n",(1ll*st[1].n-ma+1+p)%p);//注意ma-1
	}
}

void pushup_Bishop(int x){//主教_x区间更新
	segtree[x]._1=(1ll*segtree[x<<1]._1+segtree[x<<1|1]._1)%p;//累加和
	segtree[x]._2=(1ll*segtree[x<<1]._2+segtree[x<<1|1]._2)%p;//平方和
}

void pushdown_Bishop(int x){//主教_x区间传递标记
	segtree[x<<1]._2=(segtree[x<<1]._2+2ll*segtree[x].lazy%p*segtree[x<<1]._1%p+1ll*segtree[x].lazy*segtree[x].lazy%p*(segtree[x<<1].r-segtree[x<<1].l+1)%p+p)%p;//左子区间更新平方和
	segtree[x<<1]._1=(segtree[x<<1]._1+1ll*segtree[x].lazy*(segtree[x<<1].r-segtree[x<<1].l+1)%p+p)%p;//左子区间更新累加和
	segtree[x<<1].lazy=(1ll*segtree[x<<1].lazy+segtree[x].lazy+p)%p;//左子区间更新lazy标记
	segtree[x<<1|1]._2=(segtree[x<<1|1]._2+2ll*segtree[x].lazy%p*segtree[x<<1|1]._1%p+1ll*segtree[x].lazy*segtree[x].lazy%p*(segtree[x<<1|1].r-segtree[x<<1|1].l+1)%p+p)%p;//右子区间更新平方和
	segtree[x<<1|1]._1=(segtree[x<<1|1]._1+1ll*segtree[x].lazy*(segtree[x<<1|1].r-segtree[x<<1|1].l+1)%p+p)%p;//右子区间更新累加和
	segtree[x<<1|1].lazy=(1ll*segtree[x<<1|1].lazy+segtree[x].lazy+p)%p;//右子区间更新lazy标记
	segtree[x].lazy=0;//还原
}

void build_Bishop(int x,int l,int r){//主教_线段树建树
	segtree[x].l=l,segtree[x].r=r;
	if(l==r)return;
	int mid=l+r>>1;
	build_Bishop(x<<1,l,mid);
	build_Bishop(x<<1|1,mid+1,r);
}

void update_Bishop(int x,int l,int r,int add){//主教_线段树更新
	if(segtree[x].l==l&&segtree[x].r==r){//找到区间
		segtree[x]._2=(segtree[x]._2+2ll*add*segtree[x]._1%p+1ll*add*add%p*(r-l+1)%p+p)%p;//更新平方和
		segtree[x]._1=(segtree[x]._1+1ll*add*(r-l+1)%p+p)%p;//更新累加和
		segtree[x].lazy=(1ll*segtree[x].lazy+add+p)%p;//更新lazy标记
		return;
	}
	pushdown_Bishop(x);//传递标记
	int mid=segtree[x].l+segtree[x].r>>1;
	if(r<=mid)update_Bishop(x<<1,l,r,add);
	else if(l>mid)update_Bishop(x<<1|1,l,r,add);
	else update_Bishop(x<<1,l,mid,add),update_Bishop(x<<1|1,mid+1,r,add);
	pushup_Bishop(x);//更新
}

int get_Bishop(int i){//计算主教第i维度的不同操作数对应区间,并更新线段树
	int mi=min(b[i]-1,a[i]-b[i]);
	int ma=a[i]-1-mi;
	if(mi)update_Bishop(1,1,mi,2);
	if(ma>mi)update_Bishop(1,mi+1,ma,1);
	//update_Bishop(1,ma+1,a[i],0);//加0相当于没有加
	return mi;
}

int remove_Bishop(int i){//移除主教第i维度的不同操作数在线段树上对应区间的影响
	int mi=min(b[i]-1,a[i]-b[i]);
	int ma=a[i]-1-mi;
	if(mi)update_Bishop(1,1,mi,-2);
	if(ma>mi)update_Bishop(1,mi+1,ma,-1);
	//update_Bishop(1,ma+1,a[i],0);//减0相当于没有减
	return mi;
}

void solve_Bishop()//主教
{
	int ma=0,ans=0;
	For(i,1,k)ma=max(ma,a[i]);//max(a[i])-1是主教可能移动的最大步长(其实不-1也可以,不影响)
	build_Bishop(1,1,ma-1);//建树,注意ma-1
	For(i,1,k)ans=(1ll*ans+get_Bishop(i))%p;//初始化线段树,并更新sum2累加和
	printf("%lld\n",((1ll*segtree[1]._2-segtree[1]._1+p)*inv(2)%p-ans+p)%p);//注意减法+p再取模
	int l,x,d;
	while(q--){
		read(l);
		while(l--){
			read(x),read(d);
			ans=(1ll*ans-remove_Bishop(x)+p)%p;//移除原先该维度,并更新sum2累加和
			b[x]+=d;//修改b[x]
			ans=(1ll*ans+get_Bishop(x))%p;//更新当前该维度,并更新sum2累加和
		}
		printf("%lld\n",((1ll*segtree[1]._2-segtree[1]._1+p)*inv(2)%p-ans+p)%p);//注意减法+p再取模
	}
}
int main()
{
	read(k),read(q);
	cin>>ask;
	For(i,1,k)read(a[i]);
	For(i,1,k)read(b[i]);
	if(ask=="Rook")solve_Rook();//城堡
	if(ask=="King")solve_King();//国王
	if(ask=="Knight")solve_Knight();//骑士
	if(ask=="Queen")solve_Queen();//皇后
	if(ask=="Bishop")solve_Bishop();//主教
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值