省选模拟(46-50)

省选模拟46

1.俄罗斯方块

2.能力强化

期望dp,min_max容斥
喂鸽子.
n粒玉米,m只鸽子,每粒玉米造成的饱食度不同,求把所有鸽子都喂饱的期望时间.
把所有鸽子都喂饱,就等价于把最后的一只鸽子喂饱的期望时间,也就是喂饱时间的期望最大值.
这里有个min_max容斥,问题就变成了求一个集合内部第一只被喂饱的鸽子的期望时间.
\(p\)表示只喂一只鸽子,喂恰好\(i\)次时恰好喂饱的概率.
\(q\)表示只喂一只鸽子,喂\(i\)次都没有喂饱的概率.
\(q=1-sum\ p\).
枚举集合,发现鸽子等价,所以只需要枚举集合大小.
发现如果把\(p,q\)变成多项式的形式,大小为\(i\)的集合就是\(P*Q*Q*Q...\),\(P\)卷上\(i-1\)\(Q\).
可是发现因为我们在求\(P,Q\)的时候没有考虑顺序,因此每个集合内部相当于已经乘过了a!,而我们此时要把他们打乱顺序,而内部打乱顺序其实是无用的.
所以用指数型生成函数EGF,在系数上\(*finv[i]\)最后取出时\(*fac[i]\)就好了.
再次发现\(P\)不能够完全被打乱顺序,因为我们需要求出恰好第i次喂食把第一只鸽子喂饱的方案.
所以把我们不知道第一只鸽子第几次喂食就吃饱的那个最后一次吃给它抽出来,保证不参与乱序,必须是最后一次.
然后统计答案的时候有很多的系数.
枚举\(j\)表示最后的投食数量.
\(*\frac{1}{i^j}\)因为每次选中的概率为\(\frac{1}{i}\).
\(*j\)因为期望=概率*实际值.
\(*\frac{m}{i}\)考虑只考虑i只鸽子可是实际情况是每次都会在n只里面选,因此这是总方案的\(\frac{i}{m}\),所以乘上.
\(*i\),枚举选中的是哪个鸽子.

#include<cstdio>
#define ll long long
using namespace std;
const int N=10005;
const int mod=998244353;
int mo(int a){return a>=mod?a-mod:a;}
inline int rd(register int x=0,register char ch=getchar(),register int f=0){
	while(ch<'0'||ch>'9') f=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-48,ch=getchar();
	return f?-x:x;
}
int n,m,K;
int a[N],f[2][N],p[N],q[N],fac[N],inv[N],finv[N],P[N],Q[N],t[N];
int C(int x,int y){return x<y||y<0?0:1LL*fac[x]*finv[y]%mod*finv[x-y]%mod;}
int main(){
	fac[0]=fac[1]=inv[0]=inv[1]=finv[0]=finv[1]=1;
	for(int i=2;i<N;++i) fac[i]=1LL*fac[i-1]*i%mod,inv[i]=1LL*inv[mod%i]*(mod-mod/i)%mod,finv[i]=1LL*finv[i-1]*inv[i]%mod;
	n=rd();m=rd();K=rd(); for(int i=1;i<=n;++i) a[i]=rd();
	f[0][0]=1;q[0]=1;
	for(int i=1;i<=K;++i){
		for(int j=0;j<K;++j)f[i&1][j]=0;
		for(int j=0;j<K;++j)for(int k=1;k<=n;++k)
			if(j+a[k]<K) f[i&1][j+a[k]]=mo(f[i&1][j+a[k]]+1LL*f[i&1^1][j]*inv[n]%mod);
			else p[i]=mo(p[i]+1LL*f[i&1^1][j]*inv[n]%mod);
		q[i]=mo(q[i-1]-p[i]+mod);
	}
	for(int i=0;i<=K;++i) P[i]=1LL*p[i+1]*finv[i]%mod,Q[i]=1LL*q[i]*finv[i]%mod;
	int ans=0;
	for(int i=1;i<=m;++i){
		for(int j=0,J=i*K;j<=J;++j) t[j]=0;
		for(int j=0,J=(i-1)*K;j<=J;++j)if(P[j])for(int k=0;k<=K;++k)t[j+k]=mo(t[j+k]+1LL*P[j]*Q[k]%mod);
		if(i!=1) for(int j=0,J=i*K;j<=J;++j) P[j]=t[j];
		int ret=0;
		for(int j=1,J=i*K,pw=inv[i];j<=J;++j,pw=1LL*pw*inv[i]%mod) ret=mo(ret+1LL*P[j-1]*fac[j-1]%mod*j%mod*pw%mod);
		ans=mo(ans+(i&1?1LL:mod-1LL)*ret%mod*C(m,i)%mod*m%mod);
	}
	printf("%d\n",ans);
	return 0;
}

3.将军棋

省选模拟47

1.老夫

分块维护凸包.
其实有一个很简单的做法就是线段树维护,区间加等差数列,但不知道为什么没有人打,然后我很懒也没有打.
这种题都能用dp来做...
\(dp[i][j]\)\(c=i,p=j\)的最大贡献.
那么转移就是把一些看广告的变成了收费的.
即那些\(b_k=i\)的人的\(a_k\)会为\(p<=a_k\)\(p\)做贡献.
换句话说,\([1,a_k]\)区间加等差数列(p).
考虑维护凸包,然后用斜率为0的直线切出来的就是贡献最大的值.
为什么呢,因为发现区间加等差数列只会让所有的斜率都+1,可是并不会改变斜率大小关系.
换句话说,凸包形态不变.
所以分块维护凸包,维护答案的位置.
又因为斜率是单增的,所以答案的位置一定是单增的,用单调指针每次斜率增加的时候扫一下就好了.

#include<cstdio>
#include<algorithm>
#include<iostream>
#define ll long long
using namespace std;
const int N=1e5+50;
inline int rd(register int x=0,register char ch=getchar(),register int f=0){
	while(ch<'0'||ch>'9') f=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-48,ch=getchar();
	return f?-x:x;
}
int n,W,blo,num,m;
int L[350],R[350],bel[N],tag[N],sta[350][350],Ans[350];
pair<int,int>b[N];
struct point{
	ll x,y;
	friend point operator - (point a,point b){return (point){a.x-b.x,a.y-b.y};} 
}a[N];
ll cross(point a,point b){return a.x*b.y-a.y*b.x;}
double slope(point a,point b){return 1.0*(a.y-b.y)/(a.x-b.x);}

void Rebuild(int k){
	for(int i=L[k];i<=R[k];++i) a[i].y+=1LL*i*tag[k];tag[k]=0;
	sta[k][0]=0;
	for(int i=L[k];i<=R[k];++i){
		while(sta[k][0]>1&&cross(a[i]-a[sta[k][sta[k][0]]],a[sta[k][sta[k][0]-1]]-a[sta[k][sta[k][0]]])>0) --sta[k][0];
		sta[k][++sta[k][0]]=i;
	}
	Ans[k]=1;
	
	while(Ans[k]<sta[k][0]&&slope(a[sta[k][Ans[k]]],a[sta[k][Ans[k]+1]])>0) ++Ans[k];
}
int main(){
	n=rd();W=rd();
	for(int i=1;i<=n;++i) b[i].second=rd(),b[i].first=rd(),m=m>b[i].second?m:b[i].second;
	for(blo=1;blo*blo<m;++blo);
	num=m/blo+(m%blo!=0);
	for(int i=1;i<=m;++i) a[i].x=i;
	for(int i=1;i<=num;++i){
		L[i]=(i-1)*blo+1,R[i]=min(m,i*blo);
		for(int j=L[i];j<=R[i];++j) bel[j]=i;
		Rebuild(i);
	} 
	sort(b+1,b+n+1);
	for(int i=1,j=1;i<=b[n].first+1;++i){
		while(j<=n&&b[j].first<i){
			int sc=b[j++].second;
			if(!sc) continue;
			for(int k=1;k<bel[sc];++k){
				tag[k]++;
				while(Ans[k]<sta[k][0]&&slope(a[sta[k][Ans[k]]],a[sta[k][Ans[k]+1]])+tag[k]>0) ++Ans[k];
			}
			for(int k=L[bel[sc]];k<=sc;++k) a[k].y+=k;
			Rebuild(bel[sc]);
		}
		ll ans=0;
		for(int k=1;k<=num;++k) ans=max(ans,a[sta[k][Ans[k]]].y+1LL*tag[k]*sta[k][Ans[k]]);
		printf("%lld ",ans+1LL*W*i*(n-j+1));

	}
	return 0;
}

2.打算

把下标变成\((x+y,x-y)\)后,每次移动都会必然造成两维的同时改变.
因此可以单独考虑每一维了.
考虑每次的位置都可以变成形如\(pos=\frac{t}{L}S[L]+S[t\%L]\)的形式.
变成三元组的话就是(a,b,c).
这样的话按照c升序,然后每段能够列出一个恒等式,解出最后的\(S[L]\)的范围,取一个合法的范围再构造就好了.

3.报复社会

省选模拟48

1.A

\(sam+set+BIT\)
求终点在\([L,R]\)范围内的前缀的最长公共后缀.
考虑建sam.
然后考虑答案就是\(max _{i,j \in [L,R]} lca(s[i],s[j])\).
这样点对显然是\(O(n^2)\)的就死死了.
考虑最小表示这些点对无非就是一个更小的区间.
考虑枚举lca,然后用set进行启发式合并,每次新增的点和前驱后继加入结构体.
然后把这些贡献和询问按L降序排序,用BIT更新答案,单调指针扫.

#include<cstdio>
#include<cstring>
#include<set>
#include<iostream>
#include<algorithm>
#define itset set<int>::iterator
using namespace std;
const int N=3e5+50;

inline int rd(register int x=0,register char ch=getchar(),register int f=0){
	while(ch<'0'||ch>'9') f=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-48,ch=getchar();
	return f?-x:x;
}
int n,m,lst,cnt,ptr;
int fa[N],ch[N][2],len[N],buc[N],rk[N],ret[N],c[N];
set<int>se[N];
char s[N];
struct node{int x,y,v;}q[N],t[N*40];
bool cmp(node a,node b){return a.x>b.x;}
void add(int p,int v){
	for(;p<=n;p+=p&-p) c[p]=c[p]>v?c[p]:v;
}
int ask(int p,int a=0){
	for(;p;p-=p&-p) a=a>c[p]?a:c[p];
	return a;
}
void extend(int c,int u){
	int p=lst,np;np=lst=++cnt;
	len[np]=len[p]+1;
	for(;p&&!ch[p][c];p=fa[p])ch[p][c]=np;
	if(!p) fa[np]=1;
	else{
		int q=ch[p][c];
		if(len[q]==len[p]+1) fa[np]=q;
		else{
			int nq=++cnt;
			len[nq]=len[p]+1;fa[nq]=fa[q];fa[np]=fa[q]=nq;
			ch[nq][0]=ch[q][0],ch[nq][1]=ch[q][1];
			for(;p&&ch[p][c]==q;p=fa[p])ch[p][c]=nq;
		}
	}
	se[np].insert(u);
}
int main(){
	n=rd();m=rd();scanf("%s",s+1);
	lst=cnt=1;for(int i=1;i<=n;++i) extend(s[i]-'0',i);
	for(int i=1;i<=cnt;++i) buc[len[i]]++;
	for(int i=1;i<=n;++i) buc[i]+=buc[i-1];
	for(int i=1;i<=cnt;++i) rk[buc[len[i]]--]=i;
	for(int i=cnt;i>=2;--i){
		int x=rk[i],p=fa[rk[i]];
		if(se[x].size()>se[p].size()) swap(se[x],se[p]);
		for(itset it=se[x].begin();it!=se[x].end();++it){
			itset pos=se[p].lower_bound(*it);
			if(pos!=se[p].end()) t[++ptr]=(node){*it,*pos,len[p]};
			if(pos!=se[p].begin()) t[++ptr]=(node){*--pos,*it,len[p]};
		}
		for(itset it=se[x].begin();it!=se[x].end();++it) se[p].insert(*it);
	}
	for(int i=1;i<=m;++i) q[i].v=i,q[i].x=rd(),q[i].y=rd();
	sort(q+1,q+m+1,cmp);
	sort(t+1,t+ptr+1,cmp);
	for(int i=1,j=1;i<=m;++i){
		while(j<=ptr&&t[j].x>=q[i].x) add(t[j].y,t[j].v),++j;
		ret[q[i].v]=ask(q[i].y);
	}
	for(int i=1;i<=m;++i) printf("%d\n",ret[i]);
	return 0;
}

2.B

?
到所有点距离最小的点就是树的重心.
问题是求每个点变为重心最少需要改变多少条边.
首先求出重心,那么所有的子树大小都小于\(n/2\).
对于其他的不是重心的点,一定只有向着重心的那个方向的子树才会可能大于\(n/2\).
所以可以通过砍掉原重心的子树,把子树安到这个点上来使得那个方向的子树大小减小.
贪心的考虑一定会是从大到小砍子树.
如果说会砍到走向我们自己的那棵子树的话,可以有可能把那条边砍掉后把重心都安在这个点的子树是更优的.

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=2e7+50;
inline int rd(register int x=0,register char ch=getchar(),register int f=0){
	while(ch<'0'||ch>'9') f=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-48,ch=getchar();
	return f?-x:x;
}
int n,B,rt,Mx;
int head[N],to[N<<1],nxt[N<<1],is[N],s[N],t[N],siz[N],ret[N];
int cmp(int a,int b){return siz[a]>siz[b];}
void lnk(int x,int y){
	to[++B]=y,nxt[B]=head[x],head[x]=B;
	to[++B]=x,nxt[B]=head[y],head[y]=B;
}
void groot(int x,int prt){
	siz[x]=1;int mx=0;
	for(int i=head[x];i;i=nxt[i])if(to[i]!=prt) groot(to[i],x),siz[x]+=siz[to[i]],mx=max(mx,siz[to[i]]);
	mx=max(mx,n-siz[x]);
	if(mx<Mx) rt=x,Mx=mx;
}
void dfs(int x,int prt){
	siz[x]=1;
	for(int i=head[x];i;i=nxt[i])if(to[i]!=prt)dfs(to[i],x),siz[x]+=siz[to[i]];
}
void solve(int x,int prt,int bel){
	if(x!=rt){
		ret[x]=lower_bound(t,t+bel,n-siz[x]-(n>>1))-t;
		if(ret[x]>=bel) ret[x]=min(lower_bound(t+bel,t+s[0]+1,n-(n>>1)+(t[bel]-t[bel-1])-siz[x])-t-1,lower_bound(t+bel,t+s[0]+1,n-(n>>1))-t);
		for(int i=head[x];i;i=nxt[i]) if(to[i]!=prt) solve(to[i],x,bel);
	}
	else for(int i=head[x];i;i=nxt[i]) if(to[i]!=prt) solve(to[i],x,is[to[i]]);
}
int main(){
//	freopen("text_b.in","r",stdin);
	n=rd();
	
	for(int i=1;i<n;++i) lnk(rd(),rd());
	Mx=n+1;
	groot(1,0);
	dfs(rt,0);
	for(int i=head[rt];i;i=nxt[i]) s[++s[0]]=to[i];
	
	sort(s+1,s+s[0]+1,cmp);
	for(int i=1;i<=s[0];++i) is[s[i]]=i;
	for(int i=1;i<=s[0];++i) t[i]=t[i-1]+siz[s[i]];
	solve(rt,0,0);
	for(int i=1;i<=n;++i) printf("%d\n",ret[i]);
	return 0;
}

3.C

?
首先对于这个置换来讲,会形成若干个环.
首先存在奇环就一定不合法.
对于每个环上都只能间隔选择(
直接搞会是\(2^{50}\).
考虑到对于二元环来讲一定是选择前面的作为(更优.
所以就变成了2^{25}.

省选模拟49

1.Manager

主席树
假如有了dfs序就可以查询子树的信息了.
用主席树维护权值线段树.
考虑一个点的改变至多从v[mid]变成v[mid+1].
所以记录这两个值,对于递归到当前节点,如果祖先节点的v[mid]大于等于a[x],就说明这个祖先的贡献变成了v[mid+1].
用BIT维护,单点修改,区间查询.

2.GCD再放送

3.dict

考虑合法情况只有之前位都等于,当前位小于或者等于的情况.
考虑会有若干个位置确定了值,会把整个序列划分成了若干项,每项又有若干值可供选择.
就是一堆组合数相乘.
又会发现每次的乘积是可以继承的,每次只会改变一个位置.
所以暴力可以枚举当前位置的值是多少然后统计.
考虑如何优化,一个比较好的理解方式是可以把这种选数的过程看作是集合划分.
如果每次的复杂度都只是集合的更小的那一半,好像复杂度就是对的了(吗?)
所以当枚举的值超过目前值域的一半时就转而统计不合法的位置再减去就是答案.

#include<cstdio>
#include<algorithm>
#include<set>
#define ll long long
#define itset set<int>::iterator
using namespace std;
const int N=2e5+50;
const int mod=998244353;
inline int rd(register int x=0,register char ch=getchar(),register int f=0){
	while(ch<'0'||ch>'9') f=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-48,ch=getchar();
	return f?-x:x;
}
int n,m;
int b[N],p[N],fac[N],inv[N],finv[N];
ll C(ll x,ll y){return x<y||y<0?0:1LL*fac[x]*finv[y]%mod*finv[x-y]%mod;}
ll mgml(ll a,ll b,ll ans=1){for(;b;b>>=1,a=a*a%mod)if(b&1)ans=ans*a%mod;return ans;}
set<int>se;
int main(){
	//freopen("text.in","r",stdin);
	fac[0]=fac[1]=inv[0]=inv[1]=finv[0]=finv[1]=1;
	for(int i=2;i<N;++i) fac[i]=1LL*fac[i-1]*i%mod,inv[i]=1LL*inv[mod%i]*(mod-mod/i)%mod,finv[i]=1LL*finv[i-1]*inv[i]%mod;
	n=rd();m=rd();
	for(int i=1;i<=n;++i) b[i]=rd();
	for(int i=1;i<=n;++i) p[i]=rd();
	sort(b+1,b+n+1);
	ll lst=C(m,n),ans=0;
	for(int i=1;i<=n;++i){
		int x=p[i],pre_pos=0,pre_cnt=0,nxt_pos=n+1,nxt_cnt=m+1;
		itset it=se.upper_bound(x);
		if(it!=se.end()) nxt_pos=*it,nxt_cnt=b[nxt_pos];
		if(it!=se.begin()) pre_pos=*--it,pre_cnt=b[pre_pos];
		lst=lst*mgml(C(nxt_cnt-pre_cnt-1,nxt_pos-pre_pos-1),mod-2)%mod;
		//printf("x:%d %d %d %d %d %lld\n",x,pre_pos,pre_cnt,nxt_pos,nxt_cnt,lst);
		if(nxt_cnt-1-(pre_cnt+1)+1<=(b[x]-1-(pre_cnt+1)+1)*2){
			ll ret=C(nxt_cnt-pre_cnt-1,nxt_pos-pre_pos-1);
			for(int j=b[x];j<=nxt_cnt-1;++j) ret=(ret-1LL*C(j-pre_cnt-1,x-pre_pos-1)*C(nxt_cnt-j-1,nxt_pos-x-1)%mod+mod)%mod;
			ans=(ans+ret*lst)%mod;
		}
		else for(int j=pre_cnt+1;j<b[x];++j) ans=(ans+1LL*C(j-pre_cnt-1,x-pre_pos-1)*C(nxt_cnt-j-1,nxt_pos-x-1)%mod*lst)%mod;//,printf("x:%d j:%d %lld\n",x,j,1LL*C(j-pre_cnt-1,x-pre_pos-1)*C(nxt_cnt-j-1,nxt_pos-x-1)%mod);
		se.insert(x);
		lst=lst*C(b[x]-pre_cnt-1,x-pre_pos-1)%mod*C(nxt_cnt-b[x]-1,nxt_pos-x-1)%mod;
	}
	printf("%lld\n",ans);
	return 0;
}

省选模拟50

1.小A的树

点分治
和[NOI 2010]超级钢琴非常像的思路.
基本上也做过很多类似的题了,就是确定一个端点然后另一个端点在区间中选择最大值进行统计并划分为两个区间.
考虑跨过这个分治中心的路径两个端点一定在两颗子树中,所以可以塞入结构体一个形如\((l,r,x,v)\)的东西.
确定一个点,然后另一个点在之前的子树所有的点的区间中选择,可以RMQ求最大值.
然后剩下的就是超级钢琴了.

#include<cstdio>
#include<queue>
#include<iostream>
#define ll long long
#define F for(int i=head[x];i;i=nxt[i])if(to[i]!=prt&&!vis[to[i]])
using namespace std;
const int N=2e5+50;
const int NLOG=4e6+50;

inline int rd(register int x=0,register char ch=getchar(),register int f=0){
	while(ch<'0'||ch>'9') f=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-48,ch=getchar();
	return f?-x:x;
}
int n,K,B,rt,sz,mx,L,R,dfn;
int head[N],to[N<<1],nxt[N<<1],w[N<<1],siz[N],vis[N],f[22][NLOG],Log[NLOG];
struct node{int l,r;ll v;}c[NLOG];
struct Node{
	int l,r,p,x;
	friend bool operator < (Node a,Node b){return c[a.p].v+c[a.x].v<c[b.p].v+c[b.x].v;}
};
priority_queue<Node>q;

void lnk(int x,int y,int z){
	to[++B]=y,nxt[B]=head[x],head[x]=B,w[B]=z;
	to[++B]=x,nxt[B]=head[y],head[y]=B,w[B]=z;
}
void groot(int x,int prt){
	int Mx=0; siz[x]=1;
	F groot(to[i],x),siz[x]+=siz[to[i]],Mx=max(Mx,siz[to[i]]);
	Mx=max(Mx,sz-siz[x]);
	if(Mx<mx) rt=x,mx=Mx;
}
void pdfs(int x,int prt){
	siz[x]=1;
	F pdfs(to[i],x),siz[x]+=siz[to[i]];
}
void dfs(int x,int prt,ll W){
	c[++dfn]=(node){L,R,W};
	F dfs(to[i],x,W+w[i]);
} 
void solve(int x,int prt){
	vis[x]=1; L=R=++dfn; c[dfn]=(node){L,R,0};
	F dfs(to[i],x,w[i]),R=dfn;
	F sz=siz[to[i]],mx=sz+1,groot(to[i],0),solve(rt,x);
}
int z(int x,int y){return c[x].v>c[y].v?x:y;}
int ask(int x,int y){
	return z(f[Log[y-x+1]][x],f[Log[y-x+1]][y-(1<<Log[y-x+1])+1]);
}
int main(){
	n=rd();K=rd();
	for(int i=1,x,y,Z;i<n;++i) x=rd(),y=rd(),Z=rd(),lnk(x,y,Z);
	sz=n; mx=sz+1; groot(1,0); solve(rt,0);
	for(int i=2;i<NLOG;++i) Log[i]=Log[i>>1]+1;
	for(int i=1;i<=dfn;++i) f[0][i]=i;
	for(int i=1;i<=Log[dfn];++i)for(int j=1;j+(1<<i)-1<=dfn;++j)f[i][j]=z(f[i-1][j],f[i-1][j+(1<<i-1)]);
	for(int i=1;i<=dfn;++i) q.push((Node){c[i].l,c[i].r,ask(c[i].l,c[i].r),i});
	while(K--){
		Node a=q.top(); q.pop(); printf("%lld\n",c[a.p].v+c[a.x].v);
		if(a.p>a.l) q.push((Node){a.l,a.p-1,ask(a.l,a.p-1),a.x});
		if(a.p<a.r) q.push((Node){a.p+1,a.r,ask(a.p+1,a.r),a.x});
	}
	return 0;
}

2.小B的序列

线段树
其实并不会证明复杂度.
其实并不理解.
\(Accepted\)了.
每个节点维护\(Or,And,Mx,Tag\).
不继续递归的条件就是\((Or[k]|x)-Or[k]=(And[k]|x)-And[k]\)\((Or[k]&x)-Or[k]=(And[k]&x)-And[k]\)
大致的含义就是说对每一位的贡献都相同,那么就可以停止递归了.

#include<cstdio>
#define lch k<<1
#define rch k<<1|1
using namespace std;
const int N=2e5+50;

inline int rd(register int x=0,register char ch=getchar(),register int f=0){
	while(ch<'0'||ch>'9') f=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-48,ch=getchar();
	return f?-x:x;
}
int n,Q,e;
int Or[N<<2],And[N<<2],mx[N<<2],tag[N<<2];

int max(int a,int b){return a>b?a:b;}
void down(int k){
	if(!(e=tag[k]))return;tag[k]=0;
	Or[lch]+=e,And[lch]+=e,tag[lch]+=e,mx[lch]+=e;
	Or[rch]+=e,And[rch]+=e,tag[rch]+=e,mx[rch]+=e;
}
void add_and(int k,int l,int r,int L,int R,int x){
	if(L<=l&&r<=R&&(Or[k]&x)-Or[k]==(And[k]&x)-And[k]) return e=(Or[k]&x)-Or[k],Or[k]+=e,And[k]+=e,mx[k]+=e,tag[k]+=e,void();
	int mid=(l+r)>>1;down(k);
	if(L<=mid) add_and(lch,l,mid,L,R,x);
	if(R>mid) add_and(rch,mid+1,r,L,R,x);
	Or[k]=Or[lch]|Or[rch],And[k]=And[lch]&And[rch],mx[k]=max(mx[lch],mx[rch]);
}
void add_or(int k,int l,int r,int L,int R,int x){
	if(L<=l&&r<=R&&(Or[k]|x)-Or[k]==(And[k]|x)-And[k]) return e=(Or[k]|x)-Or[k],Or[k]+=e,And[k]+=e,mx[k]+=e,tag[k]+=e,void();
	int mid=(l+r)>>1;down(k);
	if(L<=mid) add_or(lch,l,mid,L,R,x);
	if(R>mid) add_or(rch,mid+1,r,L,R,x);
	Or[k]=Or[lch]|Or[rch],And[k]=And[lch]&And[rch],mx[k]=max(mx[lch],mx[rch]);
}
int ask(int k,int l,int r,int L,int R){
	if(L<=l&&r<=R) return mx[k];
	int mid=(l+r)>>1;down(k);
	if(R<=mid) return ask(lch,l,mid,L,R);
	if(L>mid) return ask(rch,mid+1,r,L,R);
	return max(ask(lch,l,mid,L,R),ask(rch,mid+1,r,L,R));
}
void build(int k,int l,int r){
	if(l==r) return mx[k]=Or[k]=And[k]=rd(),void();
	int mid=(l+r)>>1;
	build(lch,l,mid);build(rch,mid+1,r);
	Or[k]=Or[lch]|Or[rch],And[k]=And[lch]&And[rch],mx[k]=max(mx[lch],mx[rch]);
}
int main(){
	n=rd();Q=rd();
	build(1,1,n);
	while(Q--){
		int o=rd();
		if(o==1){
			int l=rd(),r=rd(),x=rd();
			add_and(1,1,n,l,r,x);
		}
		else if(o==2){
			int l=rd(),r=rd(),x=rd();
			add_or(1,1,n,l,r,x);
		}
		else{
			int l=rd(),r=rd();
			printf("%d\n",ask(1,1,n,l,r));
		}
	}
	return 0;
}

3.小C的利是

首先要知道行列式的定义是啥,\(n!\)种排列,每种排列每个位置值的积\(*(-1)^{num}\)的和.
\(num\)是逆序对个数.
因为有个\((-1)^{num}\),所以需要一个\(rand()\)来保证下正确性.
假如说我们设每个位置的权值是\(rand()*x^{a[i][j]}\),与矩阵树定理的解释类似.
那么可以通过x的幂次来判断合法情况,只要x的幂次是k的倍数的情况都合法.
那么类似于矩阵树定理的做法,可以用\(nk\)多项式插值出系数,复杂度是\(O(n^5)\)的.
然而发现我们只需要求出k的倍数项的系数和,就可以用单位根反演一下.
那么需要一个有K次单位根的质数,就用\(mod=xK+1\)构造一下就好了.
最后把\(x=(i=0:k-1)wn^i\)带入的和就是所有的k的倍数项的系数和了.
如果不是0那么就说明有解,否则无解.

#include<cstdio>
#include<vector>
#include<cstdlib>
#include<ctime>
#define ll long long
using namespace std;
const int N=105;

inline int rd(register int x=0,register char ch=getchar(),register int f=0){
	while(ch<'0'||ch>'9') f=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-48,ch=getchar();
	return f?-x:x;
}
int n,K,mod,g;
int a[N][N],b[N][N],c[N][N];
vector<int>d;

ll mgml(ll a,ll b,ll ans=1){if(b==-1) return 0;for(;b;b>>=1,a=a*a%mod)if(b&1)ans=ans*a%mod;return ans;} 
bool check(int t){
	for(int i=2;i*i<=t;++i)if(t%i==0)return 0;
	return 1;
}
bool Check(int t){
	for(int i=0;i<(int)d.size();++i) if(mgml(t,(mod-1)/d[i])==1) return 0;
	return 1;
}
int Gauss(int ans=1){
	for(int i=1;i<=n;++i){
		if(!c[i][i]) for(int j=i+1;j<=n;++j)if(c[j][i]){swap(c[i],c[j]);ans=(mod-ans);break;} 
		ans=1LL*ans*c[i][i]%mod;
		for(int bs=mgml(c[i][i],mod-2),j=1;j<=n;++j) c[i][j]=1LL*c[i][j]*bs%mod;
		for(int j=i+1;j<=n;++j)for(int k=n;k>=i;--k) c[j][k]=(c[j][k]-1LL*c[j][i]*c[i][k]%mod+mod)%mod;
	}
	return ans;
} 
int main(){
	srand((unsigned)time(0)); 
	n=rd();K=rd();
	for(int i=1;i<=n;++i)for(int j=1;j<=n;++j) a[i][j]=rd(),b[i][j]=rand();
	//mod=K*317913+1;
	for(mod=K+1;!check(mod);mod+=K);
	int t=mod-1;
	for(int i=2;i*i<=t;++i)if(t%i==0){d.push_back(i);while(t%i==0) t/=i;}
	if(t!=1) d.push_back(t); for(g=1;!Check(g);++g);
	int wn=mgml(g,(mod-1)/K),ans=0;
	for(int i=0,t=1;i<K;++i,t=1LL*t*wn%mod){
		for(int j=1;j<=n;++j)for(int k=1;k<=n;++k)c[j][k]=1LL*b[j][k]*mgml(t,a[j][k])%mod;
		ans=(ans+Gauss())%mod;
	}
	puts(ans?"Yes":"No");
	return 0;
}
posted @ 2020-03-18 07:22  _xuefeng  阅读(176)  评论(0编辑  收藏  举报