1月CWOI杂题

2024 年了捏。

C0452 【0105 A组】模拟测试

ZROI.

A 【0105 A组】剪彩灯

upd:我草?那边机子上过了?cqbz 机子这么快的吗。

啊啊啊啊啊啊啊啊啊啊啊啊,被自己蠢到了!怎么赛时写法那么垃圾啊!

考虑 dp,\(f_i\) 表示结尾为 \(i\) 的分割方法权值之和,\(f_0=1\)\(f_i=\sum\limits_{j=0}^{i-1}\text{mex}(j+1,i)f_j\)。显然 \(\text{mex}(j+1,i)\) 在固定 \(i\) 时随 \(j\) 变小而不降,于是考虑对每个 \(k\) 维护 \(L_k,R_k\) 表示当 \(j\in[L_k,R_k]\) 时,\(\text{mex}(j,i)=k\)(没有则为空集)。当右端点从 \(i-1\) 移动到 \(i\)\([L_{a_i},R_{a_i}]\) 这个区间会被分裂成若干个小区间。注意到求 \(\text{mex}(j,i)\) 可以在线段树上二分,然后假如现在有 \(t,u\),表示 \(u\) 是第一个 \(\text{mex}(u,i)=t\) 的位置,那么最后一个 \(\text{mex}=t\) 的位置 \(v\) 就等于 \(u\) 之后第一个等于 \(t\) 的位置减一。赛时这个傻逼用了一个主席树,然后 t 飞了。但是注意到 \([u+1,i]\) 内一定没有 \(t\),所以这就是 \(i\) 之前第一个等于 \(t\) 的位置。维护一下就行。势能分析可以得到复杂度均摊 \(\mathcal{O}(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int inf=1e9,mod=998244353;
char buf[1<<21],*p1=buf,*p2=buf;
#define nc() (p1==p2&&(p2=buf+fread(p1=buf,1,1<<21,stdin),p1==p2)?-1:*p1++)
inline int read(){
    int x=0;char c=nc();
    for(;!isdigit(c);c=nc());
    for(; isdigit(c);c=nc()) x=(x<<3)+(x<<1)+(c^48);
    return x;
}
inline int add(int x,int y){
	x+=y;if(x>=mod)x-=mod;
	return x;
}
inline int sub(int x,int y){
	x-=y;if(x<0)x+=mod;
	return x;
}
int a[1000005],f[1000005],g[1000005];
inline int G(int x){
	return ((x<0)?0:g[x]);
}
int sum,lst[1000005],vl[1000005],vr[1000005];
inline void Insert(int l,int r,int v){
	sum=add(sum,1ll*sub(G(r-1),G(l-2))*v%mod);
	vl[v]=min(vl[v],l),vr[v]=max(vr[v],r);
}
inline void Erase(int v){
	sum=sub(sum,1ll*sub(G(vr[v]-1),G(vl[v]-2))*v%mod);
	vl[v]=inf,vr[v]=-inf;
}
int c[4000015];
inline void upd(int l,int r,int p,int x,int v){
	if(l==r){c[p]=v;return;}
	int mid=(l+r)>>1;
	if(x<=mid)upd(l,mid,p<<1,x,v);
	else upd(mid+1,r,p<<1|1,x,v);
	c[p]=min(c[p<<1],c[p<<1|1]);
}
inline int ask(int l,int r,int p,int v){
	if(l==r)return l;
	int mid=(l+r)>>1;
	if(v<=c[p<<1])return ask(mid+1,r,p<<1|1,v);
	else return ask(l,mid,p<<1,v);
}
signed main(){
	int n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=0;i<=n;i++)vl[i]=inf,vr[i]=-inf;
	f[0]=1,g[0]=1;
	for(int i=1;i<=n;i++){
		lst[a[i]]=i;upd(0,n,1,a[i],i);
		if(vl[a[i]]!=inf){
			int l=vl[a[i]],r=vr[a[i]],p=r;Erase(a[i]);
			while(l<=p){
				int k=ask(0,n,1,p),q=max(l-1,lst[k]);
				if(q+1<=p)Insert(q+1,p,k);
				p=q;
			}
		}
		Insert(i,i,(a[i]==0));f[i]=sum,g[i]=add(g[i-1],f[i]);
	}
	printf("%d\n",f[n]);
	return 0;
}

B 【0105 A组】叉集合

注意到如果 \(a\le b\le c\),那么 \(a\oplus c\ge\min\{a\oplus b,b\oplus c\}\)。因为如果 \(a\oplus c=0\),说明 \(a=b=c\),结论显然成立;否则找到 \(a\oplus c\) 的 highbit,\(a\oplus b,b\oplus c\) 的这一位不可能同时为 1,于是得证。根据这个经典结论,我们只需要枚举相邻的一对,可以过 sub2。

还可以注意到当对于一对数字 \((i,j)\),有用的 \(x\)\(\mathcal{O}(V)\) 级别的,而且一定是使得 \(i+x\)\(j+x\) 的后若干位全为 0 的 最小的 \(x\)。证明就是现在有 \(i+x\) 的后 \(k\) 位为 0,考虑继续增大 \(x\),此时如果 \(j+x\) 进位就是刚刚说的 \(x\) 了,否则容易发现这样一定不优。

有这两个结论之后就可以直接 set 维护答案了,复杂度 \(\mathcal{O}(nV\log (nV))\),常数小。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,V;set<int>s;set<pii>t;
void ins(int x,int v){
	if(t.empty()){t.insert({x,v});return;}
	if((*t.begin()).fi<=x&&(*prev(t.upper_bound({x,inf}))).se<=v)return;
	auto it=t.upper_bound({x,-inf});
	while(it!=t.end()&&(*it).se>=v)t.erase(it++);
	t.insert({x,v});
}
void sol(int x,int y){
	ins(0,x^y);
	for(int k=0,v;k<=V;k++){
		v=(1ll<<(k+1))-(x&((1ll<<(k+1))-1));ins(v,(x+v)^(y+v));
		v=(1ll<<(k+1))-(y&((1ll<<(k+1))-1));ins(v,(x+v)^(y+v));
	}
}
int ask(int w){
	return (*prev(t.upper_bound({w,inf}))).se;
}
signed main(){
	V=read(),n=read();
	while(n--){
		int op=read(),w=read();
		if(op==1){
			auto p=s.insert(w).fi;
			if(p!=s.begin())sol(*prev(p),w);
			if(next(p)!=s.end())sol(w,*next(p));
		}
		else printf("%lld\n",ask(w));
	}
	return 0;
}

C 【0105 A组】玩图

C0453 【0106 A组】模拟测试

感觉这场相对简单一点啊。hyp 的题解

upd:傻逼傻逼傻逼傻逼傻逼傻逼傻逼傻逼傻逼傻逼傻逼傻逼傻逼傻逼傻逼傻逼,link 是关键字,B 题 ce 了,傻逼傻逼傻逼傻逼傻逼傻逼傻逼傻逼傻逼傻逼傻逼傻逼傻逼傻逼傻逼傻逼。

A 【0208 A组】矩阵

点击查看代码
#include<bits/stdc++.h>
#define int long long
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct Bignum{
	int n,a[20005];
	Bignum(){n=1;memset(a,0,sizeof(a));}
	void init(){
		for(int i=1;i<=n;i++)a[i]=0;
		n=1;
	}
	void fix(){while(n>1&&a[n]==0)n--;}
	Bignum operator +(const Bignum b)const{
		int m=max(n,b.n)+1;Bignum c;c.init();c.n=m;
		for(int i=1;i<=m;i++)c.a[i]=a[i]+b.a[i];
		for(int i=1;i<m;i++)c.a[i+1]+=c.a[i]/10,c.a[i]%=10;
		c.fix();return c;
	}
	void print(){
		for(int i=n;i>=1;i--)printf("%lld",a[i]);
		puts("");
	}
}f[2][2][2];
int x[5005],y[5005];char s[5005],t[5005];
signed main(){
	int k=read();scanf("%s%s",s+1,t+1);int ls=strlen(s+1),lt=strlen(t+1);
	for(int i=1;i<=ls;i++){
		for(int j=1;j<=k;j++)x[j]*=10;
		x[1]+=s[i]-'0';for(int j=1;j<=k;j++)x[j+1]+=x[j]/2,x[j]%=2;
	}
	for(int i=1;i<=lt;i++){
		for(int j=1;j<=k;j++)y[j]*=10;
		y[1]+=t[i]-'0';for(int j=1;j<=k;j++)y[j+1]+=y[j]/2,y[j]%=2;
	}
	if(x[k+1]||y[k+1])return puts("0"),0;
	for(int o=0;o<2;o++)for(int a=0;a<2;a++)for(int b=0;b<2;b++)f[o][a][b].init();
	f[0][0][0].n=1,f[0][0][0].a[1]=1;
	for(int i=1;i<=k;i++){
		int ox=x[i],oy=y[i],o=i&1ll;
		for(int a=0;a<2;a++)for(int b=0;b<2;b++)f[o][a][b].init();
		for(int a=0;a<2;a++)for(int b=0;b<2;b++){
			for(int c=0;c<2;c++)for(int d=0;d<2;d++){
				if((c%2==1&&d%2==0)||((c+ox+a)%2==1&&(d+oy+b)%2==0))continue;
				f[o][(c+ox+a)/2][(d+oy+b)/2]=f[o][(c+ox+a)/2][(d+oy+b)/2]+f[o^1][a][b];						
			}
		}
	}
	f[k&1ll][0][0].print();
	return 0;
}

B 【0208 A组】字符串

先把整个串 reverse 一下,问题变成结尾在 \([L,R]\) 范围内的本质不同子串个数。考虑对 s 建 sam,问题相当于对所有 \(i\in[L,R]\),找到 \(s[1\ldots i]\) 在 sam 上对应的点 \(p_i\),覆盖 \(p_i\) 到根路径上所有点,问总共有多少个点被覆盖。注意到可以离线,于是把询问离线下来,按右端点从小到大排序,每个点维护最后一次被覆盖的时间 \(t_i\),相当于问 \(L\le t_i\) 的点有多少个。树剖,用类似 CF1038D 的方法维护,复杂度 \(\mathcal{O}(n\log^3 n)\)。注意到此时询问和修改的复杂度非常不均衡,考虑用 \(\mathcal{O}(1)\)\(\mathcal{O}(\sqrt{n})\) 查的值域分块来维护答案,复杂度 \(\mathcal{O}(n\log^2 n+n\sqrt{n})\)。应该还有更优秀的做法,但我懒得想了。

这样做在 \(s[1\ldots i]\) 不是其所在等价类的最长串时会挂,但是你注意到这种情况不可能发生。

注意 link 在 noilinux 下会 ce,-100pts。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e9;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,T,num,bel[200005],bl[505],br[505];
int lst=1,tot=1,ch[400005][28],link[400005],len[400005];
int pos[200005];vector<int>g[400005];
void add(int c,int i){
	int y=lst,x=++tot;lst=x;pos[i]=x;len[x]=len[y]+1;
	while(y&&!ch[y][c])ch[y][c]=x,y=link[y];
	if(!y){link[x]=1;return;}
	int p=y,q=ch[p][c];
	if(len[q]==len[p]+1){link[x]=q;return;}
	int Q=++tot;len[Q]=len[p]+1;
	link[Q]=link[q],link[q]=link[x]=Q;
	for(int i=0;i<26;i++)ch[Q][i]=ch[q][i];
	while(y&&ch[y][c]==q)ch[y][c]=Q,y=link[y];
}
void build(){
	for(int i=2;i<=tot;i++)g[link[i]].push_back(i);
}
int dep[400005],siz[400005],son[400005];
void dfs1(int u){
	siz[u]=1,son[u]=0,dep[u]=dep[link[u]]+1;
	for(auto v:g[u]){
		dfs1(v),siz[u]+=siz[v];
		if(siz[son[u]]<siz[v])son[u]=v;
	}
}
int cur,dfn[400005],rnk[400005],top[400005];
void dfs2(int u,int rt){
	dfn[u]=++cur,rnk[cur]=u,top[u]=rt;
	if(son[u])dfs2(son[u],rt);
	for(auto v:g[u])if(son[u]!=v)dfs2(v,v);
}
struct block{
	ll c1[505],c2[200005];
	void add(int x,ll v){
		if(x)c1[bel[x]]+=v,c2[x]+=v;
	}
	ll ask(int x){
		ll res=0;
		for(int i=x;i<=br[bel[x]];i++)res+=c2[i];
		for(int i=bel[x]+1;i<=num;i++)res+=c1[i];
		return res;
	}
}B;
struct segtree{
	#define ls (p<<1)
	#define rs (p<<1|1)
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	struct Node{
		int mn;ll s;int se,tag;
	}c[1600005];
	void pushup(int p){
		c[p].mn=min(c[ls].mn,c[rs].mn);
		c[p].se=min(c[ls].se,c[rs].se);
		if(c[p].mn!=c[ls].mn)c[p].se=min(c[p].se,c[ls].mn);
		if(c[p].mn!=c[rs].mn)c[p].se=min(c[p].se,c[rs].mn);
		c[p].s=0;
		if(c[p].mn==c[ls].mn)c[p].s+=c[ls].s;
		if(c[p].mn==c[rs].mn)c[p].s+=c[rs].s;
	}
	void pushdown(int p){
		if(c[p].tag>c[ls].mn){
			c[ls].mn=c[p].tag;
			c[ls].tag=c[p].tag;
		}
		if(c[p].tag>c[rs].mn){
			c[rs].mn=c[p].tag;
			c[rs].tag=c[p].tag;
		}
		c[p].tag=-inf;
	}
	void build(int l,int r,int p){
		c[p].tag=-inf;
		if(l==r){
			c[p].mn=0,c[p].s=len[rnk[l]]-len[link[rnk[l]]],c[p].se=inf; 
			return;
		}
		int mid=(l+r)>>1;
		build(lson);build(rson);
		return;
	}
	void upd(int l,int r,int p,int L,int R,int v){
		if(c[p].mn>=v)return;
		if(L<=l&&r<=R){
			if(c[p].mn<v&&v<c[p].se){
				B.add(c[p].mn,-c[p].s);
				c[p].mn=v,c[p].tag=v;
				B.add(c[p].mn,c[p].s);
				return;
			}
		}
		if(l==r)return;
		int mid=(l+r)>>1;pushdown(p);
		if(L<=mid)upd(lson,L,R,v);
		if(R>mid)upd(rson,L,R,v);
		pushup(p);
	}
	#undef lson
	#undef rson
	#undef ls
	#undef rs
}Tr;
void upd(int u,int v,int k){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		Tr.upd(1,cur,1,dfn[top[u]],dfn[u],k);
		u=link[top[u]];
	}
	if(dep[u]<dep[v])swap(u,v);
	Tr.upd(1,cur,1,dfn[v],dfn[u],k);
}
int ql[200005],qr[200005];ll ans[200005];vector<int>v[200005];char s[200005];
inline void print(ll x){
	if(x<10){putchar(x+'0');return;}
	print(x/10);putchar(x%10+'0');
}
signed main(){
	scanf("%s",s+1);n=strlen(s+1);reverse(s+1,s+n+1);
	T=(int)sqrt(n),num=(n+T-1)/T;
	for(int i=1;i<=n;i++)bel[i]=(i-1)/T+1;
	for(int i=1;i<=num;i++)bl[i]=(i-1)*T+1,br[i]=min(n,i*T);
	for(int i=1;i<=n;i++)add(s[i]-'a',i);
	build();dfs1(1);dfs2(1,1);Tr.build(1,cur,1);
	int m=read();
	for(int i=1,L,R;i<=m;i++)L=read(),R=read(),ql[i]=n-R+1,qr[i]=n-L+1;
	for(int i=1;i<=m;i++)v[qr[i]].push_back(i);
	for(int i=1;i<=n;i++){
		upd(1,pos[i],i);
		for(auto x:v[i])ans[x]=B.ask(ql[x]);
	}
//	for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
	for(int i=1;i<=m;i++)print(ans[i]),putchar('\n');
	return 0;
}

C 【0208 A组】收费系统

不会。

C0454 【0108 A组】模拟测试

A 【0108 A组】喜庆气息

定义 \(f_u\) 表示 \(u\) 子树内的答案,转移就是 \(v\in son(u)\),把 \(\{f_v\}\) 从大到小排序,记为 \(t_0\ldots t_m\),则 \(f_u=\max\limits_{i=0}^m\{t_i+1+\lfloor\dfrac{i}{k}\rfloor\}\)。考虑换根。发现这就是多了一个子树外的 \(f\),类似做一下就行。复杂度 \(\mathcal{O}(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
#define fi first
#define se second
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
typedef pair<int,int>pii;
const int inf=1e9;
char buf[1<<21],*p1=buf,*p2=buf;
#define nc() (p1==p2&&(p2=buf+fread(p1=buf,1,1<<21,stdin),p1==p2)?-1:*p1++)
inline int read(){
    int x=0;char c=nc();
    for(;!isdigit(c);c=nc());
    for(; isdigit(c);c=nc()) x=(x<<3)+(x<<1)+(c^48);
    return x;
}
struct edge{
	int v,nxt;
}e[1000005];
int tot,head[500005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int w[500005],f[500005];
void dfs1(int u,int fa){
	vector<int>tmp;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		dfs1(v,u);tmp.push_back(v);
	}
	f[u]=0;sort(tmp.begin(),tmp.end(),[&](int x,int y){return f[x]>f[y];});
	for(int i=0,j=1;i<(int)tmp.size();i+=w[u],j++)f[u]=max(f[u],f[tmp[i]]+j);
}
int g[500005];
void dfs2(int u,int fa,int t){
//	printf("{%lld,%lld,%lld}\n",u,fa,t);
	vector<pii>tmp;tmp.push_back({t,u});
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		tmp.push_back({f[v],v});
	}
	int siz=(int)tmp.size();sort(tmp.begin(),tmp.end(),[](pii x,pii y){return x>y;});
	g[u]=0;for(int i=0,j=1;i<siz;i+=w[u],j++)g[u]=max(g[u],tmp[i].fi+j);
	vector<int>pre,suf;pre.resize(siz+5),suf.resize(siz+5);
	for(int i=0,j=0;i<siz;i++){
		if(i%w[u]==0)j++;
		if(i==0)pre[i]=tmp[i].fi+j;
		else if(i%w[u]==0)pre[i]=max(pre[i-1],tmp[i].fi+j);
		else pre[i]=pre[i-1];
	}
	for(int i=siz-1;i>=0;i--){
		if(i==siz-1){
			if((i-1)%w[u]==0)suf[i]=tmp[i].fi+1;
			else suf[i]=-inf;
		}
		else if((i-1)%w[u]==0)suf[i]=max(suf[i+1]+1,tmp[i].fi+1);
		else suf[i]=suf[i+1];
	}
	for(int i=0,j=0;i<siz;i++){
		if(tmp[i].se!=u){
			int T=0;
			if(i-1>=0)T=max(T,pre[i-1]);
			if(i+1<siz)T=max(T,suf[i+1]+j);
			dfs2(tmp[i].se,u,T);			
		}
		if(i%w[u]==0)j++;
	}
}
inline void print(int x){
	if(x>9)print(x/10);
	putchar(x%10+'0');
}
signed main(){
	int n=read();
	for(int i=1;i<=n;i++)w[i]=read();
	for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
	dfs1(1,0);dfs2(1,0,-inf);
	for(int i=1;i<=n;i++)print(g[i]),puts("");
	return 0;
}

B 【0108 A组】流行划过

先看没有黑洞的情况。显然这就是 nim,但这并不好拓展,考虑一些让四个维度不割裂开的做法。注意到此时对于一个确定的 \((x_1,x_2,x_3,*)\),至多存在一个 \(x_4\) 满足这是一个必败态,因为如果有 \(x_4<x'_4\),那么 \(x'_4\) 可以走到 \(x_4\) 了,它就必胜了。所以必败状态为 \(\mathcal{O}(n^3)\) 级别的,考虑从小到大枚举状态,每次找到一个必败态之后标记能到达它的 \(\mathcal{O}(n)\) 个状态为必胜态,时空复杂度 \(\mathcal{O}(n^4)\)

考虑 bitset 加速这个玩意。定义 \(b_1(x_1,x_2,x_3)[x_4]\) 表示对于 \(x'_1<x_1\),是否存在 \((x'_1,x_2,x_3,x_4)\) 为必败态,\(b_2,b_3\) 同理。从小到大枚举 \((x_1,x_2,x_3)\),那么一个 \(x_4\) 是必败当且仅当它是第一个到不了必败态的位置,把 \(b_1,b_2,b_3\) 或起来取反 _Find_first。时空复杂度 \(\mathcal{O}(\dfrac{n^4}{w})\)。注意到 bitset 可以滚动,空间复杂度 \(\mathcal{O}(\dfrac{n^3}{w})\)

现在我们加上黑洞。注意到这个黑洞其实就是把刚刚的过程分割为了若干段,每段相互独立,类似的做一下就行。时间复杂度 \(\mathcal{O}(\dfrac{n^4+nm}{w}+m\log m)\),空间复杂度 \(\mathcal{O}(\dfrac{n^3}{w}+m)\)

点击查看代码
#include<bits/stdc++.h>
#define y1 y3456 
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e9;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct Node{
	int x1,x2,x3,x4;
}a[100005];
bitset<201>b1[201][201],b2[201][201],b3[201][201],B1,B2;
signed main(){
	int n=read(),m=read();
	for(int i=1;i<=m;i++){
		int x1=read(),x2=read(),x3=read(),x4=read();
		a[i]=(Node){x1,x2,x3,x4};
	}
	sort(a+1,a+m+1,[](Node x,Node y){
		if(x.x1^y.x1)return x.x1<y.x1;
		if(x.x2^y.x2)return x.x2<y.x2;
		if(x.x3^y.x3)return x.x3<y.x3;
		return x.x4<y.x4;
	});
	int now=1;long long ans=0;vector<int>tmp;
	for(int x1=1;x1<=n;x1++){
		for(int x2=1;x2<=n;x2++){
			for(int x3=1;x3<=n;x3++){
				tmp.clear();int nxt=now;
				while(nxt<=m&&a[nxt].x1==x1&&a[nxt].x2==x2&&a[nxt].x3==x3)tmp.push_back(a[nxt].x4-1),nxt++;
				now=nxt;B2.reset();
				B1=(b1[x2][x3]|b2[x1][x3]|b3[x1][x2]).flip();
				for(auto x:tmp)B1[x]=0,B2[x]=1;
				for(int i=0;i<(int)tmp.size();i++){
					int pos=((i==0)?B1._Find_first():B1._Find_next(tmp[i-1]));
					if(B1[pos]!=0&&pos<tmp[i]&&pos<n)B2[pos]=1;
				}
				int pos=((tmp.empty())?B1._Find_first():B1._Find_next(tmp.back()));
				if(B1[pos]!=0&&pos<n)B2[pos]=1;
				ans+=n-B2.count();b1[x2][x3]|=B2,b2[x1][x3]|=B2,b3[x1][x2]|=B2;
				for(auto x:tmp)b1[x2][x3][x]=0,b2[x1][x3][x]=0,b3[x1][x2][x]=0;
			}
		}
	}
	printf("%lld\n",ans);
	return 0;
}

C 【0108 A组】重庆排列

CF698F 加强版。什么牛子题。

先看全为 0 的情况。考虑在 \(p_i=i\) 的排列上交换出所有合法的排列。如果 \(x=\prod\limits_{i=1}^k p_i^{k_i}\),记 \(f(x)\) 表示 \(\prod\limits_{i=1}^k p_i\)。注意到如果 \(f(i_1)=f(i_2)\),那么 \(i_1\)\(i_2\) 本质上是一样的,可以随便交换。记 \(c_1(i)\) 表示 \(\sum [f(x)=i]\),答案先乘个 \(\prod(c_1(i)!)\)。然后注意到如果有两个质数 \(p_1,p_2\)\(\lfloor\dfrac{n}{p_1}\rfloor=\lfloor\dfrac{n}{p_2}\rfloor\),那么这两个集合也可以交换。此时 \(p_1,p_2\) 需要 \(\ge\sqrt{n}\),那么这两个集合是没有交集的捏,就互不影响了捏,再乘上 \(\prod\limits_{i=1}^{i^2\le n}(c_2(i)!)\) 就好了,其中 \(c_2(i)=\sum [t(p_k)=i]\)\(t(i)=\lfloor\dfrac{n}{i}\rfloor\)。特别的,1 也算“质数”,且 \(t(1)=1\)。因为这个 \(t\) 其实是和 \(i\) 不互质的数的个数,而 1 只有它自己。

然后看有限制的情况。此时相当于强制交换了 \(i\)\(a_i\),我们需要判无解。观察交换的条件,被交换的两个数有三种情况:

  • 两个数有 \(f(i)=f(a)\)

  • 两个数有 \(t(i)=t(a)\)

  • 两个数有 \(i=k_1p_1,a=k_2p_2\),且 \(p_1,p_2\) 满足刚刚第二种交换方式;

前两个条件是简单的,考虑第三个。注意到此时 \(p_1,p_2\ge\sqrt{n}\),所以每个数最多有一个这样的质因子。我们取出 \(i,a\) 的最大质因子 \(x,y\),如果 \(t(x)\neq t(y)\) 则无解,如果 \(f(\dfrac{i}{x})\neq f(\dfrac{a}{y})\) 也无解。如果已经满足条件,则可以得到一对质因数的映射关系 \((x,y)\),需要判一下是否矛盾,然后按之前的做法做就行了。复杂度 \(\mathcal{O}(n)\)

点击查看代码
#include<bits/stdc++.h>
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int mod=1e9+7;
char buf[1<<21],*p1=buf,*p2=buf;
#define nc() (p1==p2&&(p2=buf+fread(p1=buf,1,1<<21,stdin),p1==p2)?-1:*p1++)
inline int read(){
    int x=0;char c=nc();
    for(;!isdigit(c);c=nc());
    for(; isdigit(c);c=nc()) x=(x<<3)+(x<<1)+(c^48);
    return x;
}
bool np[30000005];int p[3000005],f[30000005],big[30000005],d[30000005],mp1[30000005],mp2[30000005],c[30000005],c1[30000005],c2[30000005],jc[30000005];
signed main(){
	int n=read(),ans=1,tot=0;
	jc[0]=1;for(int i=1;i<=n;i++)jc[i]=1ll*jc[i-1]*i%mod;
	big[1]=1,f[1]=1,c[1]=1;
	for(int i=2;i<=n;i++){
		if(!np[i])f[i]=i,p[++tot]=i,big[i]=i,c[i]=1;
		for(int j=1;j<=tot&&i*p[j]<=n;j++){
			np[i*p[j]]=1;big[i*p[j]]=max(big[i],p[j]);
			if(i%p[j]==0){f[i*p[j]]=f[i];c[i*p[j]]=c[i];break;}
			f[i*p[j]]=f[i]*p[j],c[i*p[j]]=c[i]+1;
		}
	}
	for(int i=1;i<=n;i++)d[i]=((i==1)?1:n/i);
	int flag=1;
	for(int i=1;i<=n;i++){
		int a=read();flag&=(a==i);if(!a)continue;
		int x=big[i],y=big[a];
		if(c[i]!=c[a]||1ll*f[i]*y!=1ll*f[a]*x||d[x]!=d[y])return puts("0"),0;
		if((mp1[x]&&mp1[x]!=y)||(mp2[y]&&mp2[y]!=x))return puts("0"),0;
		if(!mp1[x]&&!mp2[y])c2[d[x]]--;
		c1[f[a]]--;mp1[x]=y,mp2[y]=x;
	}
	if(flag)return puts("1"),0;
	for(int i=1;i<=n;i++)c1[f[i]]++;
	for(int i=1;i<=n;i++)ans=1ll*ans*jc[c1[i]]%mod;
	for(int i=1;i<=n;i++)if(!np[i])c2[d[i]]++;
	for(int i=1;1ll*i*i<=n;i++)ans=1ll*ans*jc[c2[i]]%mod;
	printf("%d\n",ans);
	return 0;
}

C0459 【0112 A组】模拟测试

A 【0112A组】bracket

P9266 [PA 2022] Nawiasowe podziały

B 【0112 A组】lis

考虑优化。注意到如果我们把 \(f_i\) 相同的分成一组,一组内是 \(\{i_1,i_2\ldots i_k\}\),那么 \(a_{i_1}\ge a_{i_2}\ge \ldots\ge a_{i_k}\),所以一个点连边范围是一个区间。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,m,ans,a[1000005],c[1000005],f[1000005];vector<int>t[1000005];
void add(int x,int v){for(;x<=n;x+=x&-x)c[x]=max(c[x],v);}
int ask(int x){int res=0;for(;x;x-=x&-x)res=max(res,c[x]);return res;}
int dfs(int k,int u){
	if(k==m)return 1;
	while(!t[k+1].empty()){
		int v=t[k+1].back();t[k+1].pop_back();
		if(u<v&&a[u]<a[v]&&dfs(k+1,v))return 1;
		if(u<v&&a[u]>=a[v]){t[k+1].push_back(v);break;}
	}
	return 0;
}
signed main(){
	n=read();for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1;i<=n;i++)f[i]=ask(a[i]-1)+1,add(a[i],f[i]),m=max(m,f[i]);
	for(int i=n;i>=1;i--)t[f[i]].push_back(i);
	while(!t[1].empty())ans+=dfs(1,t[1].back()),t[1].pop_back();
	printf("%d\n",n-ans);
	return 0;
}

C 【0112 A组】cut

C0462 【0116 A组】模拟测试

A 【0116 A组】不休陀螺

注意到一个区间 \([l,r]\) 合法当且仅当 \(\sum_ib_i-a_i\ge 0\),且 \(\forall i\in[l,r],E+\sum_{j\neq i}\min\{0,b_j-a_j\}\ge a_i\)。注意到如果我们确定了 \(l\),且 \([l,k]\) 已经不合法,那么 \([l,k+1]\) 也不合法。赛时傻逼了直接写了个分治,多带一个 \(\log\),也能过。实际上可以直接扫一遍求合法右端点范围,这就是 \(\mathcal{O}(n\log n)\) 的了。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,E,tot,ans,a[1000005],b[1000005],A[1000005],mx[1000005],B[1000005],s1[1000005],s2[1000005];
struct BIT{
	int c[1000005];
	void add(int x,int v){
		for(;x<=tot;x+=x&-x)c[x]+=v;
	}
	int ask(int x){
		int res=0;
		for(;x;x-=x&-x)res+=c[x];
		return res;
	}
	int ask(int l,int r){
		if(l>r)return 0;
		return ask(r)-ask(l-1);
	}
}Tr;
void solve(int l,int r){
	if(l>r)return;
	if(l==r){
		ans+=(a[l]<=b[l]&&a[l]<=E);
		return;
	}
	int mid=(l+r)>>1;
	solve(l,mid),solve(mid+1,r);
	mx[mid]=A[mid];
	for(int i=mid-1;i>=l;i--)mx[i]=max(mx[i+1],A[i]);
	mx[mid+1]=A[mid+1];
	for(int i=mid+2;i<=r;i++)mx[i]=max(mx[i-1],A[i]);
	int i=l,j=mid+1;
	while(i<=mid){
		while(j<=r&&s1[j]-s1[i-1]>=max(mx[i],mx[j]))Tr.add(s2[j],1),j++;
		ans+=Tr.ask(s2[i-1],tot),i++;
	}
	while(j>mid+1)j--,Tr.add(s2[j],-1);
}
signed main(){
	n=read(),E=read();
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1;i<=n;i++)b[i]=read();
	for(int i=1;i<=n;i++)A[i]=a[i]-E+min(0ll,b[i]-a[i]);
	for(int i=1;i<=n;i++)s1[i]=s1[i-1]+min(0ll,b[i]-a[i]);
	for(int i=1;i<=n;i++)s2[i]=s2[i-1]+b[i]-a[i];
	for(int i=0;i<=n;i++)B[++tot]=s2[i];
	sort(B+1,B+tot+1);tot=unique(B+1,B+tot+1)-B-1;
	for(int i=0;i<=n;i++)s2[i]=lower_bound(B+1,B+tot+1,s2[i])-B;
	solve(1,n);printf("%lld\n",ans);
	return 0;
}

B 【0116 A组】计算

首先当 \(a,b>0\)\(\gcd(m^a-1,m^b-1)+1=m^{\gcd(a,b)}\)。证明就是令 \(a\ge b\)\(\gcd(m^a-1,m^b-1)=\gcd(m^a-m^b,m^b-1)=\gcd(m^b(m^{a-b}-1),m^b-1)\),因为 \(\gcd(x,x-1)=1\)\(\gcd(m^b(m^{a-b}-1),m^b-1)=\gcd(m^{a-b}-1,m^b-1)\),相当于在指数上更损相减。

\(L=m^{\gcd(a,b)}+1,R=m^{\gcd(c,d)}\)。注意到 \(L\bmod m=1,R\bmod m=0\),所以记 n=R-L+1,令 L=1,R=n 和原问题是等价的。令 \(f(x)=\prod_{i=1}^n(1+x^i)\),所求即为 \(\sum_i[m|i]f(x)[x^i]\)

考虑单位根反演:

\[\begin{aligned} \sum_i[m|i]f(x)[x^i]&=\sum_i\dfrac{1}{m}\sum\limits_{j=0}^{m-1}\omega_m^{ij}f(x)[x^i]\\ &=\dfrac{1}{m}\sum\limits_{j=0}^{m-1}\sum_i(\omega_m^j)^if(x)[x^i]\\ &=\dfrac{1}{m}\sum\limits_{j=1}^{m}f(\omega_m^j)\\ \end{aligned} \]

注意到,当 \(\gcd(j,m)=1\) 时,对 \(i\in[0,m-1]\)\(\{ij\bmod m\}=\{0,1\ldots m-1\}\),则 \(f(\omega_m^j)=(\prod\limits_{i=0}^{m-1}(1+\omega_m^i))^{\frac{n}{m}}\)。注意到如果对 \(x^m-1\) 进行因式分解有 \(x^m-1=(x-\omega_m^0)(x-\omega_m^1)\ldots(x-\omega_m^{m-1})\),代入 \(x=-1\)

  • \(m\bmod 2=0\)\(0=(-1-\omega_m^0)(-1-\omega_m^1)\ldots(-1-\omega_m^{m-1})=(1+\omega_m^0)(1+\omega_m^1)\ldots(1+\omega_m^{m-1})\)\(f(\omega_m^j)=0\)

  • \(m\bmod 2=1\)\(-2=(-1-\omega_m^0)(-1-\omega_m^1)\ldots(-1-\omega_m^{m-1})=-(1+\omega_m^0)(1+\omega_m^1)\ldots(1+\omega_m^{m-1})\)\(f(\omega_m^j)=2^{\frac{n}{m}}\)

\(\gcd(j,m)=g>1\) 时,类似的,可以得到 \(f(\omega_m^j)=[\frac{m}{g}\bmod 2=1]2^{ng/m}\)。直接算是 \(\mathcal{O}(m+Tm\log n)\) 的,考虑优化。\(\sum\limits_{j=1}^{m}[\frac{m}{g}\bmod 2=1]2^{ng/m}=\sum\limits_{g|m}\varphi(\frac{m}{g})[\frac{m}{g}\bmod 2=1]2^{ng/m}\),复杂度 \(\mathcal{O}(m+T(\sqrt{m}+d(m)\log n))\),随便过。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,M=1e7,mod=998244353; 
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int qpow(int b,int p){
	int res=1;
	for(;p;p>>=1,b=b*b%mod)if(p&1ll)res=res*b%mod;
	return res;
}
int tot,p[1000005],np[10000005],phi[10000005];
int F(int m,int a,int b){
	if(a==0||b==0)return 0;
	int c=__gcd(a,b),res=1;
	for(int i=1;i<=c;i++)res=res*m;
	return res;
}
void solve(){
	int m=read(),a=read(),b=read(),c=read(),d=read(),n=F(m,c,d)-F(m,a,b),ans=0;
	for(int i=1;i*i<=m;i++){
		if(m%i==0){
			if((m/i)%2==1)ans=(ans+phi[m/i]*qpow(2,n/m*i)%mod)%mod;
			if(m/i!=i&&i%2==1)ans=(ans+phi[i]*qpow(2,n/i)%mod)%mod;
		}
	}
	printf("%lld\n",ans*qpow(m,mod-2)%mod);
}
signed main(){
	phi[1]=1;
	for(int i=2;i<=M;i++){
		if(!np[i])p[++tot]=i,phi[i]=i-1;
		for(int j=1;j<=tot&&i*p[j]<=M;j++){
			np[i*p[j]]=1;
			if(i%p[j]==0){phi[i*p[j]]=phi[i]*p[j];break;}
			else phi[i*p[j]]=phi[i]*(p[j]-1);
		}
	}
	int T=read();
	while(T--){
		solve();
	}
	return 0;
}

C 【0116 A组】染色

牛牛题。注意到如果染了一个点,会受影响的点如下

称这个为一个大小为 1 的十字。不妨继续在这五个点上染大小为 1 的十字,发现会得到这样一个十字

称这个为一个大小为 2 的十字。重复上述的过程,可以得到大小分别为 \(3,4\ldots n-1\) 的十字。注意到大小为 \(n-1\) 的十字的外侧四个点此时距离会超过 \(2^n\),刚好互相抵消,于是我们就得到了给一个点染色的方案。

如果直接模拟上述过程复杂度是 \(\mathcal{O}((2^n)^2T(n))\) 的,其中 \(T(n)=5T(n-1)+\mathcal{O}(1)\),大概是 \(\mathcal{O}(20^n)\) 的?

考虑优化。注意到一个点如果被染偶数次等于不染,考虑枚举 \(k\),维护 \(b_{i,j}\) 表示 \((i,j)\) 这个位置是否需要染一个大小为 \(k\) 的十字,复杂度 \(\mathcal{O}(5n4^n)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int a[2055][2055],b[2055][2055];
signed main(){
	int n=read(),N=(1<<n);
	for(int i=0;i<N;i++)for(int j=0;j<N;j++)a[i][j]=read();
	for(int w=N/2;w>=2;w>>=1){
		for(int i=0;i<N;i++)for(int j=0;j<N;j++){
			if(a[i][j]){
				b[i][j]^=1;
				b[(i-w/2+N)%N][j]^=1,b[(i+w/2+N)%N][j]^=1;
				b[i][(j-w/2+N)%N]^=1,b[i][(j+w/2+N)%N]^=1;				
			}
		}
		for(int i=0;i<N;i++)for(int j=0;j<N;j++)a[i][j]=b[i][j],b[i][j]=0;
	}
	int ans=0;for(int i=0;i<N;i++)for(int j=0;j<N;j++)if(a[i][j])ans++;
	printf("%d\n",ans);for(int i=0;i<N;i++)for(int j=0;j<N;j++)if(a[i][j])printf("%d %d\n",i,j);
	return 0;
}

其实还有一个邪道 \(\mathcal{O}(\dfrac{(2^n)^3}{w})\) 做法,大概就是注意到我们只需要知道前两行哪些点需要染色,就可以递推整个矩阵的答案。所以直接把前两行当成未知数,表示出其余的位置,大力高斯消元即可。时间上可能有点卡常,但应该能过。

C0464 【0118 A组】模拟测试

一道不会,这就是 zjoi 的实力吗。

A 【0118 A组】破烂森林

“感觉这个 \(n\le 500\) 就是行列式啊,随便推一下就过大样例了,我也不会证啊。”——hfy

结论题。记原图邻接矩阵为 \(A\),原图出度矩阵为 \(B\),其中 \(B_{i,j}=[i=j]out_i\),注意到 \(\det(A+B)\) 即为所求。证明看不懂,似乎需要用到类似矩阵树定理的证法。复杂度 \(\mathcal{O}(n^3)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int a[505][505];
signed main(){
	int n=read(),m=read(),P=read();
	for(int i=1,u,v;i<=m;i++){
		u=read(),v=read(),a[u][v]++,a[u][u]++;
	}
	int ans=1;
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++){
			while(a[i][i]){
				int l=a[j][i]/a[i][i];
				for(int k=i;k<=n;k++)a[j][k]=((a[j][k]-a[i][k]*l)%P+P)%P;
				swap(a[i],a[j]),ans=-ans;
			}
			swap(a[i],a[j]),ans=-ans;
		}
	}
	if(ans<0)ans+=P;
	for(int i=1;i<=n;i++)ans=ans*a[i][i]%P;	
	printf("%lld\n",ans);
	return 0;
}

B 【0118 A组】有限生灵

逆天题,需要用到 P4723 【模板】常系数齐次线性递推,会不了一点。

C 【0118 A组】祖宗之法

大 ds,不会。

C0465 【0119 A组】模拟测试

只会找规律。

A 【0119 A组】币

注意力比较集中的同学可以发现一个 \((n,k)\) 的答案就是 \((n-2k+1)(\dbinom{n}{k}-\dbinom{n}{k-1})\),时间复杂度 \(\mathcal{O}(n)\)。为什么呢?小编也很惊讶,但事实就是这样。

有理有据的证明:卡特兰数的自卷积

点击查看代码
#include<bits/stdc++.h>
#define int long long
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e18,mod=998244353;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int jc[1000005],iv[1000005],ij[1000005];
int C(int n,int m){
	if(n<0||m<0||n-m<0)return 0;
	return jc[n]*ij[m]%mod*ij[n-m]%mod;
}
signed main(){
	int tp=read();
	if(tp==1){
		int n=read(),k=read();
		jc[0]=1;for(int i=1;i<=n;i++)jc[i]=jc[i-1]*i%mod;
		iv[1]=1;for(int i=2;i<=n;i++)iv[i]=mod-(mod/i)*iv[mod%i]%mod;
		ij[0]=1;for(int i=1;i<=n;i++)ij[i]=ij[i-1]*iv[i]%mod;
		printf("%lld\n",(C(n,k)-C(n,k-1)+mod)%mod*(n+1-2*k)%mod);		
	}
	else{
		int n=read();
		jc[0]=1;for(int i=1;i<=n;i++)jc[i]=jc[i-1]*i%mod;
		iv[1]=1;for(int i=2;i<=n;i++)iv[i]=mod-(mod/i)*iv[mod%i]%mod;
		ij[0]=1;for(int i=1;i<=n;i++)ij[i]=ij[i-1]*iv[i]%mod;
		int ans=0;
		for(int k=0,pw=1;k<=n/2;k++,pw=pw*233%mod)ans=(ans+(C(n,k)-C(n,k-1)+mod)%mod*(n+1-2*k)%mod*pw%mod)%mod;
		printf("%lld\n",ans);		
	}
	return 0;
}

B 【0119 A组】重庆北站

gym104060G. Meet in the Middle

两棵树看起来就很无敌的样子,考虑通过一些手段让它变成一棵树。最开始想的是树分治,但是不太会比较能过的复杂度。注意到对于一个询问 \((a,b)\),如果我们能预处理出所有的 \(\text{dist}_2(*,b)\),在第一棵树上给每个点 \(i\) 下面挂一个距离为 \(\text{dist}_2(i,b)\) 的虚点 \(i'\),那么此时答案即为第一棵树上 \(a\) 距离最远的点的距离。

注意到有一个经典结论,就是最远距离一定可以在直径的一个端点上取到。套路的,线段树维护区间直径,但是距离变化怎么维护?考虑离线,把询问挂到 \(b\) 上,注意到当 \(b\) 从父亲走到儿子时只会使得 dfs 序(第二棵树上)在一段区间内的点的 \(\text{dist}_2(*,b)\) 区间加,直接维护即可。复杂度 \(\mathcal{O}(n\log n+q)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct edge{
	int v,w,nxt;
};
struct Graph{
	int tot,head[100005];edge e[200005];
	void add(int u,int v,int w){
		e[++tot]=(edge){v,w,head[u]},head[u]=tot;
	}
	int cur,dfn[100005],rnk[100005],d[100005],f[22][100005],siz[100005];
	int getmin(int x,int y){
		return ((dfn[x]<dfn[y])?x:y);
	}
	void dfs(int u,int fa){
		dfn[u]=++cur,rnk[cur]=u,f[0][cur]=fa;siz[u]=1;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v,w=e[i].w;if(v==fa)continue;
			d[v]=d[u]+w;dfs(v,u);siz[u]+=siz[v];
		}
	}
	int getlca(int u,int v){
		if(u==v)return u;
		if((u=dfn[u])>(v=dfn[v]))swap(u,v);
		int o=__lg(v-u++);
		return getmin(f[o][u],f[o][v-(1ll<<o)+1]);
	}
	void init(){
		dfs(1,0);
		for(int j=1;(1ll<<j)<=cur;j++){
			for(int i=1;i+(1ll<<j)-1<=cur;i++){
				f[j][i]=getmin(f[j-1][i],f[j-1][i+(1ll<<(j-1))]);
			}
		}
	}
	int dist(int u,int v){
		return d[u]+d[v]-d[getlca(u,v)]*2;
	}
}t1,t2;
struct Chain{
	int u,v,du,dv,d;
	Chain(const int &_u=0,const int &_v=0,const int &_du=0,const int &_dv=0,const int &_d=0):u(_u),v(_v),du(_du),dv(_dv),d(_d){}
	bool operator <(const Chain &b)const{
		return d<b.d;
	}
	Chain operator +(const Chain &b)const{
		Chain c=max(*this,b);
		if(u&&b.u)c=max(c,Chain(u,b.u,du,b.du,du+b.du+t1.dist(u,b.u)));
		if(u&&b.v)c=max(c,Chain(u,b.v,du,b.dv,du+b.dv+t1.dist(u,b.v)));
		if(v&&b.u)c=max(c,Chain(v,b.u,dv,b.du,dv+b.du+t1.dist(v,b.u)));
		if(v&&b.v)c=max(c,Chain(v,b.v,dv,b.dv,dv+b.dv+t1.dist(v,b.v)));		
		return c;
	}
};
struct segtree{
	#define ls (p<<1)
	#define rs (p<<1|1)
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	Chain c[400005];int tag[400005];
	void pushup(int p){
		c[p]=c[ls]+c[rs];
	}
	void pushdown(int p){
		tag[ls]+=tag[p],tag[rs]+=tag[p];
		if(c[ls].u)c[ls].du+=tag[p];
		if(c[ls].v)c[ls].dv+=tag[p];
		if(c[ls].u&&c[ls].v)c[ls].d+=2*tag[p];
		if(c[rs].u)c[rs].du+=tag[p];
		if(c[rs].v)c[rs].dv+=tag[p];
		if(c[rs].u&&c[rs].v)c[rs].d+=2*tag[p];	
		tag[p]=0;
	}
	void build(int l,int r,int p){
		tag[p]=0;
		if(l==r){
			c[p]=Chain(t2.rnk[l],0,t2.d[t2.rnk[l]],0,0);
			return;
		}
		int mid=(l+r)>>1;
		build(lson);build(rson);
		pushup(p);
	}
	void add(int l,int r,int p,int L,int R,int v){
		if(L>R)return;
		if(L<=l&&r<=R){
			tag[p]+=v;
			if(c[p].u)c[p].du+=v;
			if(c[p].v)c[p].dv+=v;
			if(c[p].u&&c[p].v)c[p].d+=2*v;
			return;
		}
		int mid=(l+r)>>1;pushdown(p);
		if(L<=mid)add(lson,L,R,v);
		if(R>mid)add(rson,L,R,v);
		pushup(p);
	}
	#undef lson
	#undef rson
	#undef ld
	#undef rs
}Tr;
int T,n,q,qu[500005],qv[500005],ans[500005];vector<int>t[100005];
void solve(int u,int fa){
	Chain l=Tr.c[1];
	for(auto x:t[u]){
		if(l.u)ans[x]=max(ans[x],t1.dist(qu[x],l.u)+t2.dist(l.u,qv[x]));
		if(l.v)ans[x]=max(ans[x],t1.dist(qu[x],l.v)+t2.dist(l.v,qv[x]));		
	}
	for(int i=t2.head[u];i;i=t2.e[i].nxt){
		int v=t2.e[i].v,w=t2.e[i].w;if(v==fa)continue;
		Tr.add(1,n,1,t2.dfn[v],t2.dfn[v]+t2.siz[v]-1,-w);
		Tr.add(1,n,1,1,t2.dfn[v]-1,w);
		Tr.add(1,n,1,t2.dfn[v]+t2.siz[v],n,w);
		solve(v,u);
		Tr.add(1,n,1,t2.dfn[v],t2.dfn[v]+t2.siz[v]-1,w);
		Tr.add(1,n,1,1,t2.dfn[v]-1,-w);
		Tr.add(1,n,1,t2.dfn[v]+t2.siz[v],n,-w);
	}
}
signed main(){
	T=read(),n=read(),q=read();
	for(int i=1,u,v,w;i<n;i++)u=read(),v=read(),w=read(),t1.add(u,v,w),t1.add(v,u,w);
	for(int i=1,u,v,w;i<n;i++)u=read(),v=read(),w=read(),t2.add(u,v,w),t2.add(v,u,w);
	t1.init();t2.init();Tr.build(1,n,1);
	for(int i=1;i<=q;i++)qu[i]=read(),qv[i]=read(),t[qv[i]].push_back(i);
	solve(1,0);for(int i=1;i<=q;i++)printf("%lld\n",ans[i]); 
	return 0;
}

C 【0119 A组】等腰三角形

sto Eray orz

先不看修改。考虑先把所有长度相同的棍子两两配对,假设可以配 \(p\) 对。注意到如果只使用这些棍子拼等腰三角形,它们总是能“完全”用完,即一定能拼出 \(\lfloor\dfrac{2p}{3}\rfloor\) 个。证明就是你考虑选出长度最大的 \(\lfloor\dfrac{2p}{3}\rfloor\) 对棍子,用剩下的做底边,容易发现这一定是可以做到的,且这么做数量也一定达到了上界。

于是现在只需要对所有只剩一根的棍子尝试去和一对棍子匹配,假设可以匹配 \(k\) 个,答案就是 \(k+\lfloor\dfrac{2(p-k)}{3}\rfloor\)。注意到一对长度为 \(len\) 的棍子可以匹配一根长度在 \([1,2len)\) 范围内的底边,这是一段前缀。所以我们按前缀大小从小到大满足条件即可。

注意到这个过程非常像括号匹配。具体地,如果有一根单独的长为 \(i\) 的棍子,我们在 \(i\) 处加一个左括号。如果有 \(t\) 对长为 \(j\) 的棍子,我们在 \(2j-1\) 处加 \(t\) 个右括号,求匹配括号数即可。加上修改后可以用线段树简单维护,复杂度 \(\mathcal{O}((n+q)\log n)\)。注意同一个位置可以同时存在左括号和右括号,可以自己匹配自己。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define L(x) ((x)<<1)
#define R(x) ((x)<<1|1)
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
struct segtree{
	#define ls (p<<1)
	#define rs (p<<1|1)
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	struct Node{
		int l,r,c;
	}c[3200005];
	void pushup(int p){
		int tmp=min(c[ls].l,c[rs].r);
		c[p].c=c[ls].c+c[rs].c+tmp;
		c[p].l=c[ls].l+c[rs].l-tmp;
		c[p].r=c[ls].r+c[rs].r-tmp;
	}
	void build(int l,int r,int p){
		if(l==r){
			c[p]=(Node){0,0,0};
			return;
		}
		int mid=(l+r)>>1;
		build(lson);build(rson);
		pushup(p);
	}
	void upd(int l,int r,int p,int x,int vl,int vr){
		if(l==r){
			c[p]=(Node){vl,vr,0};
			return;
		}
		int mid=(l+r)>>1;
		if(x<=mid)upd(lson,x,vl,vr);
		else upd(rson,x,vl,vr);
		pushup(p);
	}
	void add(int l,int r,int p,int x,int vl,int vr){
		if(l==r){
			c[p].l+=vl,c[p].r+=vr;
			return;
		}
		int mid=(l+r)>>1;
		if(x<=mid)add(lson,x,vl,vr);
		else add(rson,x,vl,vr);
		pushup(p);
	}
	#undef lson
	#undef rson
	#undef ls
	#undef rs
}Tr;
int c[200005];
signed main(){
	int n=read(),q=read(),p=0;
	for(int i=1;i<=n;i++)c[i]=read(),p+=c[i]/2;
	Tr.build(1,4*n,1);
	for(int i=1;i<=n;i++){
		if(c[i]%2==1)Tr.add(1,4*n,1,L(i),1,0);
		Tr.add(1,4*n,1,R(2*i-1),0,(c[i]/2));
	}
	while(q--){
		int x=read(),y=read();p-=c[x]/2;
		if(c[x]%2==1)Tr.add(1,4*n,1,L(x),-1,0);
		Tr.add(1,4*n,1,R(2*x-1),0,-(c[x]/2));
		c[x]+=y,p+=c[x]/2;
		if(c[x]%2==1)Tr.add(1,4*n,1,L(x),1,0);
		Tr.add(1,4*n,1,R(2*x-1),0,(c[x]/2));
		int k=Tr.c[1].c;printf("%lld\n",k+(2*(p-k)/3));
	}
	return 0;
}

高一高二联合省选模拟赛19「重庆市友谊赛」

A. 树

3261. 二次求和

考虑求出每个点成为一条长度在 \([L,R]\) 间的路径上的点的次数,这个可以点分治一下,然后修改就是求路径和了。复杂度 \(\mathcal{O}(n\log^2 n+q)\),瓶颈在于点分治的 sort。\(L=1\) 时如果写的比较丑可能需要特殊处理一下。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e18,mod=1e9+7,i2=(mod+1)/2;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
vector<int>g[100005];int siz[100005],vis[100005];
void dfssize(int u,int fa){
	siz[u]=1;
	for(auto v:g[u])if(!vis[v]&&v!=fa)dfssize(v,u),siz[u]+=siz[v];
}
int root,mx[100005];
void dfsroot(int u,int fa,int s){
	mx[u]=-inf;
	for(auto v:g[u])if(!vis[v]&&v!=fa)dfsroot(v,u,s),mx[u]=max(mx[u],siz[v]);
	mx[u]=max(mx[u],s-siz[u]);
	if(mx[root]>mx[u])root=u;
}
int n,m,L,R,num,a[100005],dep[100005],bel[100005];
void dfsdep(int u,int fa,int id){
	a[++num]=u,bel[u]=id;
	for(auto v:g[u])if(!vis[v]&&v!=fa)dep[v]=dep[u]+1,dfsdep(v,u,id);
}
int d[100005],cnt[100005];
void calc(int u,int fa){
	for(auto v:g[u])if(!vis[v]&&v!=fa)calc(v,u),d[u]=(d[u]+d[v])%mod;
}
int B[100005];
void sol(int u){
	num=0;dep[u]=0,a[++num]=u,bel[u]=u;int all=0;
	for(auto v:g[u])if(!vis[v])dep[v]=dep[u]+1,dfsdep(v,u,v);
	sort(a+1,a+num+1,[&](int x,int y){
		return dep[x]<dep[y];
	});
	for(int i=1;i<=num;i++)B[bel[a[i]]]++;
	for(int i=1,j=num;i<=num;i++){
		while(j>=1&&dep[a[i]]+dep[a[j]]+1>R)B[bel[a[j]]]--,j--;
		d[a[i]]=(d[a[i]]+j-B[bel[a[i]]])%mod;all=(all+j-B[bel[a[i]]])%mod;
	}
	cnt[u]=(cnt[u]+all*i2%mod)%mod;
	calc(u,0);all=0;
	for(int i=1;i<=num;i++)if(a[i]!=u)cnt[a[i]]=(cnt[a[i]]+d[a[i]])%mod;
	for(int i=1;i<=num;i++)B[bel[a[i]]]=0,d[a[i]]=0;
	for(int i=1;i<=num;i++)B[bel[a[i]]]++;
	for(int i=1,j=num;i<=num;i++){
		while(j>=1&&dep[a[i]]+dep[a[j]]+1>L-1)B[bel[a[j]]]--,j--;
		d[a[i]]=(d[a[i]]+j-B[bel[a[i]]])%mod;all=(all+j-B[bel[a[i]]])%mod;
	}
	cnt[u]=(cnt[u]-all*i2%mod+mod)%mod;
	calc(u,0);
	for(int i=1;i<=num;i++)if(a[i]!=u)cnt[a[i]]=(cnt[a[i]]-d[a[i]]+mod)%mod;
	for(int i=1;i<=num;i++)B[bel[a[i]]]=0,d[a[i]]=0;
}
void dfz(int u){
	vis[u]=1;sol(u);
	for(auto v:g[u])if(!vis[v]){
		root=0,dfssize(v,0),dfsroot(v,0,siz[v]),dfz(root);
	}
}
int cur,dfn[100005],f[20][100005],sum[100005],val[100005];
int getmin(int x,int y){
	return ((dfn[x]<dfn[y])?x:y);
}
void dfslca(int u,int fa){
	dfn[u]=++cur,f[0][cur]=fa;sum[u]=(sum[fa]+cnt[u])%mod;
	for(auto v:g[u])if(v!=fa)dfslca(v,u);
}
int getlca(int u,int v){
	if(u==v)return u;
	if((u=dfn[u])>(v=dfn[v]))swap(u,v);
	int o=__lg(v-u++);
	return getmin(f[o][u],f[o][v-(1ll<<o)+1]);
}
signed main(){
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
	int T=1;
	while(T--){
		n=read(),m=read(),L=read(),R=read();
		for(int i=1;i<=n;i++)val[i]=read();
		for(int i=2,p;i<=n;i++)p=read(),g[i].push_back(p),g[p].push_back(i);
		mx[0]=inf,root=0,dfssize(1,0),dfsroot(1,0,siz[1]),dfz(root);
		if(L==1){for(int i=1;i<=n;i++)cnt[i]=(cnt[i]+1)%mod;}
		dfslca(1,0);
		for(int j=1;(1ll<<j)<=cur;j++){
			for(int i=1;i+(1ll<<j)-1<=cur;i++){
				f[j][i]=getmin(f[j-1][i],f[j-1][i+(1ll<<(j-1))]);
			}
		}
		int ans=0;
		for(int i=1;i<=n;i++)ans=(ans+cnt[i]*val[i]%mod)%mod;
		while(m--){
			int u=read(),v=read(),k=read();
			ans=(ans+((sum[u]+sum[v]-2*sum[getlca(u,v)]+cnt[getlca(u,v)])%mod+mod)%mod*k%mod)%mod;
			printf("%lld\n",ans);
		}
		cur=0;for(int i=1;i<=n;i++)g[i].clear(),cnt[i]=vis[i]=0;
	} 
	return 0;
}

B. 图

神秘巨大恶心 dp,不会。

C. 序列

常系数齐次线性递推。

高一高二联合省选模拟赛20

A. 魔法师

注意到 9 以内只有 2,3,5,7 四个质数,考虑先数位 dp,对所有 \((a,b,c,d)\) 求数位积为 \(2^a3^b5^c7^d\) 的数的个数,然后枚举 gcd 为 \((x,y,z,w)\),那么需要满足这两个数质因子次数最小值必须等于 gcd 的。考虑枚举两个数和 gcd 次数相同的位置,其他位置都需要大于,这个高维后缀和求一下就行了,容斥即可求方案。复杂度大概是 \(\mathcal{O}(24\log^5 n+16^2\log^4 n)\)

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define popc(x) (__builtin_popcount(x))
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e9,mod=998244353;
const int d2[]={0,0,1,0,2,0,1,0,3,0};
const int d3[]={0,0,0,1,0,0,1,0,0,2};
const int d5[]={0,0,0,0,0,1,0,0,0,0};
const int d7[]={0,0,0,0,0,0,0,1,0,0};
inline ll read(){
	ll x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
ll n,k;int len,n2,n3,n5,n7,a[55],f[2][2][2][62][42][22][22];
int check(int c2,int c3,int c5,int c7){
	ll tmp=1;
	while(c2--){
		tmp=tmp*2;
		if(tmp>k)return 0;
	}
	while(c3--){
		tmp=tmp*3;
		if(tmp>k)return 0;
	}
	while(c5--){
		tmp=tmp*5;
		if(tmp>k)return 0;
	}
	while(c7--){
		tmp=tmp*7;
		if(tmp>k)return 0;
	}
	return (tmp<=k);
}
inline void add(int &x,int y){
	x+=y;if(x>=mod)x-=mod;
}
inline void sub(int &x,int y){
	x-=y;if(x<0)x+=mod;
}
int s[62][42][22][22];bool t[62][42][22][22];
int dfs(int c2,int c3,int c5,int c7){
	if(c2>=n2+1||c3>=n3+1||c5>=n5+1||c7>=n7+1)return 0;
	if(t[c2][c3][c5][c7])return s[c2][c3][c5][c7];
	int res=0;
	for(int i=1;i<(1<<4);i++){
		int C2=c2,C3=c3,C5=c5,C7=c7;
		if((i>>0)&1)C2++;
		if((i>>1)&1)C3++;
		if((i>>2)&1)C5++;
		if((i>>3)&1)C7++;
		if(popc(i)&1)add(res,dfs(C2,C3,C5,C7));
		else sub(res,dfs(C2,C3,C5,C7));
	}
	add(res,f[1][0][0][c2][c3][c5][c7]);
	add(res,f[1][1][0][c2][c3][c5][c7]);
	t[c2][c3][c5][c7]=1;
	return s[c2][c3][c5][c7]=res;
}
int calc(int c2,int c3,int c5,int c7,int msk){
	if(!((msk>>0)&1))c2++;
	if(!((msk>>1)&1))c3++;
	if(!((msk>>2)&1))c5++;
	if(!((msk>>3)&1))c7++;
	int res=0;
	for(int i=0;i<(1<<4);i++){
		if((i|msk)!=msk)continue;
		int C2=c2,C3=c3,C5=c5,C7=c7;
		if((i>>0)&1)C2++;
		if((i>>1)&1)C3++;
		if((i>>2)&1)C5++;
		if((i>>3)&1)C7++;
		if(popc(i)&1)sub(res,dfs(C2,C3,C5,C7));
		else add(res,dfs(C2,C3,C5,C7));
	}
	return res;
}
int val[25];
signed main(){
	n=read(),k=read();
	ll N=n;while(N)a[++len]=N%10,N/=10;
	n2=len*3,n3=len*2,n5=len,n7=len;
	f[(len+1)&1][1][1][0][0][0][0]=1;
	for(int p=len;p>=1;p--){
		for(int lim=0;lim<2;lim++)for(int z=0;z<2;z++){
			for(int c2=0;c2<=(len-p+1)*3;c2++)for(int c3=0;c3<=(len-p+1)*2;c3++){
				for(int c5=0;c5<=(len-p+1);c5++)for(int c7=0;c7<=(len-p+1);c7++){
					f[p&1][lim][z][c2][c3][c5][c7]=0;
				}
			}
		}
		for(int lim=0;lim<2;lim++)for(int z=0;z<2;z++){
			for(int c2=0;c2<=(len-p)*3;c2++)for(int c3=0;c3<=(len-p)*2;c3++){
				for(int c5=0;c5<=(len-p);c5++)for(int c7=0;c7<=(len-p);c7++){
					for(int i=z^1;i<=9;i++){
						if(lim&&i>a[p])break;
						add(f[p&1][lim&(i==a[p])][z&(i==0)][c2+d2[i]][c3+d3[i]][c5+d5[i]][c7+d7[i]],f[(p+1)&1][lim][z][c2][c3][c5][c7]);
					}
				}
			}
		}
	}
	int ans=0;
	ll p2=1;
	for(int c2=0;c2<=n2;c2++){
		ll p3=p2;
		if(p3>k)break;
		for(int c3=0;c3<=n3;c3++){
			ll p5=p3;
			if(p5>k)break;
			for(int c5=0;c5<=n5;c5++){
				ll p7=p5;
				for(int c7=0;c7<=n7;c7++){
					if(p7>k)break;
//					if(!check(c2,c3,c5,c7))break;
					int res=0;
					for(int i=0;i<16;i++)val[i]=calc(c2,c3,c5,c7,i);
					for(int i=0;i<16;i++){
						for(int j=0;j<16;j++){
							if((i|j)!=15)continue;
							add(res,1ll*val[i]*val[j]%mod);
						}
					}
					add(ans,res);
					p7=p7*7;
				}
				p5=p5*5;
			}
			p3=p3*3;
		}	
		p2=p2*2;
	}
	printf("%d\n",ans);
	return 0;
}
/*
1000000000000000000 1000000000000000000
*/

B. Warrior

神秘做法。注意到固定左端点后随右端点增大,区间字典序递减。考虑对每个左端点维护一个右端点区间 \([l_i,r_i]\),表示 \(\forall j\in[l_i,r_i]\)\([i,j]\) 可能成为答案区间。每次随机取一个区间 \([L,R]\) 出来算它的排名,如果是 \(k\) 那就找到了,否则可以调整 \(l,r\) 的大小。随机意义下这个过程只会进行 \(\log(n^2)\) 轮。

考虑快速求排名。因为上述结论,考虑双指针,比较即在值域上看第一个不同的位置的大小关系,随便维护一下可以 \(n\log n\)。总复杂度 \(\mathcal{O}(n\log^2 n)\),可以压位 trie 进一步卡常,但是没必要。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define get(l,r) (rnd()%((r)-(l)+1)+(l))
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,k,a[100005],B[100005],l[100005],r[100005],t[100005],eq[100005],del[100005];
priority_queue<int,vector<int>,greater<int> >q;
void add(int x,int v){
	if(B[x]==0){
		if(del[x])del[x]=0;
		else q.push(x);
	}
	B[x]+=v;
	if(B[x]==0)del[x]=1;
}
int ask(){
	while(!q.empty()&&del[q.top()])del[q.top()]=0,q.pop();
	if(q.empty())return 0;
	else return B[q.top()];
}
int Find(int L,int R){
	for(int i=1;i<=n;i++)B[i]=0,del[i]=0;
	while(!q.empty())q.pop();
	for(int i=L;i<=R;i++)add(a[i],-1);
	ll c1=0,c2=0;
	for(int i=1,j=0;i<=n;i++){
		j=max(j,i-1);eq[i]=0;
		while(j+1<=n){
			j++,add(a[j],1);
			if(ask()==0){eq[i]=1;break;}
			if(ask()>0){add(a[j],-1),j--;break;}
		}
		c1+=n-j,c2+=eq[i];t[i]=j;
		if(i<=j)add(a[i],-1);
	}
	if(c1<k&&c1+c2>=k)return 1;
	if(c1<k){for(int i=1;i<=n;i++)r[i]=min(r[i],t[i]-eq[i]);}
	else{for(int i=1;i<=n;i++)l[i]=max(l[i],t[i]+1);}
	return 0;
}
signed main(){
	n=read(),k=read();mt19937 rnd(time(0));
	for(int i=1;i<=n;i++)a[i]=read(),l[i]=i,r[i]=n;
	int L,R;
	do{
		ll sum=0;
		for(int i=1;i<=n;i++)if(l[i]<=r[i])sum+=r[i]-l[i]+1;
		ll o=get(1,sum);
		for(int i=1;i<=n;i++){
			if(r[i]-l[i]+1>=o){L=i,R=o+l[i]-1;break;}
			o-=r[i]-l[i]+1;
		}
	}while(!Find(L,R));
	sort(a+L,a+R+1);for(int i=L;i<=R;i++)printf("%d ",a[i]);
	return 0;
}

其实有不需要随机化的相对常规的做法。考虑延续暴力的思路,从小到大枚举每个数,确定它在答案中出现了多少次。对每个左端点 \(i\) 维护 \([l_i,r_i]\) 表示当右端点在

C. 最佳观影

唉唉唉,感觉不难啊,考场上没认真想导致的。

考虑如果一列还没放任何座位,那么选择位置可以确定,否则因为我们会尽量让座位接近中心,所以一列已经被填满的位置形成一个区间,所以这一列只会有两种放法:放上面的空或放下面的空。以上面的空为例,记 \(p=\dfrac{k+1}{2}\),注意到 \((x,l,l+m-1,m)\) 的贡献可以写成 \(m|x-p|+\sum\limits_{i=l}^{l+m-1}|i-p|=m|x-p|+pm-\dfrac{(2l+m-1)m}{2}=(mp-\dfrac{(m-1)m}{2})+m(|x-p|-l)\),所以我们只需要比较 \(|x-p|-l\) 即可,随便维护一下就行。细节不太多,写的比较劣可能需要卡常。复杂度 \(\mathcal{O}((n+k)\log k)\)

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define A(x) array<ll,x>
using namespace std;
const ll inf=1e18;
char buf[1<<21],*p1=buf,*p2=buf;
#define nc() (p1==p2&&(p2=buf+fread(p1=buf,1,1<<21,stdin),p1==p2)?-1:*p1++)
inline int read(){
    int x=0;char c=nc();
    for(;!isdigit(c);c=nc());
    for(; isdigit(c);c=nc()) x=(x<<3)+(x<<1)+(c^48);
    return x;
}
struct segtree{	
	int d,N;A(3)c[1200005];
	void build(int n){
		d=__lg(n+5)+1,N=(1<<d)-1;
		for(int i=1;i<(1<<(d+1));i++)c[i]={inf,inf,inf};
	}
	void upd(int x,A(3)v){
		for(c[x+N]=v,x=(x+N)>>1;x>=1;x>>=1)c[x]=min(c[x<<1],c[x<<1|1]);
	}
	A(3)ask(int l,int r){
		A(3)ans={inf,inf,inf};l--,r++;
		for(l+=N,r+=N;(l^r)!=1;l>>=1,r>>=1){
			if(l%2==0)ans=min(ans,c[l^1]);
			if(r%2==1)ans=min(ans,c[r^1]);
		}
		return ans;
	}
}Tup,Tdown;
inline ll calc(int x,int l,int r,int m){
	if(r<=m)return 1ll*abs(x-m)*(r-l+1)+1ll*(r-l+1)*m-1ll*(l+r)*(r-l+1)/2;
	if(l>=m)return 1ll*abs(x-m)*(r-l+1)+1ll*(l+r)*(r-l+1)/2-1ll*(r-l+1)*m;
	return 1ll*abs(x-m)*(r-l+1)+(1ll*(m-l+1)*m-1ll*(l+m)*(m-l+1)/2)+(1ll*(m+r)*(r-m+1)/2-1ll*(r-m+1)*m);
}
priority_queue<A(3),vector<A(3)>,greater<A(3)> >sup[300005],sdown[300005];
priority_queue<A(2),vector<A(2)>,greater<A(2)> >p;
inline void print(int x){
	if(x>9)print(x/10);
	putchar(x%10+'0');
}
signed main(){
	int T=read(),k=read(),m=(k+1)/2;
	Tup.build(k);Tdown.build(k);
	for(int i=1;i<=k;i++)p.push({abs(i-m),i});
	for(int i=0;i<=k+1;i++)sup[i].push({inf,inf,inf});
	for(int i=0;i<=k+1;i++)sdown[i].push({inf,inf,inf});	
	while(T--){
		int n=read();
		if(n>k){puts("-1");continue;}
		A(3)res={inf,inf,inf},tmp1=Tup.ask(n,k),tmp2=Tdown.ask(n,k);
		if(tmp1[0]<inf)res=min(res,{calc(tmp1[1],tmp1[2]-n+1,tmp1[2],m),tmp1[1],tmp1[2]-n+1});
		if(tmp2[0]<inf)res=min(res,{calc(tmp2[1],tmp2[2],tmp2[2]+n-1,m),tmp2[1],tmp2[2]});
		if(!p.empty()){
			int u=p.top()[1];
			if(n&1ll)res=min(res,{calc(u,m-(n-1)/2,m+(n-1)/2,m),u,m-(n-1)/2});
			else res=min({res,{calc(u,m-n/2+1,m+n/2,m),u,m-n/2+1},{calc(u,m-n/2,m+n/2-1,m),u,m-n/2}});
		}
		if(res[0]>=inf){puts("-1");continue;}
		int x=res[1],l=res[2],r=l+n-1;
		print(x),putchar(' '),print(l),putchar(' '),print(r),putchar('\n');
		if(!p.empty()&&p.top()[1]==x){
			p.pop();
			if(l>1){
				sup[l-1].push({abs(x-m)-(l-1),x,l-1});
				Tup.upd(l-1,sup[l-1].top());
			}
			if(r<k){
				sdown[k-r].push({abs(x-m)+(r+1),x,r+1});
				Tdown.upd(k-r,sdown[k-r].top());
			}
		}
		else if(x==tmp1[1]&&r<m){
			sup[r].pop();Tup.upd(r,sup[r].top());
			if(l>1){
				sup[l-1].push({abs(x-m)-(l-1),x,l-1});
				Tup.upd(l-1,sup[l-1].top());
			}
		}
		else{
			sdown[k-l+1].pop();Tdown.upd(k-l+1,sdown[k-l+1].top());
			if(r<k){
				sdown[k-r].push({abs(x-m)+(r+1),x,r+1});
				Tdown.upd(k-r,sdown[k-r].top());	
			}		
		}
	}
	return 0;
}

C0472 【0129 A组】模拟测试

SNOI2024 Day2。明天是不是就考 SNOI2024 Day1 啊?

A 【0129 A组】平方数

P10063 [SNOI2024] 平方数

思考怎么刻画这个平方数的条件。一个常规的想法是分解质因数,但这个 \(10^{36}\) 的逆天范围一眼寄。考虑一些更牛牛的东西,比如二次剩余。显然我们随便取一个奇质数 \(p\),一个完全平方数一定都是二次剩余。同时你还注意到这个玩意是有可减性的捏,考虑随 60 个质数,根据后缀积是否是二次剩余压一下状态,状态相同的两个位置对应了一个合法的区间。

如果遇到 \(p\) 的倍数怎么办捏,我们把所有 \(p\) 除掉即可(这里如果要更严谨一点应该记一下每个 \(p\) 出现次数的奇偶性,类似双哈希一样做一下)。这里有约一半的概率可以求得正确答案,具体为什么我也不知道,因为 EI 也不知道。正确性比较玄学(EI Blog)。

点击查看代码
#include<bits/stdc++.h>
#define i128 __int128
#define ll long long
#define ull unsigned long long
using namespace std;
const i128 lim=1e5;
const int p[]={
	1213,757,3541,727,3823,1831,401,1823,223,2633,
	2417,2243,1481,853,1871,3919,4007,3911,4229,3833,
	3229,907,1601,4523,4957,1721,3023,911,4451,2357,
	3067,3571,2027,1289,601,11,4373,1657,2039,277,
};
inline i128 read(){
	i128 x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
inline void print(i128 x){
	if(x>9)print(x/10);
	putchar(x%10+'0');
}
#define U128 __uint128_t
struct Barrett {
    ull d;
    U128 m;
    Barrett(const ull &_d=1ull):d(_d),m(((U128)(1)<<64)/_d) {}
    ull reduce(ull x) {
        ull w=(m*x)>>64;
        w=x-w*d;
        if(w>=d) return w-d;
        return w;
    }
} mod;
ull operator%(ll x,Barrett &y) {return y.reduce(x);}
ll qpow(ll b,int p){
	ll res=1;
	for(;p;p>>=1,b=b*b%mod)if(p&1)res=res*b%mod;
	return res;
}
i128 a[300005];ull suf[300005];vector<int>v[300005];bool tag[5005]; 
signed main(){
	int n=read();i128 cnt=0;
	for(int i=1;i<=n;i++)a[i]=read(); 
	for(int k=0;k<40;k++){
		mod=Barrett(p[k]);ll pr=1;
		for(int i=1;i<p[k];i++)tag[1ll*i*i%mod]=1;
		for(int i=n;i>=1;i--){
			i128 tmp=a[i];while(tmp%p[k]==0)tmp/=p[k];
			pr=1ll*pr*(tag[tmp%p[k]]?1:p[k]-1)%mod;
			if(pr==p[k]-1)suf[i]|=(1ull<<k);
		}
		for(int i=1;i<p[k];i++)tag[1ll*i*i%mod]=0;
	}
	unordered_map<ull,int>mp;mp[0]++;
	for(int i=n;i>=1;i--){
		cnt+=mp[suf[i]],mp[suf[i]]++;
	}
	print(cnt);puts("");cnt=min(cnt,lim);
	mp.clear();int num=0;mp[0]=++num;
	v[mp[0]].push_back(n+1);
	for(int i=n;i>=1;i--){
		if(!mp[suf[i]])mp[suf[i]]=++num;
		v[mp[suf[i]]].push_back(i);
	}
	for(int i=1;i<=n&&cnt;i++){
		v[mp[suf[i]]].pop_back();
		i128 siz=(int)v[mp[suf[i]]].size(),t=min(cnt,siz);
		for(int j=siz-1;j>=siz-t;j--)printf("%d %d\n",i,v[mp[suf[i]]][j]-1),cnt--;
	}
	return 0;
}

B 【0129 A组】公交线路

P10064 [SNOI2024] 公交线路

\(\mathcal{O}(n^2)\) 做法看不懂,\(\mathcal{O}(n^2\log n)\) 做法看不懂也不会 ntt。

C 【0129 A组】字符树

P10065 [SNOI2024] 字符树

神秘科技,会不了一点。

C0473 【0130 A组】模拟测试

A 【0130 A组】体育课

注意到如果我们确定了前 \(r\) 行和前 \(c\) 列,那么整个矩阵就确定了(虽然可能不合法)。注意到因为每个 \(r\times c\) 的子矩阵的和一样,所以有 \(a_{i,j}-a_{i+r,j}=a_{i,j+c}-a_{i+r,j+c}\)

注意到如果下标从 0 开始,我们把 \((i\bmod r,j\bmod c)\) 相同的 \((i,j)\) 提出来组成一个新的矩阵 \(b_{\lfloor i/r\rfloor,\lfloor j/c\rfloor}=a_{i,j}\),则要么 \(\forall i,b_{i,0}=b_{0,0}\),要么 \(\forall j,b_{0,j}=b_{0,0}\),说人话就是 \(b\) 要么第一行全相等要么第一列全相等。证明就是考虑如果不是这样,找到一个 01 交界处,应用上面 \(a_{i,j}-a_{i+r,j}=a_{i,j+c}-a_{i+r,j+c}\) 的结论后容易发现这一定不合法。容易注意到这是整个矩阵合法的充要条件。

考虑 \(2^{rc}\) 枚举每个 \(b\) 是列相等还是行相等。令 \(c_{1,x}\) 表示第 \(x\) 行有几个行相等的,\(c_{2,x}\) 表示第 \(x\) 列有几个列相等的。

先看列相等。每一列独立,且此时其他位置行相等,对限制没有影响,我们只看这些列相等的位置。枚举这一列有几个列全为 1,答案即为 \(\prod\limits_{i=0}^{c-1}\sum\limits_{j=0}^{c_{2,i}}\dbinom{c_{2,i}}{j}^{num}\),其中 \(num=\lceil\dfrac{m-i}{c}\rceil\),表示 \(b\) 在后面有几列。

再看行相等。注意此时行相等需要减去同时满足列相等时的贡献。仿照上面的过程容斥一下即可得到答案为 \(\prod\limits_{i=0}^{r-1}\sum\limits_{j=0}^{c_{1,i}}(-1)^j2^j\sum\limits_{k=0}^{c_{1,i}-j}\dbinom{c_{1,i}-j}{k}^{num}\),其中 \(num=\lceil\dfrac{n-i}{r}\rceil\)。其中 \((-1)^j\) 是容斥系数,\(2^j\) 是表示同时满足行相等和列相等的位置要么全是 0 要么全是 1。

枚举所有情况,对上面两部分的积求和即可。精细实现复杂度可以做到 \(\mathcal{O}(2^{rc}rc)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
#define U128 __uint128_t
struct Barrett {
    ull d;
    U128 m;
    Barrett(const ull &_d=1ull):d(_d),m(((U128)(1)<<64)/_d) {}
    ull reduce(ull x) {
        ull w=(m*x)>>64;
        w=x-w*d;
        if(w>=d) return w-d;
        return w;
    }
}mod;
ull operator%(int x,Barrett &y) {return y.reduce(x);}
int n,m,r,c,P,a[5][5],c1[5],c2[5],C[5][5],p1[2][5][5],p2[2][5][5],pw[5],f1[5][5],f2[5][5];
int qpow(int b,int p){
	int res=1;
	for(;p;p>>=1,b=b*b%mod)if(p&1ll)res=res*b%mod;
	return res;
}
int sol(int msk){
	int res=1;
	for(int i=0;i<r;i++)c1[i]=0;
	for(int i=0;i<c;i++)c2[i]=0;
	for(int i=0;i<r;i++)for(int j=0;j<c;j++)a[i][j]=(msk>>(i*c+j))&1ll;
	for(int i=0;i<r;i++)for(int j=0;j<c;j++)c1[i]+=a[i][j],c2[j]+=a[i][j]^1;
	for(int i=0;i<c;i++)res=res*f2[c2[i]][i]%mod;
	for(int i=0;i<r;i++)res=res*f1[c1[i]][i]%mod;
	return res;
}
signed main(){
	n=read(),m=read(),r=read(),c=read(),P=read();mod=Barrett(P);int ans=0;
	pw[0]=1;for(int i=1;i<=4;i++)pw[i]=pw[i-1]*(P-2)%mod;
	for(int i=0;i<=4;i++){C[i][0]=1;for(int j=1;j<=i;j++)C[i][j]=C[i-1][j]+C[i-1][j-1];}
	for(int i=0;i<2;i++)for(int j=0;j<=4;j++)for(int k=0;k<=4;k++)p1[i][j][k]=qpow(C[j][k],n/r+i);
	for(int i=0;i<2;i++)for(int j=0;j<=4;j++)for(int k=0;k<=4;k++)p2[i][j][k]=qpow(C[j][k],m/c+i);
	for(int o=0;o<=c;o++)for(int i=0;i<r;i++)for(int j=0;j<=o;j++)for(int k=0;k<=o-j;k++)f1[o][i]=(f1[o][i]+pw[j]*C[o][j]%mod*p1[(i<n%r)][o-j][k]%mod)%mod;
	for(int o=0;o<=r;o++)for(int i=0;i<c;i++)for(int j=0;j<=o;j++)f2[o][i]=(f2[o][i]+p2[(i<m%c)][o][j])%mod;
	for(int msk=0;msk<(1ll<<(r*c));msk++)ans=(ans+sol(msk))%mod;
	printf("%lld\n",ans);
	return 0;
}

B 【0130 A组】优化采购

直接做很困难啊,因为每次遍历这个结构是不可避免的,也没有办法通过记录函数分段的断点来快速计算。考虑离线,把所有修改操作挂到对应节点上,在时间轴上开线段树,维护子树内答案,合并的时候线段树合并即可。具体地,考虑怎么维护区间加以及区间取与某个数 \(v\) 的差的绝对值。考虑区间维护最大值最小值 \(mx,mn\) 以及区间加 tag 和区间乘 -1 tag。如果 \(v\le mn\)\(v\ge mx\) 就可以直接打标记了,否则我们递归下去处理。注意到一次递归至少会让子树内极差减小 1,每次区间加也只会让 \(\mathcal{O}(\log q)\) 个点的极差增大,势能分析一下时间复杂度 \(\mathcal{O}((n+q)L\log q)\),空间复杂度 \(\mathcal{O}((n+q)\log q)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int root[100005];
struct segtree{
	#define ls (c[p].lc)
	#define rs (c[p].rc)
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	struct Node{
		int mx,mn,tm,ta,lc,rc;
	}c[5000005];
	int T;
	int newnode(){
		int p=++T;
		c[p]=(Node){0,0,1,0,0,0};
		return p;
	}
	void pushup(int p){
		c[p].mx=max(c[ls].mx,c[rs].mx);
		c[p].mn=min(c[ls].mn,c[rs].mn);
	}
	void pushtm(int p,int v){
		if(v!=-1)return;
//		assert(v==-1);
		swap(c[p].mx,c[p].mn);c[p].mx*=v,c[p].mn*=v;
		assert(c[p].mx>=c[p].mn);
		c[p].tm*=v,c[p].ta*=v;
	} 
	void pushta(int p,int v){
		c[p].mx+=v,c[p].mn+=v;
		c[p].ta+=v;
	}
	void pushdown(int p){
		if(!ls)ls=newnode();
		if(!rs)rs=newnode();
		pushtm(ls,c[p].tm),pushtm(rs,c[p].tm),c[p].tm=1;
		pushta(ls,c[p].ta),pushta(rs,c[p].ta),c[p].ta=0;		
	}
	int upd(int l,int r,int p,int L,int R,int v){
		if(!p)p=newnode();
		if(L<=l&&r<=R){
			if(c[p].mn>=v){pushta(p,-v);return p;}
			if(c[p].mx<=v){pushtm(p,-1),pushta(p,v);return p;}
		}
		if(l==r){
			c[p].mx=c[p].mn=abs(c[p].mx-v);
			return p;
		}
		int mid=(l+r)>>1;pushdown(p);
		if(L<=mid)ls=upd(lson,L,R,v);
		if(R>mid)rs=upd(rson,L,R,v);
		pushup(p);return p;
	}
	int add(int l,int r,int p,int L,int R,int v){
		if(!p)p=newnode();
		if(L<=l&&r<=R){pushta(p,v);return p;}
		int mid=(l+r)>>1;pushdown(p);
		if(L<=mid)ls=add(lson,L,R,v);
		if(R>mid)rs=add(rson,L,R,v);
		pushup(p);return p;
	}
	int merge(int l,int r,int p,int q){
		if(!p)return q;
		if(!q)return p;
		if(c[p].mx==c[p].mn){
			upd(l,r,q,l,r,c[p].mx);
			return q;
		}
		if(c[q].mx==c[q].mn){
			upd(l,r,p,l,r,c[q].mx);
			return p;
		}
		int mid=(l+r)>>1;pushdown(p),pushdown(q);
		ls=merge(lson,c[q].lc),rs=merge(rson,c[q].rc);
		pushup(p);return p;
	}
	int ask(int l,int r,int p,int x){
		if(l==r)return c[p].mx;
		int mid=(l+r)>>1;pushdown(p);
		if(x<=mid)return ask(lson,x);
		else return ask(rson,x);
	}
	#undef lson
	#undef rson
	#undef ls
	#undef rs	
}Tr;
int n,m,ls[100005],rs[100005],a[100005];vector<array<int,2> >v[100005];
void dfs(int u){
	if(ls[u])dfs(ls[u]);
	if(rs[u])dfs(rs[u]);
	if(ls[u])root[u]=Tr.merge(1,m,root[ls[u]],root[rs[u]]);
	for(int i=0;i+1<(int)v[u].size();i++){
		int L=v[u][i][0],R=v[u][i+1][0]-1;
		if(L<=R)root[u]=Tr.add(1,m,root[u],L,R,v[u][i][1]);
	}
}
signed main(){
	n=read();for(int i=1;i<=n;i++)ls[i]=read(),rs[i]=read();
	for(int i=1;i<=n;i++)a[i]=read(),v[i].push_back({1,a[i]});
	m=read();for(int i=1,u,x;i<=m;i++)u=read(),x=read(),v[u].push_back({i,x});
	for(int i=1;i<=n;i++)v[i].push_back({m+1,0});
	dfs(1);for(int i=1;i<=m;i++)printf("%lld\n",Tr.ask(1,m,root[1],i));
	return 0;
}

C 【0130 A组】尝试了飞行

posted @ 2024-01-05 19:10  xx019  阅读(19)  评论(2)    收藏  举报