UOJ Round #8 题解

A

显然走路策略不是一直向左就是一直向右,不是一直向下就是一直向上。

假如 a [ i ] ! = a [ i + 1 ] a[i]!=a[i+1] a[i]!=a[i+1],那么此时不管 b [ j ] b[j] b[j] 是什么,从 ( i , j ) (i,j) (i,j) 走到 ( i + 1 , j ) (i+1,j) (i+1,j) 时间必然都 + 1 +1 +1,所以可以发现,横向移动与竖向移动之间互不影响,所以我们求一个前缀和与后缀和,每次看看向左还是向右,向上还是向下就好了。

代码如下:

#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 100010

int n,m,q,a[maxn],b[maxn];
int suma1[maxn],suma2[maxn];
int sumb1[maxn],sumb2[maxn];

int main()
{
    
    
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i<=m;i++)scanf("%d",&b[i]);
	for(int i=2;i<=n;i++)suma1[i]=suma1[i-1]+(a[i]!=a[i-1]);
	for(int i=2;i<=m;i++)sumb1[i]=sumb1[i-1]+(b[i]!=b[i-1]);
	for(int i=n-1;i>=1;i--)suma2[i]=suma2[i+1]+(a[i]!=a[i+1]);
	for(int i=m-1;i>=1;i--)sumb2[i]=sumb2[i+1]+(b[i]!=b[i+1]);
	
	scanf("%d",&q);
	while(q--)
	{
    
    
		int x,y,xx,yy,ans=0;
		scanf("%d %d %d %d",&x,&y,&xx,&yy);
		if(xx<x)swap(x,xx);if(yy<y)swap(y,yy);
		if(x!=xx)ans+=min(suma1[xx]-suma1[x],suma1[x]+suma2[xx]+(a[1]!=a[n]));
		if(y!=yy)ans+=min(sumb1[yy]-sumb1[y],sumb1[y]+sumb2[yy]+(b[1]!=b[m]));
		printf("%d\n",ans);
	}
}

B

看到这种敢随机给数据的,就容易想到珂朵莉树,不难发现,又是要我们进行复杂度分析了。

对于一个位置 i i i,假如存在 j j j 满足 i < j i<j i<j a i < a j a_i<a_j ai<aj,那么显然 i i i 是对答案无影响的,如果一个点对答案有影响,那么显然满足 ∀ j > i : a i > a j \forall j>i:a_i>a_j j>i:ai>aj。那么 i i i 有影响的概率为 1 n − i + 1 \frac 1 {n-i+1} ni+11,于是可以得到,这个数列中有影响的点的期望个数为 ∑ i = 1 n 1 i = ln ⁡ n ≈ log ⁡ 2 n \sum_{i=1}^n \frac 1 i=\ln n \approx \log_2 n i=1ni1=lnnlog2n

这样的话,每次询问我们找到这些有影响的点,更新一波答案即可。

维护的话选择用线段树,每个节点记录区间内的最大和最小值,这样修改操作很容易就能解决。

询问的话,设 c a l c ( x , y ) calc(x,y) calc(x,y) 表示将 x , y x,y x,y 代入式子 a x + b y + c x y ax+by+cxy ax+by+cxy 的值。在递归时,先看右儿子,设左右儿子的最大值分别为 m a x _ z u o , m a x _ y o u max\_zuo,max\_you max_zuo,max_you,假如满足 c a l c ( r , m a x _ y o u ) > a n s calc(r,max\_you)>ans calc(r,max_you)>ans,那么就递归进入右儿子,然后搞完右儿子再看左儿子,左儿子如法炮制,看是否满足 c a l c ( m i d , m a x _ z u o ) > a n s calc(mid,max\_zuo)>ans calc(mid,max_zuo)>ans 即可。

这样做的意义,其实就是在看这个儿子里面是否存在有影响的点,可以发现,我们每次优先处理右儿子,这样可以保证,在处理区间 x x x ~ y y y 时,右边的 y + 1 y+1 y+1 ~ n n n 都处理完了,此时的 a n s ans ans 记录的就是右边区间的最大值,假如存在 c a l c ( v a l u e _ m a x , y ) > a n s calc(value\_max,y)>ans calc(value_max,y)>ans,那么这个 v a l u e _ m a x value\_max value_max 一定比右边的所有 a i a_i ai 都大,那么就是有影响的。

代码如下:

#include <cstdio>
#include <algorithm>
using namespace std;
#define ll long long

int n,m,x0,v[100010];
int rd(){
    
    x0=(100000005ll*x0+20150609ll)%998244353;return x0/100;}
ll ans,a,b,c;
ll calc(int x,int y){
    
    return (ll)a*x+(ll)b*y+(ll)c*x*y;}
struct node{
    
    
	int l,r,mid,ma,mi,lazy;
	node *zuo,*you;
	void check(){
    
    ma=max(zuo->ma,you->ma);mi=min(zuo->mi,you->mi);}
	node(int x,int y):l(x),r(y),lazy(0)
	{
    
    
		mid=l+r>>1;
		if(l<r)
		{
    
    
			zuo=new node(l,mid);you=new node(mid+1,r);
			check();
		}
		else ma=mi=v[l],zuo=you=NULL;
	}
	void update(){
    
    lazy^=1;swap(mi,ma);mi=100000-mi;ma=100000-ma;}
	void pushdown()
	{
    
    
		if(lazy)
		{
    
    
			if(zuo!=NULL)zuo->update(),you->update();
			lazy=0;
		}
	}
	void Cchange(int x,int y)
	{
    
    
		if(l==r){
    
    ma=mi=y;return;}
		pushdown();
		if(x<=mid)zuo->Cchange(x,y);
		else you->Cchange(x,y);
		check();
	}
	void Rchange(int x,int y)
	{
    
    
		if(l==x&&r==y){
    
    update();return;}
		pushdown();
		if(y<=mid)zuo->Rchange(x,y);
		else if(x>=mid+1)you->Rchange(x,y);
		else zuo->Rchange(x,mid),you->Rchange(mid+1,y);
		check();
	}
	void ask(int x,int y)
	{
    
    
		pushdown();
		if(x<=l&&r<=y)
		{
    
    
			if(l==r)return ans=max(ans,calc(l,mi)),void();
			if(calc(r,you->ma)>ans)you->ask(x,y);
			if(calc(mid,zuo->ma)>ans)zuo->ask(x,y);
			return;
		}
		if(l==r)return;
		if(y>=mid+1)you->ask(x,y);
		if(x<=mid)zuo->ask(x,y);
	}
};
node *root;
char s[5];

int main()
{
    
    
	scanf("%d %d %d",&n,&m,&x0);
	for(int i=1;i<=n;i++)v[i]=rd()%100001;
	root=new node(1,n);
	while(m--)
	{
    
    
		scanf("%s",s);
		int x=rd(),y=rd();
		if(s[0]=='C')root->Cchange(x%n+1,y%100001);
		else
		{
    
    
			x=x%n+1;y=y%n+1;if(x>y)swap(x,y);
			if(s[0]=='R')root->Rchange(x,y);
			else
			{
    
    
				scanf("%lld %lld %lld",&a,&b,&c); ans=0ll;
				root->ask(x,y); printf("%lld\n",ans);
			}
		}
	}
}

C

将限制从 [ 1 , c i ] [1,c_i] [1,ci] 改成 [ 0 , c i ) [0,c_i) [0,ci),显然是不影响的,将所有合法多项式常数项 − 1 -1 1 即可。

考虑这个多项式的下降幂形式,设为 f ( x ) = ∑ i = 0 n q i x i ‾ f(x)=\sum_{i=0}^n q_i x^{\underline i} f(x)=i=0nqixi

对于第 i i i 个限制 0 ≤ f ( i ) < c i 0\leq f(i)<c_i 0f(i)<ci,可以写成 0 ≤ q i i i ‾ + . . . + q 0 i 0 ‾ < c i 0\leq q_ii^{\underline i}+...+q_0i^{\underline 0}<c_i 0qiii+...+q0i0<ci,将除了第 i i i 项以外的加起来记为 C C C,就有 − C ≤ q i i i ‾ < c i − C -C\leq q_ii^{\underline i}<c_i-C Cqiii<ciC,于是 q i q_i qi 的方案数就是:
{ ⌊ c i i i ‾ ⌋               ( C   m o d   i i ‾ ≥ c i   m o d   i i ‾ ) ⌊ c i i i ‾ ⌋ + 1        ( C   m o d   i i ‾ < c i   m o d   i i ‾ ) \begin{cases} \lfloor \dfrac {c_i} {i^{\underline i}}\rfloor~~~~~~~~~~~~~(C\bmod {i^{\underline i}}\geq c_i \bmod {i^{\underline i}})\\ \lfloor \dfrac {c_i} {i^{\underline i}}\rfloor+1~~~~~~(C\bmod {i^{\underline i}}<c_i \bmod {i^{\underline i}})\\ \end{cases} iici             (Cmodiicimodii)iici+1      (Cmodii<cimodii)

于是我们需要知道 C   m o d   i i ‾ C \bmod i^{\underline i} Cmodii 的值,这等于 ∑ j = 0 j − 1 q j × i j ‾   m o d   i i ‾ \sum_{j=0}^{j-1} q_j\times i^{\underline j} \bmod i^{\underline i} j=0j1qj×ijmodii

所以我们需要前面的 q j q_j qj 取模后的值,考虑枚举 q i   m o d   ( n − i ) ! q_i\bmod (n-i)! qimod(ni)! 的值,设为 r i r_i ri,那么 q i = ( n − i ) ! × t + r i q_i=(n-i)!\times t+r_i qi=(ni)!×t+ri

由于现在枚举的是 r i r_i ri,所以取模时不对 i i ‾ = i ! i^{\underline i}=i! ii=i! 取模,而是对 i ! ( n − i ) ! i!(n-i)! i!(ni)! 取模,对于一个 q j × i j ‾ q_j\times i^{\underline j} qj×ij,即 ( n − j ) ! × t ′ × i j ‾ (n-j)!\times t'\times i^{\underline j} (nj)!×t×ij,它对 i ! ( n − i ) ! i!(n-i)! i!(ni)! 取模后的结果等于 r j   m o d   i ! ( n − i ) ! r_j\bmod i!(n-i)! rjmodi!(ni)!,因为 ( n − j ) ! × i j ‾ (n-j)!\times i^{\underline j} (nj)!×ij 整除 ( n − i ) ! × i i ‾ (n-i)!\times i^{\underline i} (ni)!×ii

于是 C   m o d   i ! ( n − i ) ! = ( ∑ j = 0 i − 1 r j )   m o d   i ! ( n − i ) ! C\bmod i!(n-i)!=(\sum_{j=0}^{i-1} r_j)\bmod i!(n-i)! Cmodi!(ni)!=(j=0i1rj)modi!(ni)!,将它与 c i   m o d   i ! ( n − i ) ! c_i\bmod i!(n-i)! cimodi!(ni)! 作对比,即可求出 q i q_i qi 的方案数,枚举出所有 r i r_i ri 后,将 q i q_i qi 的方案数相乘就是贡献,将所有贡献加起来就是答案,时间复杂度 O ( n × ∏ i = 1 n i ! ) O(n\times \prod_{i=1}^n i!) O(n×i=1ni!)

考虑优化第一层枚举,即 r 0 = 0 r_0=0 r0=0 ~ n ! − 1 n!-1 n!1,当 r 0 r_0 r0 增加时, C   m o d   i ! ( n − i ) ! C\bmod i!(n-i)! Cmodi!(ni)! 在不断加一然后归零,当 C ≥ c i ( m o d i ! ( n − i ) ! ) C\geq c_i \pmod {i!(n-i)!} Cci(modi!(ni)!) 时,贡献为 ⌊ c i i ! ( n − i ) ! ⌋ \lfloor \dfrac {c_i} {i!(n-i)!}\rfloor i!(ni)!ci,否则就要 + 1 +1 +1,也就是说,在 C C C 的一个变化周期内,只会有两个变化点,周期长度为 n ! n! n!,变化点总数为 2 n ! i ! ( n − i ) ! = 2 ( n i ) 2\dfrac {n!} {i!(n-i)!}=2\Large\binom n i 2i!(ni)!n!=2(in),总数为 ∑ i = 1 n 2 ( n i ) = 2 n + 1 \sum_{i=1}^n 2\binom n i=2^{n+1} i=1n2(in)=2n+1,相邻两个变化点之间,对答案的贡献是不变的。

于是我们不需要枚举第一层,枚举完其它层之后,求出所有变化点,然后将变化点排序,最后再枚举 r 0 r_0 r0 求解即可,这样的好处是,你不需要每次求出变化点后就枚举 r 0 r_0 r0,你可以积攒一下,比如说积攒个 50 50 50 次枚举,将它们的所有变化点合起来一起求解,用桶排可以去掉排序的 log ⁡ \log log,时间复杂度为 O ( ( n − 1 ) ! . . . 1 ! × 2 n − 1 ) O((n-1)!...1!\times 2^{n-1}) O((n1)!...1!×2n1)

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 10
#define mod 998244353

int T,n,c[maxn],ans;
int fac[maxn],w[maxn][maxn];
int k[maxn],k1[maxn],k2[maxn];
int val[1<<maxn+1];
int r[maxn],sr[maxn];
struct par{
    
    int x,y;}sav[730][510];
int tot[730],Sr[310][maxn],cnt=0;
void add(int &x,int y){
    
    x=(x+y>=mod?x+y-mod:x+y);}
void dec(int &x,int y){
    
    x=(x-y<0?x-y+mod:x-y);}
int sta[210];
void calc_ans(){
    
    
	for(int i=1;i<=cnt;i++){
    
    
		sta[i]=0;for(int j=0;j<=n;j++){
    
    
			if(Sr[i][j]<k2[j])sta[i]|=(1<<j);
		}
	}
	int sum=0;
	for(int i=1;i<=cnt;i++)add(sum,val[sta[i]]);
	add(ans,sum);
	for(int i=1;i<fac[n];i++){
    
    
		for(;tot[i];tot[i]--){
    
    
			par p=sav[i][tot[i]];
			dec(sum,val[sta[p.x]]);//在变化点改变贡献
			add(sum,val[sta[p.x]^=p.y]);
		}
		add(ans,sum);
	}
	tot[0]=cnt=0;
}
void calc(){
    
    
	cnt++;
	for(int x=0;x<=n;x++){
    
    
		for(int i=-sr[x];i<fac[n];i+=k[x])//找到所有变化点,i在枚举会产生变化的r0的值
			if(i>=0)sav[i][++tot[i]]=(par){
    
    cnt,1<<x};
		for(int i=k2[x]-sr[x];i<fac[n];i+=k[x])
			if(i>=0)sav[i][++tot[i]]=(par){
    
    cnt,1<<x};
		Sr[cnt][x]=sr[x];
	}
	if(cnt==50)calc_ans();
}
void dfs(int x){
    
    
	if(x>n)return calc();
	sr[x]=0;
	for(int i=1;i<x;i++)sr[x]+=r[i]*w[x][i];//求C
	sr[x]%=k[x];
	for(r[x]=0;r[x]<fac[n-x];r[x]++)
		dfs(x+1),sr[x]=(sr[x]+fac[x])%k[x];
}

int main()
{
    
    
	fac[0]=1;for(int i=1;i<=6;i++)fac[i]=fac[i-1]*i;
	w[0][0]=1;for(int i=1;i<=6;i++){
    
    //下降幂预处理
		w[i][0]=1;for(int j=1;j<=i;j++){
    
    
			w[i][j]=w[i][j-1]*(i-j+1);
		}
	}
	scanf("%d",&T);while(T--)
	{
    
    
		scanf("%d",&n);ans=0;
		for(int i=0;i<=n;i++){
    
    
			scanf("%d",&c[i]);
			k[i]=fac[i]*fac[n-i];//第i层的模数
			k1[i]=c[i]/k[i];k2[i]=c[i]%k[i];
		}
		for(int i=0;i<(1<<(n+1));i++){
    
    
			//枚举每个位置是C%k[i]>=c[i]%k[i]还是<,对于每种情况求出贡献,一共2^{n+1}种
			val[i]=1;for(int j=0;j<=n;j++){
    
    
				val[i]=1ll*val[i]*(k1[j]+(i>>j&1))%mod;
			}
		}
		dfs(1);calc_ans();
		printf("%d\n",ans);
	}
}

猜你喜欢

转载自blog.csdn.net/a_forever_dream/article/details/109646606