2025.4

兄弟,太菜了。

627E、P5398、QOJ8331、CF1548E、希望、命运、SNOI2024.


没有代码的题可能有多种情况:不想写、写一半不想写了、写完发现卡不过去不想重构了。


P5072 [Ynoi] 盼君勿忘

shengqile.cpp。

一个小众的做法。

拆贡献到每个数上,设这个数是区间内第 \(p+1\) 次出现,则贡献为 \(x\times 2^{r-l-p}\)。那么设这个数一共出现了 \(t\) 次,等比数列求和得到 \(x\times(2^{r-l+1}-2^{r-l-t+1})\)

根据经典结论,区间内不同出现次数的不超过 \(O(\sqrt n)\),自然想到维护每种出现次数对应的所有数的和。分块,对于第 \(l\) 块到第 \(r\) 块维护所有出现次数及其对应的和,显然维护的信息总量是 \(O(n\sqrt n)\) 的。查询时对于整块直接得到信息,散块通过维护每个数出现的前缀和(简单做到 \(O(n\sqrt n)\))来更新信息。

现在考虑对于整块如何维护信息。固定最左边的块后扫描最右边的块,扫描的过程中需要维护所有出现次数及其对应的和,观察到增加一个数对于该种数出现次数的变化一定是 \(t\to t+1\),这种特性使得我们可以直接维护链表代表所有出现次数,和可以简单维护,找到最右边的块以后在链表上跳一下即可。

时间复杂度 \(O(n\sqrt n)\)。可能空间需要调一下块长来平衡。

点击查看代码
#include<bits/stdc++.h>
// #define int long long
#define ll long long
#define fi first
#define se second
#define pii std::pair<int,int>
#define vint std::vector<int>
#define vpair std::vector<pii>
#define debug(...) fprintf(stderr,##__VA_ARGS__)

template<typename T>
void read(T &x){
	x=0;
	int f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9') x=x*10+(int)(c-'0'),c=getchar();
	x*=f;
}

std::stack<char>st;
template<typename T>
void print(T x){
	if(x==0) putchar('0');
	if(x<0) putchar('-'),x=-x;
	while(st.size()) st.pop();
	while(x) st.push((char)('0'+x%10)),x/=10;
	while(st.size()) putchar(st.top()),st.pop();
}

template<typename T>
void printsp(T x){
	print(x),putchar(' ');
}

template<typename T>
void println(T x){
	print(x),putchar('\n');
}

template<typename T,typename I>
bool chkmin(T &a,I b){
	if(a>b) return a=b,1;
	return 0;
}

template<typename T,typename I>
bool chkmax(T &a,I b){
	if(a<b) return a=b,1;
	return 0;
}

template<typename T,typename I>
void addedge(std::vector<I>*vec,T u,T v){
	vec[u].push_back(v);
}

template<typename T,typename I,typename K>
void addedge(std::vector<K>*vec,T u,T v,I w){
	vec[u].push_back({v,w});
}

template<typename T,typename I>
void addd(std::vector<I>*vec,T u,T v){
	addedge(vec,u,v),addedge(vec,v,u);
}

template<typename T,typename I,typename K>
void addd(std::vector<K>*vec,T u,T v,I w){
	addedge(vec,u,v,w),addedge(vec,v,u,w);
}

bool Mbe;

const int inf=1e9,MOD1=998244353,MOD2=1e9+7;

const int maxn=1e5+10,maxB=250;

int a[maxn],n,m,b[maxB][maxB][451],B,l[maxB],r[maxB],bel[maxn],head,cnt,ti[maxn],sum[maxB][maxn],num[maxB][maxB];

ll d[maxB][maxB][451],pw[maxB],s[maxn],p[maxn],tim,w[maxn];

ll prob[maxn],sp[maxn],mit,mi[maxn];

struct node{
	int pre,nxt;
}c[maxn];

void clear(int p){
	c[p].pre=c[p].nxt=0;
}

void erase(int p){
	c[c[p].pre].nxt=c[p].nxt;
	if(c[p].nxt!=0) c[c[p].nxt].pre=c[p].pre;
	c[p].pre=c[p].nxt=0;
}

void insert(int p,int pos){
	clear(p);
	if(c[pos].nxt) c[c[pos].nxt].pre=p;
	c[p].nxt=c[pos].nxt,c[p].pre=pos;
	c[pos].nxt=p;
}

int ksm(int x,ll y,int p){
	if(y==0) return 1;
	int z=ksm(x,y>>1,p);
	z=1ll*z*z%p;
	if(y&1) z=1ll*z*x%p;
	return z;
}

void add(int &a,int b,int p){
	a+=b;
	if(a>=p) a-=p;
}

bool Men;

signed main(){
	debug("%.6lfMB\n",(&Mbe-&Men)/1048576.0);
	read(n),read(m);
	for(int i=1;i<=n;i++) read(a[i]);
	B=500;
	for(int i=1;i<=n;i++){
		cnt++;
		l[cnt]=i,r[cnt]=std::min(i+B-1,n);
		for(int j=l[cnt];j<=r[cnt];j++) bel[j]=cnt;
		i=r[cnt];
	}
	for(int i=1;i<=cnt;i++){
		for(int j=1;j<=(int)1e5;j++) sum[i][j]=sum[i-1][j];
		for(int j=l[i];j<=r[i];j++) sum[i][a[j]]++;
	}
	// debug("ok\n");
	for(int i=1;i<=cnt;i++){
		head=0;
		clear(head);
		memset(s,0,sizeof(s)),memset(p,0,sizeof(p));
		s[0]=inf;
		for(int j=i;j<=cnt;j++){
			for(int k=l[j];k<=r[j];k++){
				// debug("i=%d j=%d k=%d\n",i,j,k);
				int v=a[k];
				s[p[v]+1]+=v;
				if(s[p[v]+1]==v) insert(p[v]+1,p[v]);
				s[p[v]]-=v;
				if(!s[p[v]]) erase(p[v]);
				p[v]++;
			}
			int now=c[head].nxt;
			num[i][j]=0;
			while(now){
				// debug("now=%d\n",now);
				b[i][j][++num[i][j]]=now,d[i][j][num[i][j]]=s[now];
				now=c[now].nxt;
			}
		}
	}
	// return 0;
	while(m--){
		int ql,qr,mod;
		read(ql),read(qr),read(mod);
		int bl=bel[ql],br=bel[qr];
		int ans=0;
		if(bl==br){
			tim++;
			pw[0]=1;
			for(int i=1;i<=B;i++) pw[i]=pw[i-1]*2%mod;
			for(int i=ql;i<=qr;i++){
				int coef=qr-ql;;
				if(ti[a[i]]==tim) coef-=w[a[i]],w[a[i]]++;
				else ti[a[i]]=tim,w[a[i]]=1;
				add(ans,1ll*a[i]*pw[coef]%mod,mod);
			}
			println(ans);
			continue;
		}
		tim++,mit++;
		int x=0;
		if(bl+1<=br-1){
			x=num[bl+1][br-1];
			// assert(x<=400);
			for(int i=1;i<=x;i++){
				prob[i]=b[bl+1][br-1][i];
				mi[prob[i]]=mit,sp[prob[i]]=d[bl+1][br-1][i];
				// debug("prob=%lld sp=%lld\n",prob[i],sp[prob[i]]);
			}
		}
		int ap;
		for(int i=ql;i<=r[bl];i++){
			ap=sum[br-1][a[i]]-sum[bl][a[i]]+1;
			if(ti[a[i]]==tim) ap+=w[a[i]],w[a[i]]++;
			else ti[a[i]]=tim,w[a[i]]=1;
			if(ap-1>0) sp[ap-1]-=a[i];
			sp[ap]+=a[i];
			if(mi[ap]!=mit) prob[++x]=ap,mi[ap]=mit;
		}
		for(int i=l[br];i<=qr;i++){
			ap=sum[br-1][a[i]]-sum[bl][a[i]]+1;
			// debug("ap=%d ti=%d tim=%lld\n",ap,ti[a[i]],tim);
			if(ti[a[i]]==tim) ap+=w[a[i]],w[a[i]]++;
			else ti[a[i]]=tim,w[a[i]]=1;
			if(ap-1>0) sp[ap-1]-=a[i];
			sp[ap]+=a[i];
			// debug("a=%d ap=%d\n",a[i],ap);
			if(mi[ap]!=mit) prob[++x]=ap,mi[ap]=mit;
		}	
		int pww=ksm(2,qr-ql+1,mod);
		pw[0]=1;
		for(int i=1;i<=300;i++) pw[i]=pw[i-1]*2%mod;
		for(int i=600;i<=n;i+=300) pw[i]=pw[i-300]*pw[300]%mod;
		for(int i=1;i<=x;i++){
			// debug("sp=%lld prob=%lld\n",sp[prob[i]],prob[i]);
			add(ans,sp[prob[i]]*(((pww-pw[(qr-ql-prob[i]+1)/300*300]*pw[(qr-ql-prob[i]+1)%300]%mod)+mod)%mod)%mod,mod);
			sp[prob[i]]=0;
		}
		println(ans);
	}
	debug("%.6lfms\n",1e3*clock()/CLOCKS_PER_SEC);
}

一轮省集 day4 异或

\(w=\log V\)

简单思考发现很难脱离 trie,因此一个 \(w\) 是省不掉的。\(O(qw\sqrt n)\) 是平凡的,分块维护 trie 后修改散块重构整块打标记。对于查询在所有整块的 trie 上维护指针二分,散块特殊处理即可。

这个做法的时间复杂度实际上是修改 \(O(\frac n B+Bw)\),查询 \(O(\frac{nw}B+B)\)。我们想做到 \(O(\frac n B+B)+O(\frac{nw}B+B)\) 的时间复杂度,这样取 \(B=\sqrt{nw}\) 可以平衡复杂度到 \(O(q\sqrt{nw})\)。注意到瓶颈在于修改时总要对 trie 重构。实际上对于一个排好序的数组,我们可以在 \(O(n)\) 时间内建出其的 trie(将整数 lcp 视为 \(O(1)\))。这样修改时归并排序后线性重构 trie。查询时依然二分即可。

详细写一下压缩 trie。正常的 trie 是 \(O(\sum)\) 个节点,若一个节点下面已经只包含一个数了,我们就将这个数放到这个节点上存,这样总节点数量大概是线性的,同时还存在性质度数 \(>2\) 个点的数量为 \(O(n)\)。对其求虚树不难发现总点数为 \(O(n)\)。直接用单调栈线性求虚树即可。

CF1835D

需要加深一下对 SCC 这种结构的理解。

考虑 SCC 本质上是若干环复合在一起,但是用文字还是较难刻画的,其有一些性质。设所有环的 \(\gcd\)\(d\)。不难发现是否考虑非简单环不影响 \(d\)以下讨论建立在模 \(d\) 意义上

\(a\to b\) 存在长度为 \(w\) 的路径,则 \(b\to a\) 存在长度为 \(-w\) 的路径。

考虑一个包含该路径的环,其长度为 \(0\)。绕这个环走到 \(a\) 即可构造出长为 \(-w\) 的路径。

\(a\to b\) 的所有路径长度相同。

考虑一条 \(b\to a\) 的路径,显然任取 \(a\to b\) 的路径加上这条 \(b\to a\) 的路径后长度为 \(0\)。所以 \(a\to b\) 的所有路径长度相同。

\(a\to b\) 的一条路径可以视作 \(a\to b\) 的一条简单路径加上若干环。

比较显然了。这说明设所有环为 \(l_1,l_2,\dots,l_m\),路径长度就可以刻画为 \(w+\sum a_il_i\)(不在取模意义下也是这样的)。

考虑对于同一个 scc 中的固定两点 \(x\)\(y\)\(w\) 是确定的,需要考察能增广的路径长度 \(s\),由于题目中 \(k\) 充分大,我们认为任意的 \(s\) 都能被增广得到,实际上需要对裴蜀定理进行扩展得到这个结论。

裴蜀定理

普通的裴蜀定理告诉我们 \(ax+by=1\)\((x,y)=1\) 时有解,我们进一步指出 \(\sum a_ix_i=1\) 当且仅当在 \(\gcd(a_i)=1\) 时有解。

前者的证明是利用 \(ax\) 可以取遍 \(b\) 的剩余类。我们类似地证明后者,归纳地,已知 \(\sum a_ix_i\) 可以表示出任何 \(d=\gcd(a_i)\) 的倍数之后,想要证明 \(\sum a_ix_i+a'x'\) 可以表示出任何数(\(\gcd(d,a')=1\))。

\(\sum a_ix_i\) 的结果可以表示成 \(xd\),这样就转化成一般的证明形式了。

接下来如果能求出该 scc 环长的 \(\gcd\),问题就能变成只考虑简单路径的情况下是否有 \(dis(x\to y)\equiv dis(y\to x)\equiv k\pmod d\) 了(由于 \(k\) 充分大,我们无需考虑将路径长度的大小进行控制)。

先来求 \(d\),JCY_std 声称,对于正整数 \(p\)\(p\mid d\) 的一个充要条件是找到一组 \(\{dis_n\}\),使得原图所有边都有 \(dis_v-dis_u\equiv w\pmod p\)

充分性

不难发现任意一个环在模 \(p\) 意义下均为 \(0\),因此 \(p\mid d\)

必要性

给出构造性证明。

任取根 \(rt\),求一棵以 \(rt\) 为根的外向生成树,对于所有树边 \((u,v,w)\) 定义 \(dis_v=dis_u+w\)。此时所有树边都是合法的。考虑非树边 \((u,v,w)\),存在一条 \(v\to rt\) 长度为 \(-dis_v\) 的路径,考察路径 \(rt\to u\to v\) 可以得到 \(dis_u+w-dis_v\equiv0\pmod p\),移项得到成立。

根据必要性中的构造性证明可以发现只需要求外向生成树后 \(\gcd(dis_v-dis_u-w)\) 就是需要的 \(d\)。考虑求答案,上文提到 \(dis(x\to y)\equiv-dis(y\to x)\pmod d\),因此 \(dis(x\to y)\equiv dis(y\to x)\equiv k\) 当且仅当 \(k=0\) 或者 \(2\mid d\)\(dis(x\to y)\equiv\frac d2\pmod d\),求出 \(d\) 以后若 \(k\) 满足条件,则只需要求 \(dis(x\to y)\equiv k\pmod d\),其中 \(x\le y\)\((x,y)\) 数量。由于 \(dis(x\to y)\equiv dis_y-dis_x\pmod d\),扫一遍即可。注意分 scc 处理。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define pii std::pair<int,int>
#define vint std::vector<int>
#define vpair std::vector<pii>
#define debug(...) fprintf(stderr,##__VA_ARGS__)

template<typename T>
void read(T &x){
	x=0;
	int f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9') x=x*10+(int)(c-'0'),c=getchar();
	x*=f;
}

std::stack<char>st;
template<typename T>
void print(T x){
	if(x==0) putchar('0');
	if(x<0) putchar('-'),x=-x;
	while(st.size()) st.pop();
	while(x) st.push((char)('0'+x%10)),x/=10;
	while(st.size()) putchar(st.top()),st.pop();
}

template<typename T>
void printsp(T x){
	print(x),putchar(' ');
}

template<typename T>
void println(T x){
	print(x),putchar('\n');
}

template<typename T,typename I>
bool chkmin(T &a,I b){
	if(a>b) return a=b,1;
	return 0;
}

template<typename T,typename I>
bool chkmax(T &a,I b){
	if(a<b) return a=b,1;
	return 0;
}

template<typename T,typename I>
void addedge(std::vector<I>*vec,T u,T v){
	vec[u].push_back(v);
}

template<typename T,typename I,typename K>
void addedge(std::vector<K>*vec,T u,T v,I w){
	vec[u].push_back({v,w});
}

template<typename T,typename I>
void addd(std::vector<I>*vec,T u,T v){
	addedge(vec,u,v),addedge(vec,v,u);
}

template<typename T,typename I,typename K>
void addd(std::vector<K>*vec,T u,T v,I w){
	addedge(vec,u,v,w),addedge(vec,v,u,w);
}

bool Mbe;

const int inf=1e18,MOD1=998244353,MOD2=1e9+7;

const int maxn=1e5+10;

vint a[maxn];

int n,m,k,dfn[maxn],low[maxn],stk[maxn],tp,cnt,ans,in[maxn],dis[maxn],bel[maxn],num,t[maxn];

vint vec;

void dfs(int p){
	for(int i:a[p]){
		if(bel[i]!=bel[p]) continue;
		if(dis[i]) continue;
		dis[i]=dis[p]+1,dfs(i);
	}
}

bool cmp(int x,int y){
	return dis[x]<dis[y];
}
int my(int x){
	return x<0?-x:x;
}

void tarjan(int p){
	dfn[p]=low[p]=++cnt,stk[++tp]=p,in[p]=1;
	for(int i:a[p]){
		if(!dfn[i]) tarjan(i),chkmin(low[p],low[i]);
		else if(in[i]) chkmin(low[p],low[i]);
	}
	if(dfn[p]==low[p]){
		num++,vec.clear();
		while(stk[tp+1]!=p){
			in[stk[tp]]=0,bel[stk[tp]]=num,vec.push_back(stk[tp]),tp--;
		}
		if(vec.size()==1) return ;
		dis[p]=1,dfs(p);
		int d=0;
		for(int i:vec){
			for(int j:a[i]){
				if(bel[i]!=bel[j]) continue;
				d=std::__gcd(my(dis[j]-dis[i]-1),d);
			}
		}
		// debug("\n");
		if(k%d!=0&&(d%2!=0||k%d!=d/2)) return ;
		std::sort(vec.begin(),vec.end(),cmp);
		for(int i:vec){
			dis[i]%=d;
			// debug("i=%lld dis=%lld d=%lld mod=%lld\n",i,dis[i],d,k%d);
			t[dis[i]]++;
			if(k%d==0) ans+=t[dis[i]];
			else ans+=t[(dis[i]+d/2)%d];
		}
		for(int i:vec) t[dis[i]]--;
	}
}

bool Men;

signed main(){
	debug("%.6lfMB\n",(&Mbe-&Men)/1048576.0);
	read(n),read(m),read(k);
	for(int i=1;i<=m;i++){
		int u,v;
		read(u),read(v);
		addedge(a,u,v);
	}
	for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
	println(ans);
	debug("%.6lfms\n",1e3*clock()/CLOCKS_PER_SEC);
}

CF1904F

没做完,先写个思路。

考虑将权值按照 \(n\to1\) 的顺序填上去,那么一个数能填当且仅当满足以下两点:

  • 对于所有第一类限制,限制的不是它,或者该限制除了它外所有点都填上了。

  • 对于所有第二类限制,限制的是它,或者限制的那个点已经填上了。

这样就有一个 \(O(nm)\) 的做法了。考虑硬上数据结构维护这些东西。第二个要求比较简单,需要支持链加以及查一些东西;第一个要求比较困难,要求所有和它相关的集合权值全都 \(-1\)。目前对于后者有了一个分块的 \(O(n\sqrt n\log n)\) 做法,具体优化再想想,少做一点数据结构吧。

CF2043F2

这么牛的题。

ev 存在简单的三方做法,但是对 hv 没有任何帮助。

考虑这个选宝石的过程很像在网格图上走,因此扔到网格图上,等价于可以往左走将当前价值 \(+2\),往右走将价值 \(+1\),每次到达一个关键点将价值 \(\times 2\)。设一条路径经过的关键点为 \((p_1,p_2,\dots,p_k)\),则价值为:

\[\sum (2(r_{p_i}-r_{p_{i-1}})+b_{p_i}-b_{p_{i-1}})\times 2^{k-i+1} \]

拆贡献后对于每个关键点的式子是 \((2r_{p_i}+b_{p_i})\times 2^{k-i}\)。这个 \(2^{k-i}\) 存在非常好的组合意义,就是从 \(i+1\sim k\) 中任取一个集合的方案数。这样我们就有一个做法,钦定一个关键点集合,要求全部经过但是不必恰好经过那些点,设方案数为 \(f_i\),则贡献乘上 \(f_i\) 即可。\(f_i\) 直接求即可,时间复杂度 \(O(k^2)\)

P????? [USTCPC2025] 高位逼抢

这题评绿,洛谷螳臂吧。
同一场的积分题评黄?那可以理解了。

洛谷上先开的这道题,感觉很趣味啊!由于之前在正睿做过类似的题,大概做了十分钟就做出来了!

\(a_i\) 是第 \(i\) 个点的答案,那么我们对于答案能列出来一个式子,设 \(b\) 是度数,将所有相邻的点按照 \(a\) 从小到大排序,有 \(\min(b,\max(b-1,a_1),\max(b-2,a_2),\dots,\max(b-b,a_b))\)。可惜有环,这个式子不能直接用。

考虑一开始我们能确定什么样的点的答案,显然度数最小的点答案可以直接确定。这给了一定启发,将所有点分成两个集合,划分标准是答案是否确定,将所有点按照当前的最优答案排序(没有当前最优答案的就是该点的度数),取出最小值松弛即可,时间复杂度 \(O(m\log m)\)

点击查看代码
#include<bits/stdc++.h>
// #define int long long
#define fi first
#define se second
#define pii std::pair<int,int>
#define vint std::vector<int>
#define vpair std::vector<pii>
#define debug(...) fprintf(stderr,##__VA_ARGS__)

template<typename T>
void read(T &x){
	x=0;
	int f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9') x=x*10+(int)(c-'0'),c=getchar();
	x*=f;
}

std::stack<char>st;
template<typename T>
void print(T x){
	if(x==0) putchar('0');
	if(x<0) putchar('-'),x=-x;
	while(st.size()) st.pop();
	while(x) st.push((char)('0'+x%10)),x/=10;
	while(st.size()) putchar(st.top()),st.pop();
}

template<typename T>
void printsp(T x){
	print(x),putchar(' ');
}

template<typename T>
void println(T x){
	print(x),putchar('\n');
}

template<typename T,typename I>
bool chkmin(T &a,I b){
	if(a>b) return a=b,1;
	return 0;
}

template<typename T,typename I>
bool chkmax(T &a,I b){
	if(a<b) return a=b,1;
	return 0;
}

template<typename T,typename I>
void addedge(std::vector<I>*vec,T u,T v){
	vec[u].push_back(v);
}

template<typename T,typename I,typename K>
void addedge(std::vector<K>*vec,T u,T v,I w){
	vec[u].push_back({v,w});
}

template<typename T,typename I>
void addd(std::vector<I>*vec,T u,T v){
	addedge(vec,u,v),addedge(vec,v,u);
}

template<typename T,typename I,typename K>
void addd(std::vector<K>*vec,T u,T v,I w){
	addedge(vec,u,v,w),addedge(vec,v,u,w);
}

bool Mbe;

const int inf=1e9,MOD1=998244353,MOD2=1e9+7;

const int maxn=1e6+10;

vint a[maxn];

int n,m,deg[maxn],ans[maxn],num[maxn];

std::map<int,int>mp[maxn];

bool f[maxn];

bool Men;

signed main(){
	debug("%.6lfMB\n",(&Mbe-&Men)/1048576.0);
	read(n),read(m);
	int u,v;
	for(int i=1;i<=m;i++){
		read(u),read(v);
		if(u>v) std::swap(u,v);
		// assert(u!=v);
		// assert(!mp[u][v]),mp[u][v]=1;
		addd(a,u,v);
		deg[u]++,deg[v]++;
	}
	std::priority_queue<pii,vpair,std::greater<pii> >s1,s2;
	// std::set<pii>s1,s2;
	for(int i=1;i<=n;i++) s2.push({ans[i]=inf,i});
	for(int i=1;i<=n;i++) s1.push({deg[i],i});
	for(int i=1;i<=n;i++){
		while(s1.size()){
			if(f[s1.top().se]) s1.pop();
			else break;
		}
		while(s2.size()) 
			if(f[s2.top().se]) s2.pop();
			else break;
		int b=s1.top().se,c=s2.top().se,las;
		if(deg[b]<=ans[c]){
			ans[b]=deg[b],f[b]=1;
			for(int j:a[b]){
				if(f[j]) continue;
				num[j]++,las=ans[j];
				if(chkmin(ans[j],std::max(deg[j]-num[j],ans[b]))) s2.push({ans[j],j});
			}
			continue;
		}
		std::swap(b,c);
		f[b]=1;
		for(int j:a[b]){
			if(f[j]) continue;
			num[j]++,las=ans[j];
			if(chkmin(ans[j],std::max(deg[j]-num[j],ans[b]))) s2.push({ans[j],j});
		}
	}
	for(int i=1;i<=n;i++) printsp(ans[i]);
	debug("\n%.6lfms\n",1e3*clock()/CLOCKS_PER_SEC);
}

QOJ8328 A good problem.

A good problem.

感觉盯了一万年只会 \(O(n\sqrt n)\) 次操作,大致就是对值域分块,每块分别操作。实际上这个做法和正解已经很接近了。对值域分治,设当前区间为 \([l,r]\),选取 \(mid\),将所有最后 \(\ge mid\) 的位置先调整到 \(\mid\),然后对 \([l,mid-1]\)\([mid,r]\) 分治即可。

不难发现一个位置被做 \(2\) 操作的次数只有 \(O(\log n)\) 次(每次分治区间包含这个位置时可能会做一次),因此 \(2\) 操作总次数就是 \(O(n\log n)\) 次。同时每次分治只会做 \(O(len)\)\(1\) 操作,总的操作次数为 \(O(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define pii std::pair<int,int>
#define vint std::vector<int>
#define vpair std::vector<pii>
#define debug(...) fprintf(stderr,##__VA_ARGS__)

template<typename T>
void read(T &x){
	x=0;
	int f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9') x=x*10+(int)(c-'0'),c=getchar();
	x*=f;
}

std::stack<char>st;
template<typename T>
void print(T x){
	if(x==0) putchar('0');
	if(x<0) putchar('-'),x=-x;
	while(st.size()) st.pop();
	while(x) st.push((char)('0'+x%10)),x/=10;
	while(st.size()) putchar(st.top()),st.pop();
}

template<typename T>
void printsp(T x){
	print(x),putchar(' ');
}

template<typename T>
void println(T x){
	print(x),putchar('\n');
}

template<typename T,typename I>
bool chkmin(T &a,I b){
	if(a>b) return a=b,1;
	return 0;
}

template<typename T,typename I>
bool chkmax(T &a,I b){
	if(a<b) return a=b,1;
	return 0;
}

template<typename T,typename I>
void addedge(std::vector<I>*vec,T u,T v){
	vec[u].push_back(v);
}

template<typename T,typename I,typename K>
void addedge(std::vector<K>*vec,T u,T v,I w){
	vec[u].push_back({v,w});
}

template<typename T,typename I>
void addd(std::vector<I>*vec,T u,T v){
	addedge(vec,u,v),addedge(vec,v,u);
}

template<typename T,typename I,typename K>
void addd(std::vector<K>*vec,T u,T v,I w){
	addedge(vec,u,v,w),addedge(vec,v,u,w);
}

bool Mbe;

const int inf=1e18,MOD1=998244353,MOD2=1e9+7;

const int maxn=1010;

int a[maxn],n,d[maxn];

pii b[maxn],c[maxn];

vpair vec;

void bl1(int x){
	for(int i=1;i<=n;i++) if(d[i]==x) d[i]++;
}

void bl2(int x){
	d[x]++;
}

void f1(int x){
	vec.push_back({1,x});
	// bl1(x);
}

void f2(int x){
	vec.push_back({2,x});
	// bl2(x);
}

void solve(int l,int r){
	if(l==r) return ;
	int mid=(l+r)>>1;
	for(int i=mid+1;i<=r;i++){
		for(int j=c[i].fi;j<=c[i].se;j++){
			f2(b[j].se);
		}
	}
	for(int i=l+1;i<=mid;i++) f1(i);
	solve(l,mid),solve(mid+1,r);
}

void printa(){
	for(int i=1;i<=n;i++) printsp(d[i]);
	putchar('\n');
}

bool Men;

signed main(){
	// freopen("data.in","r",stdin),freopen("my.out","w",stdout);
	debug("%.6lfMB\n",(&Mbe-&Men)/1048576.0);
	read(n);
	for(int i=1;i<=n;i++) read(a[i]),b[i]={a[i],i};
	std::sort(b+1,b+n+1);
	int z=1;
	for(int i=0;i<=n;i++){
		c[i].fi=z;
		while(z<=n&&b[z].fi==i) z++;
		c[i].se=z-1;
	}
	solve(0,n);
	println(vec.size());
	// debug("vec.size()=%lld\n",vec.size());
	for(pii i:vec) printsp(i.fi),println(i.se);
	// printa();
	return 0;
	for(int i=n;i>=1;i--){
		int r=i,l=std::max(1ll,i-32),s=0;
		for(int j=l;j<=r;j++) if(c[j].se>=c[j].fi) s++;
		if(s==0){
			i=l;
			continue;
		}
		for(int j=l;j<=r;j++) for(int k=c[j].fi;k<=c[j].se;k++) f2(b[k].se);
		int las=1;
		for(int j=l;j<=r;j++){
			if(c[j].fi>c[j].se) continue;
			for(;las<j;las++) f1(las);
			for(int k=j+1;k<=r;k++) for(int p=c[k].fi;p<=c[k].se;p++) f2(b[p].se);
			las++;
		}
		i=l;
	}
	debug("vec.size=%lld\n",vec.size());
	// assert(vec.size()<=20000);
	println(vec.size());
	for(pii i:vec) printsp(i.fi),println(i.se);
	debug("%.6lfms\n",1e3*clock()/CLOCKS_PER_SEC);
}

QOJ8332 Two in One

主要是为了记一个结论。

设一个 \(01\) 序列,\(0\) 出现了 \(x\) 次,\(1\) 出现了 \(y\) 次,定义一个子串的价值是 \(0\) 的数量按位或 \(1\) 的数量,求最大价值。则答案与 \(01\) 序列形态无关,为 \(0\le a\le x\)\(0\le b\le y\) 中最大的 \(a\operatorname{or}b\)

首先这个价值有一个很好的刻画方式 \(x\operatorname{or}y\operatorname{or}(highbit(x\& y)-1)\)。这显然是上界。不妨钦定选择的是一段前缀,此时找到最短的前缀使得 \(a\operatorname{or}b\) 高于 \(highbit(x\& y)\) 的位和 \(x\operatorname{or}y\) 一致。此时必然存在 \(a\)\(b\) 在不超过 \(highbit(x\& y)\) 上的位为全 \(0\),此后必然在这些位上可以取到全 \(1\),同时这个过程中更高位不会发生变化。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define pii std::pair<int,int>
#define vint std::vector<int>
#define vpair std::vector<pii>
#define debug(...) fprintf(stderr,##__VA_ARGS__)

template<typename T>
void read(T &x){
	x=0;
	int f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9') x=x*10+(int)(c-'0'),c=getchar();
	x*=f;
}

std::stack<char>st;
template<typename T>
void print(T x){
	if(x==0) putchar('0');
	if(x<0) putchar('-'),x=-x;
	while(st.size()) st.pop();
	while(x) st.push((char)('0'+x%10)),x/=10;
	while(st.size()) putchar(st.top()),st.pop();
}

template<typename T>
void printsp(T x){
	print(x),putchar(' ');
}

template<typename T>
void println(T x){
	print(x),putchar('\n');
}

template<typename T,typename I>
bool chkmin(T &a,I b){
	if(a>b) return a=b,1;
	return 0;
}

template<typename T,typename I>
bool chkmax(T &a,I b){
	if(a<b) return a=b,1;
	return 0;
}

template<typename T,typename I>
void addedge(std::vector<I>*vec,T u,T v){
	vec[u].push_back(v);
}

template<typename T,typename I,typename K>
void addedge(std::vector<K>*vec,T u,T v,I w){
	vec[u].push_back({v,w});
}

template<typename T,typename I>
void addd(std::vector<I>*vec,T u,T v){
	addedge(vec,u,v),addedge(vec,v,u);
}

template<typename T,typename I,typename K>
void addd(std::vector<K>*vec,T u,T v,I w){
	addedge(vec,u,v,w),addedge(vec,v,u,w);
}

bool Mbe;

const int inf=1e18,MOD1=998244353,MOD2=1e9+7;

const int maxn=5e5+10;

int T,n,a[maxn],t[maxn];

bool Men;

signed main(){
	debug("%.6lfMB\n",(&Mbe-&Men)/1048576.0);
	read(T);
	while(T--){
		read(n);
		for(int i=1;i<=n;i++) t[i]=0;
		for(int i=1;i<=n;i++) read(a[i]),t[a[i]]++;
		int mx,cmx;
		mx=cmx=-1;
		for(int i=1;i<=n;i++){
			if(t[i]>mx) cmx=mx,mx=t[i];
			else if(t[i]>cmx) cmx=t[i];
		}
		if(cmx==-1){
			println(mx);;
			continue;
		}
		int ans=0;
		for(int i=25;i>=0;i--){
			if(mx<(1ll<<i)&&cmx<(1ll<<i)) continue;
			if((mx>>i)&1) ans+=(1ll<<i),mx-=(1ll<<i);
			else ans+=(1ll<<i),cmx-=(1ll<<i);
		}
		println(ans);
	}
	debug("%.6lfms\n",1e3*clock()/CLOCKS_PER_SEC);
}
/*
1
7
1 2 3 4 3 2 1

1
9
1 1 1 1 1 2 2 2 2
*/

QOJ8331 Bot Brothers

神秘注意力题。

首先后手可以模仿先手的操作做到不败。因此只需判断后手是否必胜。

CF2093G Shorten the Array

全球三血,还是挺激动的。

问题等价于对于每个点找出最前面一个合法的点,直接二分+可持久化 trie 可以无脑 \(O(n\log^2 n)\)

\(\log\) 感觉也不难,直接 trie 上维护子树最小值大概就可以做。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define pii std::pair<int,int>
#define vint std::vector<int>
#define vpair std::vector<pii>
#define debug(...) fprintf(stderr,##__VA_ARGS__)

template<typename T>
void read(T &x){
	x=0;
	int f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9') x=x*10+(int)(c-'0'),c=getchar();
	x*=f;
}

std::stack<char>st;
template<typename T>
void print(T x){
	if(x==0) putchar('0');
	if(x<0) putchar('-'),x=-x;
	while(st.size()) st.pop();
	while(x) st.push((char)('0'+x%10)),x/=10;
	while(st.size()) putchar(st.top()),st.pop();
}

template<typename T>
void printsp(T x){
	print(x),putchar(' ');
}

template<typename T>
void println(T x){
	print(x),putchar('\n');
}

template<typename T,typename I>
bool chkmin(T &a,I b){
	if(a>b) return a=b,1;
	return 0;
}

template<typename T,typename I>
bool chkmax(T &a,I b){
	if(a<b) return a=b,1;
	return 0;
}

template<typename T,typename I>
void addedge(std::vector<I>*vec,T u,T v){
	vec[u].push_back(v);
}

template<typename T,typename I,typename K>
void addedge(std::vector<K>*vec,T u,T v,I w){
	vec[u].push_back({v,w});
}

template<typename T,typename I>
void addd(std::vector<I>*vec,T u,T v){
	addedge(vec,u,v),addedge(vec,v,u);
}

template<typename T,typename I,typename K>
void addd(std::vector<K>*vec,T u,T v,I w){
	addedge(vec,u,v,w),addedge(vec,v,u,w);
}

bool Mbe;

const int inf=1e18,MOD1=998244353,MOD2=1e9+7;

const int maxn=2e5+10;

int T,n,a[maxn],rt[maxn],k;

struct trie{
	int ls[maxn*60],rs[maxn*60],sz[maxn*60],tot;
	void clear(){
		tot=0;
	}
	void push_up(int p){
		sz[p]=sz[ls[p]]+sz[rs[p]];
	}
	void newnode(int &p){
		p=++tot;
		ls[p]=rs[p]=0,sz[p]=0;
	}
	void insert(int &p,int &rt,int x,int y){
		if(!p) newnode(p);
		if(y==-1){
			sz[p]++;
			return ;
		}
		if(((x>>y)&1)==1) insert(rs[p],rs[rt],x,y-1),ls[p]=ls[rt];
		else insert(ls[p],ls[rt],x,y-1),rs[p]=rs[rt];
		push_up(p);
	}
	int getmx(int &p,int &rt,int x,int y){//在 p-rt 这棵主席树上做减法 求 xor x 的最大值
		if(y==-1) return 0;
		if(((x>>y)&1)==0){
			if(rs[p]-rs[rt]) return (1ll<<y)+getmx(rs[p],rs[rt],x,y-1);
			else return getmx(ls[p],ls[rt],x,y-1);
		}else{
			if(ls[p]-ls[rt]) return (1ll<<y)+getmx(ls[p],ls[rt],x,y-1);
			else return getmx(rs[p],rs[rt],x,y-1);
		}
	}
}ds;

bool Men;

signed main(){
	debug("%.6lfMB\n",(&Mbe-&Men)/1048576.0);
	read(T);
	while(T--){
		read(n),read(k);
		for(int i=1;i<=n;i++) read(a[i]),rt[i]=0;
		ds.clear();
		for(int i=1;i<=n;i++) ds.insert(rt[i],rt[i-1],a[i],32);
		int ans=inf;
		for(int i=1;i<=n;i++){
			int l=1,r=i;
			while(l<=r){
				int mid=(l+r)>>1;
				// debug("mid=%lld mx=%lld\n",ds.getmx(rt[i],rt[mid-1]))
				if(ds.getmx(rt[i],rt[mid-1],a[i],32)>=k) l=mid+1;
				else r=mid-1;
			}
			// debug("i=%lld l-1=%lld\n",i,l-1);
			if(l-1)chkmin(ans,i-(l-1)+1);
		}
		if(ans==inf) println(-1);
		else println(ans);
	}
	debug("%.6lfms\n",1e3*clock()/CLOCKS_PER_SEC);
}

P7447 [Ynoi] rgxsxrs

倍增值域分块,设块长为 \(K\),则将值域分成 \([1,K)\)\([K,K^2)\)\(\dots\)\([K^{\lfloor\log_K V\rfloor},V]\),这样一共有 \(\log_K V\) 个块。对于每一块维护一棵线段树,其中只有块中的数才在线段树上产生贡献。

对于修改操作,枚举每个块,进行分类讨论。

  • \(r\le x\),此时无需操作。

  • \(l>x\),此时每个数都要操作,同时会存在一些数掉到下面块中的情况,由于只有 \(\log_K V\) 个块,这样总的掉落次数为 \(O(n\log_K V)\),线段树上二分暴力找这些点做到这部分的复杂度为 \(O(n\log_K V\log n)\)

  • \(l\le x<r\),此时存在部分数需要操作,我们暴力枚举这块中的所有数做操作,由于这块中的数经过该操作后值会至少变成原来的 \(\frac{K-1}K\),因此操作次数为 \(O(Kn\log V\log n)\)

总时间复杂度 \(O((n+m)\log n\log_K V)\),空间复杂度 \(O(n\log_K V)\)

这样空间过不去,考虑底层分块,首先说明当一个区间在线段树上做查询时一层最多有 \(O(1)\) 个节点被经过。因此设块长 \(B\),对于线段树上区间长度 \(<B\) 的区间直接暴力维护。此时的时间复杂度变成 \(O(K(n+m)(\log n+B)\log V)\),空间复杂度变成 \(O(\frac{n\log_K V}{B})\),原因在于我们可以将底层分块后的线段树视作原线段树维护序列长度压缩成原来的 \(\frac1B\)

复杂度分析的乱七八糟,块长随便乱试一下就好,可以选择参考块长 \(K=32\)\(B=40\)

P9993 [YER2024] TEST_133

首先考虑 \(x=\infty\) 的做法,此时问题等价于区间加,查询区间历史最值。线段树维护广义矩阵乘法即可。

注意并非任意两个运算都能够重新定义矩阵乘法,大部分要求 \((R,+,\times)\) 形成一个类似环的结构,不过不要求加法有逆。

分块,块内排序,维护线段树代表区间内所有向量信息复合。修改时整块直接维护矩阵标记,散块暴力重构。询问时整块直接查,散块暴力查。注意整块直接查不需要真的下放标记,因为标记都是对于整块而言的,只需要求出前缀信息复合直接应用标记即可,查询复杂度 \(O(B\log B+B\omega)\)。修改复杂度 \(O(B\omega)\)。其中 \(\omega\) 为矩阵乘向量复杂度。

直接取 \(B=\sqrt n\) 看上去很有道理!

神秘颜色段均摊还是太难了

将操作分为 assign(区间推平)、perform(区间查询)。用 set 来维护颜色段,每次 assign 暴力删除一些区间同时 split \(O(1)\) 个区间后将新区间加进去。perform 直接在 set 中查询就好。如果每次 perform 后紧跟一次对相同区间的 assign,可以证明复杂度为 \(O(m\log n)\)。证明考虑将势能分成两部分,每个颜色段只会被增删一次,同时作为边缘区间被 split 的总次数只有 \(O(m)\) 次。

P7562 Event Hopping 2

这么牛的题。

考虑贪心加入每一个区间,此时需要解决两个问题,一个区间能否加入,加入后最多还能选几个区间。用 set 维护空闲时间的连续段,那么第一个问题等价于 set 中是否存在一个区间完全包含当前区间,这是简单的。第二个问题考虑动态维护当前剩余最多还能选几个区间,那么实质上就是要支持查询 \([l,r]\) 内最多能选几个区间。注意选到标号较小的区间也是可以的。维护倍增 \(f_{i,j}\) 代表从 \(i\) 开始选 \(2^j\) 个区间最左边到哪,不难做到 \(O(n\log n)\) 时间,\(O(n\log n)\) 空间。

P5251 第二代图灵机

该题数据随机,望周知。

颜色段均摊在随机数据下 perform 后不 assign 的复杂度为 \(O(n\log^2 n)\),望周知。

操作一操作二可以用颜色段均摊结合上其它数据结构维护。操作三考虑暴力双指针,这样的复杂度为区间内颜色段数量,等同于做了一次 perform 操作。操作四同理。

CF1076G Array Game

做数据结构做多的坏处就是遇到这种 6s 2e5 的题就往分块上想。

首先考虑序列形态固定怎么做,手玩不难发现若 \(a_i\) 为偶数则落到 \(i\) 上时先手必胜,否则需要考察 \([i+1,i+m]\) 中是否有必败点。由于 \(m\) 很小,对原序列分块,每块维护当下一块第一个必败点的位置在 \([r+1,r+m]\) 中任意一个时该块第一个必败点的位置。注意还需要加上奇偶性是否翻转这一维。修改时对于整块直接维护 tag,散块暴力 \(O(12B)\) 重构。查询时散块暴力模拟,整块利用维护的信息求出第一个必败点往前转移即可,精细实现做到 \(O(q(\frac nB+24B))\),不难观察到最优块长 \(B=\sqrt{\frac n{24}}\)

上面是自己做这个题想出来的东西。但是我们惊人地发现,所有维护过程都可以搬到线段树上,做到 \(O(mn\log n)\)

启示

做题的时候把想到的条件都列在纸上。

多做一点树上数据结构,少做一点序列上数据结构。

P5311 [Ynoi] 成都七中

首先考虑暴力的做法,刻画一下这个连通块。显然和 \(x\) 处于同一个连通块中当且仅当路径上所有点都在 \([l,r]\) 中。这样我们每次跳到这个连通块在原树上最浅的点,从这个点出发统计会好很多,其余所有点都在这个点的子树中。但是这样子树大小没有保证,考虑建立点分树。

几个点分树的性质:

  • 深度为 \(O(\log n)\)。所有子树大小之和为 \(O(n\log n)\)

  • 两个点在点分树上的 LCA 一定在原树两个点的路径上。

  • 对于原树任意一个连通块,找到该连通块在点分树上最浅的节点 \(p\),则整个连通块一定在 \(p\) 的子树中。证明直接用上面那条性质反证即可。

那么我们跳到每个询问在点分树上连通块深度最浅的点。将所有子树内的点(点分树上)维护出到这个点路径的 \(\max\)\(\min\),这样问题就变成了给出若干个点,每个点有 \(\min\)\(\max\) 和颜色,每个询问需要求出满足 \(l\le\min\)\(\max\le r\) 的颜色数有多少。要求单 \(\log\)

套路地扫描线,对于每种颜色维护最大的 \(\min\),树状数组维护即可。时间复杂度 \(O(n\log^2 n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define pii std::pair<int,int>
#define vint std::vector<int>
#define vpair std::vector<pii>
#define debug(...) fprintf(stderr,##__VA_ARGS__)

template<typename T>
void read(T &x){
	x=0;
	int f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9') x=x*10+(int)(c-'0'),c=getchar();
	x*=f;
}

std::stack<char>st;
template<typename T>
void print(T x){
	if(x==0) putchar('0');
	if(x<0) putchar('-'),x=-x;
	while(st.size()) st.pop();
	while(x) st.push((char)('0'+x%10)),x/=10;
	while(st.size()) putchar(st.top()),st.pop();
}

template<typename T>
void printsp(T x){
	print(x),putchar(' ');
}

template<typename T>
void println(T x){
	print(x),putchar('\n');
}

template<typename T,typename I>
bool chkmin(T &a,I b){
	if(a>b) return a=b,1;
	return 0;
}

template<typename T,typename I>
bool chkmax(T &a,I b){
	if(a<b) return a=b,1;
	return 0;
}

template<typename T,typename I>
void addedge(std::vector<I>*vec,T u,T v){
	vec[u].push_back(v);
}

template<typename T,typename I,typename K>
void addedge(std::vector<K>*vec,T u,T v,I w){
	vec[u].push_back({v,w});
}

template<typename T,typename I>
void addd(std::vector<I>*vec,T u,T v){
	addedge(vec,u,v),addedge(vec,v,u);
}

template<typename T,typename I,typename K>
void addd(std::vector<K>*vec,T u,T v,I w){
	addedge(vec,u,v,w),addedge(vec,v,u,w);
}

bool Mbe;

const int inf=1e18,MOD1=998244353,MOD2=1e9+7;

const int maxn=1e5+10;

int c[maxn],n,m,l[maxn],r[maxn],x[maxn],sz[maxn],h,fa[maxn],mxsz,rt,ans[maxn],mi[maxn],mx[maxn],in[maxn],tim,lasmx[maxn];

bool f[maxn],vis[maxn];

vint a[maxn],b[maxn],d[maxn],g[maxn];

namespace Ynoi{
	int dep[maxn],f[maxn][20],mi[maxn][20],mx[maxn][20];
	void dfs(int p){
		dep[p]=dep[fa[p]]+1;
		for(int i:a[p]){
			if(i==fa[p]) continue;
			fa[i]=p;
			dfs(i);
		}
	}
	void init(){
		for(int i=1;i<=n;i++) f[i][0]=fa[i],mi[i][0]=std::min(i,fa[i]),mx[i][0]=std::max(i,fa[i]);
		for(int j=1;j<20;j++)
			for(int i=1;i<=n;i++) f[i][j]=f[f[i][j-1]][j-1],mi[i][j]=std::min(mi[i][j-1],mi[f[i][j-1]][j-1]),mx[i][j]=std::max(mx[i][j-1],mx[f[i][j-1]][j-1]);
		return ;
	}
	pii query(int u,int v){
		if(dep[u]<dep[v]) std::swap(u,v);
		pii res={inf,-inf};
		chkmin(res.fi,u),chkmax(res.se,v);
		// debug("u=%lld v=%lld\n",u,v);
		for(int i=19;i>=0;i--)
			if(dep[f[u][i]]>=dep[v]) chkmin(res.fi,mi[u][i]),chkmax(res.se,mx[u][i]),u=f[u][i];
		if(u==v) return res;
		for(int i=19;i>=0;i--)
			if(f[u][i]!=f[v][i]) chkmin(res.fi,std::min(mi[u][i],mi[v][i])),chkmax(res.se,std::max(mx[u][i],mx[v][i])),u=f[u][i],v=f[v][i];
		chkmin(res.fi,std::min(mi[u][0],mi[v][0])),chkmax(res.se,std::max(mx[u][0],mx[v][0]));
		return res;
	}
}

const int lim=1e5;

namespace BIT{
	int tim,s[maxn],t[maxn];
	int lowbit(int x){
		return x&(-x);
	}
	void init(){
		tim=0;
	}
	void clear(){
		tim++;
	}
	void add(int k,int x){
		if(k==-inf) return ;
		for(;k<=lim;k+=lowbit(k)){
			if(t[k]==tim) s[k]+=x;
			else s[k]=x,t[k]=tim;
		}
	}
	int query(int k){
		int res=0;
		while(k){
			if(t[k]==tim) res+=s[k];
			k-=lowbit(k);
		}
		return res;
	}
}

namespace Tree{
	vint vec;
	void pdfs(int rt,int p,int fa){
		pii res=Ynoi::query(rt,p);
		mi[p]=res.fi,mx[p]=res.se,in[p]=tim;
		vec.push_back(p);
		for(int i:b[p]){
			if(i==fa) continue;
			pdfs(rt,i,p);
		}
	}
	bool cmp(int x,int y){
		return r[x]<r[y];
	}
	bool cmp_(int x,int y){
		return mx[x]<mx[y];
	}
	void dfs(int p,int fa){
		tim++,vec.clear();
		pdfs(p,p,fa);
		// if(p==3) for(int i=1;i<=n;i++) debug("i=%lld mi=%lld mx=%lld\n",i,mi[i],mx[i]);
		std::sort(g[p].begin(),g[p].end(),cmp);
		int z=0;
		BIT::clear();
		std::sort(vec.begin(),vec.end(),cmp_);
		for(int i:vec){
			// debug("i=%lld z=%lld\n",i,mx[i]);
			while(z<g[p].size()&&r[g[p][z]]<mx[i]) ans[g[p][z]]=BIT::query(lim)-BIT::query(l[g[p][z]]-1),z++;//,debug("z=%lld g=%lld ans=%lld\n",z,g[p][z],ans[g[p][z]]),z++;
			// debug("ok\n");
			if(mi[i]>lasmx[c[i]]) BIT::add(lasmx[c[i]],-1),BIT::add(lasmx[c[i]]=mi[i],1);//,debug("ok mi=%lld\n",mi[i]);
		}
		while(z<g[p].size()) ans[g[p][z]]=BIT::query(lim)-BIT::query(l[g[p][z]]-1),z++;
		for(int i:vec) lasmx[c[i]]=-inf;
		for(int i:b[p]) if(i!=fa) dfs(i,p);
	}
}

void pdfs(int p,int fa){
	sz[p]=1;
	for(int i:a[p]){
		if(f[i]||i==fa) continue;
		pdfs(i,p);
		sz[p]+=sz[i];
	}
}

void findrt(int p,int fa,int ad){
	int s=ad;
	for(int i:a[p]) if((!f[i])&&(i!=fa)) chkmax(s,sz[i]);
	if(chkmin(mxsz,s)) h=p;
	for(int i:a[p]) if((!f[i])&&(i!=fa)) findrt(i,p,ad+sz[p]-sz[i]);
}

void compose(int rt,int fa,int p,int mi,int mx){
	chkmin(mi,p),chkmax(mx,p);
	for(int i:d[p]){
		if(vis[i]) continue;
		if(mi<l[i]||mx>r[i]) continue;
		// debug("query %lld in %lld\n",i,rt);	
		g[rt].push_back(i),vis[i]=1;
	}
	for(int i:a[p]) if(!f[i]&&(i!=fa)) compose(rt,p,i,mi,mx);
}

void dfs(int p,int fa){
	// debug("p=%lld\n",p);
	pdfs(p,0);
	mxsz=inf;
	findrt(p,0,0),p=h;
	// debug("h=%lld\n",h);
	addd(b,p,fa);
	compose(p,0,p,inf,-inf);
	f[p]=1;
	for(int i:a[p]) if(!f[i]) dfs(i,p);
}

bool Men;

signed main(){
	debug("%.6lfMB\n",(&Mbe-&Men)/1048576.0);
	read(n),read(m);
	for(int i=1;i<=n;i++) read(c[i]);
	for(int i=1;i<n;i++){
		int u,v;
		read(u),read(v);
		addd(a,u,v);
	}
	for(int i=1;i<=m;i++) read(l[i]),read(r[i]),read(x[i]),d[x[i]].push_back(i);
	Ynoi::dfs(1);
	Ynoi::init();
	// debug("ok\n");
	dfs(1,0);
	// debug("ok\n");
	rt=b[0][0];
	// debug("rt=%lld\n",rt);
	for(int i=1;i<=lim;i++) lasmx[i]=-inf;
	BIT::init();
	Tree::dfs(rt,0);
	for(int i=1;i<=m;i++) println(ans[i]);
	debug("%.6lfms\n",1e3*clock()/CLOCKS_PER_SEC);
}

CF765F Souvenirs

怎么你们的入门题都这么难。

这种大概属于第二类支配对。总的贡献对数为 \(O(n^2)\),但是实际上有用的只有 \(O(n\times\rm polylog)\) 种。一般考虑固定支配对的一个端点,考虑所有有用的另一个端点。

不妨钦定 \(i\) 是支配对中选中的一个点,只需考虑 \(a_j\ge a_i\)\(j\neq i\)\(j\)。考虑在左右某个方向上存在有用的 \(j_1\)\(j_2\),不妨设 \(i<j_1<j_2\),那么显然需要满足 \(a_i\le a_{j_2}<a_{j_1}\),同时 \((i,j_2)\) 有用的前提还需确保 \((j_1,j_2)\) 没用,则意味着 \(a_{j_2}-a_i<a_{j_1}-a_{j_2}\),不难发现此时值域减半,因此在一个方向上最多 \(O(\log V)\) 个有用点,总的有用点数量为 \(O(n\log V)\)

将所有支配对拉出来以后相当于询问 2-side 矩形最大值,扫描线拿脚维护,不需要任何脑子,时间复杂度 \(2\log\)

代码正在尝试写。

扫描线

最近感觉很多数据结构题的最后一步都是扫描线,但是对这种快速维护可差分信息的技巧还不太熟练。

二维平面上的不可差分信息维护是困难的,需要分治。但是对于可差分信息可以将其抽象成一个 4-side 矩形,差分后将其变成 3-side 或 2-side,扫描线可以自然消去一维,另一维用数据结构维护即可。

往往见到的扫描线都是可以转成 2-side,遇到转成 3-side 的扫描线只需要通过合适的转置坐标系将矩形扔到 y 轴上即可变成熟知的模型。这样可以有效避免撤销操作。

未知渠道获得的题

给出 \(n\) 个点的树,\(q\) 次查询只保留点和边编号在 \([l,r]\) 中时连通块的数量。单 \(\log\)

对于森林而言数连通块可以分别数点数和边数,对于图而言往往数代表元,这个月应该还会做到类似的题。

考虑点数是简单的,边数需要满足较多限制,两个端点都在 \([l,r]\) 之中且边自身的编号也在 \([l,r]\) 之中,这不难拆成一个 2-side 矩形数数的样子,然后直接做就好了。

UOJ637 数据结构

很好的题啊!

首先从询问出发统计有多少颜色是不好做的,从颜色出发统计哪些询问会被贡献到看上去很可以做。考察一个颜色 \(x\) 不被贡献到一个询问上的条件(直觉上这更好做),当且仅当所有 \(x\) 都被覆盖且所有 \(x-1\) 都没被覆盖。建坐标系,前者可以表示成一个 3-side 矩形,后者均摊总共有 \(O(n)\) 个矩形,将后者的每个矩形和前者求交以后扔到二维平面上就是一个很简单的问题了。时间复杂度 \(O(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define pii std::pair<int,int>
#define vint std::vector<int>
#define vpair std::vector<pii>
#define debug(...) fprintf(stderr,##__VA_ARGS__)

template<typename T>
void read(T &x){
	x=0;
	int f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9') x=x*10+(int)(c-'0'),c=getchar();
	x*=f;
}

std::stack<char>st;
template<typename T>
void print(T x){
	if(x==0) putchar('0');
	if(x<0) putchar('-'),x=-x;
	while(st.size()) st.pop();
	while(x) st.push((char)('0'+x%10)),x/=10;
	while(st.size()) putchar(st.top()),st.pop();
}

template<typename T>
void printsp(T x){
	print(x),putchar(' ');
}

template<typename T>
void println(T x){
	print(x),putchar('\n');
}

template<typename T,typename I>
bool chkmin(T &a,I b){
	if(a>b) return a=b,1;
	return 0;
}

template<typename T,typename I>
bool chkmax(T &a,I b){
	if(a<b) return a=b,1;
	return 0;
}

template<typename T,typename I>
void addedge(std::vector<I>*vec,T u,T v){
	vec[u].push_back(v);
}

template<typename T,typename I,typename K>
void addedge(std::vector<K>*vec,T u,T v,I w){
	vec[u].push_back({v,w});
}

template<typename T,typename I>
void addd(std::vector<I>*vec,T u,T v){
	addedge(vec,u,v),addedge(vec,v,u);
}

template<typename T,typename I,typename K>
void addd(std::vector<K>*vec,T u,T v,I w){
	addedge(vec,u,v,w),addedge(vec,v,u,w);
}

bool Mbe;

const int inf=1e18,MOD1=998244353,MOD2=1e9+7;

const int maxn=1e6+10;

int n,m,a[maxn],fir[maxn],lst[maxn],ans[maxn];

bool f;

vint vec[maxn];

struct Info{
	int lx,ly,rx,ry;
};

struct Info_{
	int x,y,val;
};

Info info(int lx,int ly,int rx,int ry){
	Info res;
	res.lx=lx,res.ly=ly,res.rx=rx,res.ry=ry;
	return res;
}

Info_ info_(int x,int y,int val){
	Info_ res;
	res.x=x,res.y=y,res.val=val;
	return res;
}

Info U(Info x,Info y){
	if(x.rx<y.lx||y.rx<x.lx||x.ry<y.ly||y.ry<x.ly) return f=0,x;
	return f=1,info(std::max(x.lx,y.lx),std::max(x.ly,y.ly),std::min(x.rx,y.rx),std::min(x.ry,y.ry));
}

std::vector<Info>vc;

std::vector<Info_>occ,Q;

bool cmp(Info_ x,Info_ y){
	if(x.x==y.x) return x.y<y.y;
	return x.x>y.x;
}

struct BIT{
	int s[maxn];
	int lowbit(int x){
		return x&(-x);
	}
	void upd(int k,int x){
		for(;k<=n+1;k+=lowbit(k)) s[k]+=x;
	}
	int qry(int k){
		int res=0;
		while(k) res+=s[k],k-=lowbit(k);
		return res;
	}
	void update(int l,int r,int k){
		upd(r+1,-k),upd(l,k);
	}
	int query(int k){
		return qry(k);
	}
}ds;

bool Men;

signed main(){
	debug("%.6lfMB\n",(&Mbe-&Men)/1048576.0);
	read(n),read(m);
	for(int i=1;i<=n+1;i++) fir[i]=inf;
	for(int i=0;i<=n+1;i++) vec[i].push_back(0);
	for(int i=1;i<=n;i++) read(a[i]),chkmin(fir[a[i]],i),lst[a[i]]=i,vec[a[i]].push_back(i);
	for(int i=0;i<=n+1;i++) vec[i].push_back(n+1);
	for(int i=1;i<=n+1;i++){
		Info w;
		if(fir[i]!=inf){
			w.lx=1,w.ly=lst[i],w.rx=fir[i],w.ry=n;
		}
		for(int j=0;j<vec[i-1].size()-1;j++){
			int pos=vec[i-1][j],lpos=vec[i-1][j+1];
			if(pos==lpos-1) continue;
			Info x=info(pos+1,pos+1,lpos-1,lpos-1);
			if(fir[i]==inf) vc.push_back(x);
			else{
				vc.push_back(U(w,x));
				if(!f) vc.pop_back();
			}
		}
	}
	for(Info i:vc){
		occ.push_back(info_(i.rx,i.ry,1));
		if(i.lx>1) occ.push_back(info_(i.lx-1,i.ry,-1));
		if(i.ly>1) occ.push_back(info_(i.rx,i.ly-1,-1));
		if(i.lx>1&&i.ly>1) occ.push_back(info_(i.lx-1,i.ly-1,1));
	}
	std::sort(occ.begin(),occ.end(),cmp);
	for(int i=1;i<=m;i++){
		int qx,qy;
		read(qx),read(qy),Q.push_back(info_(qx,qy,i));
	}
	std::sort(Q.begin(),Q.end(),cmp);
	int z=0,z_=0;
	for(int i=n;i>=1;i--){
		while(z<occ.size()&&occ[z].x==i) ds.update(1,occ[z].y,occ[z].val),z++;
		while(z_<Q.size()&&Q[z_].x==i) ans[Q[z_].val]=ds.query(Q[z_].y),z_++;
	}
	for(int i=1;i<=m;i++) println(n+1-ans[i]);
	debug("%.6lfms\n",1e3*clock()/CLOCKS_PER_SEC);
}

CF2094H

老年选手不睡觉,胡一下 div4 的 H。感觉算比较 naive 的分块了,大概做了三分钟。

观察到一个数只会除 \(\log\) 次。那么考虑暴力找出这 \(\log\) 次的位置。这个数据范围和时间限制看上去就很像 \(O(q\sqrt n\log n)\)。分块,块内维护 \(x\) 在这个块内最后一个能除的位置是啥,这样每找到一个位置会付出 \(O(\sqrt n)\) 的代价,均摊一下复杂度就对了。空间 \(O(n\sqrt n)\)

当然预处理不能扫一遍,根据经典调和级数结论不难做到预处理 \(O(n\log\sqrt{n})\)

UOJ958 彩色括号

不难注意到整个序列合法这个条件没用,反证即可。考虑如何使得一个括号序列以尽可能小的代价合法,这是经典的,将前面的 ) 改成 (,将后面的 ( 改成 ) 直到数量平衡,然后将第一个 ) 和最后一个 ( 同时改掉直到合法。不难从整个限制推出每种颜色左右括号数需要相同,因此根据贪心,先将每种括号改到左右括号数量相同,此时得到的序列为 \(a'\)。接下来要做的事情就是同时改掉某种颜色第一个 ) 和最后一个 (,设第 \(i\) 颜色这么做了 \(x_i\) 次,那么在每一处前缀和非负等价于对于任意 \(1\le j\le n,1\le k\le 3\),有一个式子 \(\ge0\),这个式子的形式感觉官解中写的有点错。将 \(\min(a,b)\ge0\) 拆成 \(a\ge0\& b\ge0\),可以得到一个整数线性规划问题,后面的步骤有点太困难了。

CF????F

赛时感觉出来了支配对数量不会很多,于是想找支配对,思考了半天不会做。上文化课的时候发现直接找本质不同的数对就是对的,这也太厉害了。

一个初步的 observation 是对于一个区间,nor 在第 \(i\) 位上为何值取决于最后一段 \(0\) 长度的奇偶性。感受一下整道题,存在一个十分暴力的做法是枚举右端点,考虑每一个左端点形成的支配对,更新答案,实际上,整个本质不同数量是 \(O(\log V)\) 的。只考虑所有奇偶性相同的左端点位置,设存在 \(>\log V\) 个左端点对应的结果不同,充分感受得知这等价于对于每一位在右端点前最后一个 \(1\) 需要均匀插入所有 \(\log V\) 个左端点中,由于 \(1\) 的数量为 \(\log V\) 个,所以奇偶性相同的左端点数量也只有 \(\log V\) 个。

ucup35Krakow J.Sumotonic Sequences

你妈的。

定义两个相同长度序列的和是一个序列,对应位置相加。不难发现两个单调性相同的和的单调性不变,因此未免可以将问题表述为“判断一个序列能否被一个单调不降和一个单调不升序列相加得到”,题目中限定元素必须非负,不难发现若可负则必定有解。感受一下,直接从序列的角度出发考虑看上去非常困难,考虑从差分角度开始考虑,单调不降序列的差分为非正,单调不升序列差分除了第一个数外为非负。这样我们就只需要找两个序列使得其相加后差分等于原序列的差分。不难发现等价于两个序列差分相加后等于原序列的差分。

进一步根据样例去感受。我们发现从“构造一个尽可能优的单调不升序列”出发思考很有前途。设原序列差分为 \(d_i=a_i-a_{i-1}\),构造出的单调不升序列差分为 \(d'_i\),则考虑其差序列 \(x_i=d_i-d'_i\),显然地,需要满足 \(x_i\ge0\)。同时发现还需满足 \(d'_1+d'_2+\dots+d'_n\ge0\)。贪心地让每个 \(d'_i\) 都最大即可。则可以得出构造方式:\(d'_i=\min(0,d_i)\),此时有解等价于 \(\sum\min(0,d_i)\ge0\)

考虑修改对差分数组的影响,对 \(d_{l+1\sim r}\) 这一段相当于区间 \(+D\),对 \(d_l\) 单点加 \(S\),对 \(d_{r+1}\) 单点加 \(-[S+(r-l)\times D]\),这个 \(\min(0,d_i)\) 看上去很魔怔,仔细阅读题面不难发现限制保证的是 \(n+k\le4\times 10^5\),让人联想到均摊。可以注意到每次修改使得对 \(d_i\) 增量为负的只有 \(1\) 个点,线段树维护所有 \(d_i<0\) 的点后暴力修改即可,势能可以得到 \(O((n+k)\log n)\) 的时间复杂度。

写成文字以后感觉思路很自然啊!不知道赛时在想什么,瞪着自己的 typora 蹬出一个颜色段均摊维护二阶差分,真唐。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define pii std::pair<int,int>
#define vint std::vector<int>
#define vpair std::vector<pii>
#define debug(...) fprintf(stderr,##__VA_ARGS__)

template<typename T>
void read(T &x){
	x=0;
	int f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9') x=x*10+(int)(c-'0'),c=getchar();
	x*=f;
}

std::stack<char>st;
template<typename T>
void print(T x){
	if(x==0) putchar('0');
	if(x<0) putchar('-'),x=-x;
	while(st.size()) st.pop();
	while(x) st.push((char)('0'+x%10)),x/=10;
	while(st.size()) putchar(st.top()),st.pop();
}

template<typename T>
void printsp(T x){
	print(x),putchar(' ');
}

template<typename T>
void println(T x){
	print(x),putchar('\n');
}

template<typename T,typename I>
bool chkmin(T &a,I b){
	if(a>b) return a=b,1;
	return 0;
}

template<typename T,typename I>
bool chkmax(T &a,I b){
	if(a<b) return a=b,1;
	return 0;
}

template<typename T,typename I>
void addedge(std::vector<I>*vec,T u,T v){
	vec[u].push_back(v);
}

template<typename T,typename I,typename K>
void addedge(std::vector<K>*vec,T u,T v,I w){
	vec[u].push_back({v,w});
}

template<typename T,typename I>
void addd(std::vector<I>*vec,T u,T v){
	addedge(vec,u,v),addedge(vec,v,u);
}

template<typename T,typename I,typename K>
void addd(std::vector<K>*vec,T u,T v,I w){
	addedge(vec,u,v,w),addedge(vec,v,u,w);
}

bool Mbe;

const int inf=3e18+1,MOD1=998244353,MOD2=1e9+7;

const int maxn=2e5+10;

int T,n,k,a[maxn],d[maxn],dd[maxn];

int min(int a,int b){
	if(a>b) return b;
	return a;
}

int max(int a,int b){
	if(a>b) return a;
	return b;
}

struct SegmentTree{
	int tot,l[maxn*2],r[maxn*2],ls[maxn*2],rs[maxn*2],s[maxn*2],num[maxn*2],mi[maxn*2],lz[maxn*2];
	void clear(){
		tot=0;
	}
	void push_up(int p){
		s[p]=s[ls[p]]+s[rs[p]];
		mi[p]=max(mi[ls[p]],mi[rs[p]]);
		num[p]=num[ls[p]]+num[rs[p]];
	}
	int build(int L,int R){
		int p=++tot;
		l[p]=L,r[p]=R;
		lz[p]=0;
		if(L==R){
			// debug("L=%lld dd=%lld\n",L,dd[L]);
			if(d[L]<0) s[p]=d[L],mi[p]=d[L],num[p]=1;
			else s[p]=0,mi[p]=-inf,num[p]=0;
			return p;
		}
		int mid=(L+R)>>1;
		ls[p]=build(L,mid),rs[p]=build(mid+1,R);
		push_up(p);
		return p;
	}
	void push_down(int p){
		lz[ls[p]]+=lz[p],lz[rs[p]]+=lz[p];
		s[ls[p]]+=num[ls[p]]*lz[p],s[rs[p]]+=num[rs[p]]*lz[p],mi[ls[p]]+=lz[p],mi[rs[p]]+=lz[p];
		lz[p]=0;
	}
	void update(int p,int L,int R,int k){
		if(l[p]==r[p]){
			if(s[p]==0) return ;
			if(s[p]+k>=0) s[p]=0,mi[p]=-inf,num[p]=0;
			else s[p]+=k,num[p]=1,mi[p]=s[p];
			return ;
		}
		push_down(p);
		if(l[p]>=L&&r[p]<=R){
			if(mi[p]+k>=0){
				update(ls[p],L,R,k);
				update(rs[p],L,R,k);
				push_up(p);
				return ;
			}
			mi[p]+=k,lz[p]+=k,s[p]+=num[p]*k;
			return ;
		}
		// push_down(p);
		int mid=(l[p]+r[p])>>1;
		if(mid>=L) update(ls[p],L,R,k);
		if(R>mid) update(rs[p],L,R,k);
		push_up(p);
	}
	void modify(int p,int pos,int k){
		if(l[p]==r[p]){
			// debug("pos=%lld s=%lld\n",pos,s[p]);
			s[p]=k,mi[p]=k;
			// debug("%lld %lld qwq\n",pos,s[p]);
			if(s[p]!=0)num[p]=1;
			else num[p]=0,mi[p]=-inf;
			return ;
		}
		push_down(p);
		int mid=(l[p]+r[p])>>1;
		if(mid>=pos) modify(ls[p],pos,k);
		else modify(rs[p],pos,k);
		push_up(p);
	}
	int query(){
		return s[1];
	}
}ds;

struct SegmentTree_{
	int tot,l[maxn*2],r[maxn*2],ls[maxn*2],rs[maxn*2],sum[maxn*2],lz[maxn*2];
	void clear(){
		tot=0;
	}
	void  push_up(int p){
		sum[p]=sum[ls[p]]+sum[rs[p]];
	}
	int build(int L,int R){
		int p=++tot;
		l[p]=L,r[p]=R;
		lz[p]=0;
		if(L==R) return sum[p]=d[L],p;
		int mid=(l[p]+r[p])>>1;
		ls[p]=build(L,mid),rs[p]=build(mid+1,R);
		push_up(p);
		return p;
	}
	void push_down(int p){
		sum[ls[p]]+=lz[p]*(r[ls[p]]-l[ls[p]]+1),sum[rs[p]]+=lz[p]*(r[rs[p]]-l[rs[p]]+1);
		lz[ls[p]]+=lz[p],lz[rs[p]]+=lz[p],lz[p]=0;
		return ;
	}
	void update(int p,int L,int R,int x){
		// debug("[%lld,%lld]\n",l[p],r[p]);
		if(l[p]>=L&&r[p]<=R){
			sum[p]+=x*(r[p]-l[p]+1);
			lz[p]+=x;
			return ;
		}
		push_down(p);
		int mid=(l[p]+r[p])>>1;
		if(mid>=L) update(ls[p],L,R,x);
		if(R>mid) update(rs[p],L,R,x);
		push_up(p);
	}
	int query(int p,int L,int R){
		// debug("[%lld,%lld]\n",l[p],r[p]);
		if(l[p]>=L&&r[p]<=R) return sum[p];
		int mid=(l[p]+r[p])>>1;
		int res=0;
		push_down(p);
		if(mid>=L) res+=query(ls[p],L,R);
		if(R>mid) res+=query(rs[p],L,R);
		return res;
	}
}ds_;

int calc(int a,int b,int c){
	return a+(b-1)*c;
}

bool Men;

signed main(){
	debug("%.6lfMB\n",(&Mbe-&Men)/1048576.0);
	read(T);
	int cnt=0;
	while(T--){
		read(n),read(k);
		for(int i=1;i<=n;i++) read(a[i]);
		for(int i=1;i<=n;i++) d[i]=a[i]-a[i-1],dd[i]=d[i]-d[i-1];
		ds_.clear(),ds_.build(1,n);
		ds.clear(),ds.build(1,n);
		// debug("qwq=%lld\n",ds.query());
		if(ds.query()+ds_.query(1,1,1)>=0) printf("YES\n");
		else printf("NO\n");
		while(k--){
			int l,r,s,d;
			read(l),read(r),read(s),read(d);
			// debug("l=%lld r=%lld s=%lld d=%lld\n",l,r,s,d);
			int p=calc(s,r-l+1,d);
			if(l<r)ds_.update(1,l+1,r,d);
			ds_.update(1,l,l,s);
			if(r<n)ds_.update(1,r+1,r+1,-p);
			if(l<r) ds.update(1,l+1,r,d);
			ds.update(1,l,l,s);
			if(r<n){
				int g1=ds_.query(1,1,r),g2=ds_.query(1,1,r+1);
				ds.modify(1,r+1,min(0ll,g2-g1));
			}
			// int ss=0;
			// for(int i=1;i<=n;i++) ss+=min(0,ds_.query(1,i,i));//,debug("qwq=%lld\n",ds_.query(1,1,i));
			// ss+=ds_.query(1,1,1);
			// if(ss>=0) printf("YES\n");
			// else printf("NO\n");
			// cnt++;
			// if(cnt>=59) return 0;
			// continue;
			int sum=ds.query();
			// debug("qwq=%lld\n",sum);
			if(sum+ds_.query(1,1,1)>=0) printf("YES\n");
			else printf("NO\n");
			// for(int i=1;i<=n;i++) debug("i=%lld d=%lld\n",i,ds_.query(1,i,i));
		}
	}
	debug("%.6lfms\n",1e3*clock()/CLOCKS_PER_SEC);
}

未知渠道获得的题

给出一棵树上的若干点对,每个点对有 \(p_i\) 的概率消失,一个点对有贡献当且仅当该点对没消失且和所有没消失的点对没有交边,贡献为 \(1\),求期望总贡献。

首先根据期望的线性性将贡献拆到每个点对上,该点对产生的贡献就是 \(p_i\times\prod_{\text{x 和 i 有交边}}p_x\)。后面的那堆东西考虑经典的点边容斥。两个点对路径的交必然是一条路径,设该条路径上长为 \(x\) 的链有 \(cnt_x\) 条,显然 \(cnt_x\) 单调不升,同时由于是路径,有交边可以刻画成 \(cnt_1-cnt_2=1\),没有交边就是 \(cnt_1-cnt_2=0\),因此我们惊人地发现,式子上后面 \(p_x\) 的贡献即为 \(p_x^{cnt_1-cnt_2}\),拆成 \(p_x^{cnt_1}\)\(p_x^{-cnt_2}\) 分别维护。由于没有修改,直接树上差分即可。

该题是链的情况,对于一般图来说,\(cnt_x\) 不一定单调不升,例如考虑一棵深度为 \(2\) 的完全三叉树。至于凸性,我猜测是凸的,不过凸性几乎没有什么用。

P5298[PKUWC2018] Minimax

shengqile.cpp。一道题做一年?在线段树合并上疑似有点过于彩笔了,题解看了一年还没看懂。

转移式子:\(f_{x,i}=f_{l,i}\times[p\times(\sum\limits_{j=1}^if_{r,j})+(1-p)(\sum\limits_{j=i}^nf_{r,j})]+f_{r,i}\times[p\times(\sum\limits_{j=1}^if_{l,j})+(1-p)(\sum\limits_{j=i}^nf_{l,j})]\)

考虑整个线段树合并的过程,我们在线段树上维护区间内概率的和,那么如果两棵树对应区间都有点,递归修改,否则说明一棵树没有点,此时另一棵树的权值已经能被 \([1,l-1]\)\([r+1,n]\) 的和确定地修改下来(乘上系数 \(coef\),此时由于另一棵树没有点,因此可以唯一确定 \(coef\)),对其打标记即可。都有点的时候,递归修改完了之后做 push_up 合并信息即可,注意 push_downcmd 的题解写得比较深刻

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define pii std::pair<int,int>
#define vint std::vector<int>
#define vpair std::vector<pii>
#define debug(...) fprintf(stderr,##__VA_ARGS__)

template<typename T>
void read(T &x){
	x=0;
	int f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9') x=x*10+(int)(c-'0'),c=getchar();
	x*=f;
}

std::stack<char>st;
template<typename T>
void print(T x){
	if(x==0) putchar('0');
	if(x<0) putchar('-'),x=-x;
	while(st.size()) st.pop();
	while(x) st.push((char)('0'+x%10)),x/=10;
	while(st.size()) putchar(st.top()),st.pop();
}

template<typename T>
void printsp(T x){
	print(x),putchar(' ');
}

template<typename T>
void println(T x){
	print(x),putchar('\n');
}

template<typename T,typename I>
bool chkmin(T &a,I b){
	if(a>b) return a=b,1;
	return 0;
}

template<typename T,typename I>
bool chkmax(T &a,I b){
	if(a<b) return a=b,1;
	return 0;
}

template<typename T,typename I>
void addedge(std::vector<I>*vec,T u,T v){
	vec[u].push_back(v);
}

template<typename T,typename I,typename K>
void addedge(std::vector<K>*vec,T u,T v,I w){
	vec[u].push_back({v,w});
}

template<typename T,typename I>
void addd(std::vector<I>*vec,T u,T v){
	addedge(vec,u,v),addedge(vec,v,u);
}

template<typename T,typename I,typename K>
void addd(std::vector<K>*vec,T u,T v,I w){
	addedge(vec,u,v,w),addedge(vec,v,u,w);
}

bool Mbe;

const int inf=1e18,MOD1=998244353,MOD2=1e9+7;

const int maxn=3e5+10;

int n,fa[maxn],ls[maxn],rs[maxn],p[maxn],rt[maxn],b[maxn];

void add(int &a,int b){
	a+=b;
	if(a>=MOD1) a-=MOD1;
}

int ksm(int x,int y,int p=MOD1){
	if(y==0) return 1;
	int z=ksm(x,y>>1,p);
	z=z*z%p;
	if(y&1) z=z*x%p;
	return z;
}

struct SegmentTree{
	int ls[maxn*40],rs[maxn*40],s[maxn*40],tot,lz[maxn*40];
	void clear(){
		tot=0;
	}
	void push_up(int p){
		s[p]=s[ls[p]];
		add(s[p],s[rs[p]]);
	}
	void newnode(int &p){
		if(p) return ;
		p=++tot,lz[p]=1;
	}
	void push_down(int p){
		s[ls[p]]=s[ls[p]]*lz[p]%MOD1,s[rs[p]]=s[rs[p]]*lz[p]%MOD1;
		lz[ls[p]]=lz[ls[p]]*lz[p]%MOD1,lz[rs[p]]=lz[rs[p]]*lz[p]%MOD1;
		lz[p]=1;
	}
	void adD(int &p,int l,int r,int pos,int k){
		newnode(p);
		if(l==r){
			s[p]=k;
			return ;
		}
		push_down(p);
		int mid=(l+r)>>1;
		if(mid>=pos) adD(ls[p],l,mid,pos,k);
		else adD(rs[p],mid+1,r,pos,k);
		push_up(p);
	}
	void merge(int &p,int &p2,int l,int r,int a,int b,int c,int d,int _p,int ps){
		if(!p&&!p2) return ;
		if(!p){
			p=p2;
			// debug("[%lld,%lld] %lld %lld %lld %lld %lld %lld\n",l,r,a,b,c,d,_p,ps);
			s[p]=s[p]*((_p*a%MOD1+ps*b%MOD1)%MOD1)%MOD1;
			lz[p]=lz[p]*((_p*a%MOD1+ps*b%MOD1)%MOD1)%MOD1;
			return ;
		}
		if(!p2){
			lz[p]=lz[p]*((_p*c%MOD1+ps*d%MOD1)%MOD1)%MOD1;
			s[p]=s[p]*((_p*c%MOD1+ps*d%MOD1)%MOD1)%MOD1;
			return ;
		}
		push_down(p),push_down(p2);
		int mid=(l+r)>>1,qwq=s[ls[p]],wqw=s[ls[p2]];
		merge(ls[p],ls[p2],l,mid,a,(b+s[rs[p]])%MOD1,c,(d+s[rs[p2]])%MOD1,_p,ps);
		merge(rs[p],rs[p2],mid+1,r,(a+qwq)%MOD1,b,(c+wqw)%MOD1,d,_p,ps);
		push_up(p);
	}
	int query(int p,int l,int r,int pos){
		if(!p) return 0;
		if(l==r) return s[p];
		push_down(p);
		int mid=(l+r)>>1;
		if(mid>=pos) return query(ls[p],l,mid,pos);
		else return query(rs[p],mid+1,r,pos);
	}
}ds;

void dfs(int u){
	if(!ls[u]){
		ds.adD(rt[u],1,n,p[u],1);
		return ;
	}
	dfs(ls[u]);
	rt[u]=rt[ls[u]];
	if(!rs[u]) return ;
	dfs(rs[u]);
	ds.merge(rt[u],rt[rs[u]],1,n,0,0,0,0,p[u],(MOD1-p[u]+1)%MOD1);
}

bool Men;

signed main(){
	debug("%.6lfMB\n",(&Mbe-&Men)/1048576.0);
	read(n);
	ds.clear();
	for(int i=1;i<=n;i++){
		read(fa[i]);
		if(!ls[fa[i]]) ls[fa[i]]=i;
		else rs[fa[i]]=i;
	}
	for(int i=1;i<=n;i++) read(p[i]);
	int cnt=0,val=ksm(10000,MOD1-2);
	for(int i=1;i<=n;i++)
		if(!ls[i]) b[++cnt]=p[i];
		else /*p[i]=10000-p[i],*/p[i]=p[i]*val%MOD1;
	std::sort(b+1,b+cnt+1);
	for(int i=1;i<=n;i++) if(!ls[i]) p[i]=std::lower_bound(b+1,b+cnt+1,p[i])-b;
	dfs(1);
	int ans=0,d;
	for(int i=1;i<=cnt;i++) d=ds.query(rt[1],1,n,i),add(ans,i*b[i]%MOD1*d%MOD1*d%MOD1);//,debug("i=%lld b=%lld D=%lld\n",i,b[i],ds.query(rt[1],1,n,i));
	println(ans);
	debug("%.6lfms\n",1e3*clock()/CLOCKS_PER_SEC);
}

P10064 [SNOI2024]公交线路

有弱化版 CF1827E。首先任取非一度点作为根。

该题无论何种思考方式,都需要注意到的事情是只需要叶子两两可达就能判定整棵树合法。设 \(S_x\) 代表 \(x\) 一步能到达的所有点,那么叶子两两可达这件事情就可以刻画为任意两个叶子 \(xy\),满足 \(S_x\cap S_y\neq\emptyset\),有一个引理:

在树上钦定若干连通块,则连通块两两交非空等价于所有连通块交非空。

感受一下,反证可以说明。因此我们只需对 \(\cap S_x\neq\emptyset\) 的方案数计数,我们扔掉所有两端均不为叶子的路径(显然,这些路径的选择不影响答案)。类似上上个题,使用点边容斥,对必须选中某个点和必须选中某条边的方案数进行计数。考虑必须选中 \(x\) 这个点。如果 \(x\) 为叶子那么计数是简单的,否则将根换到 \(x\)。看上去后面的计数直接做就很不好做,考虑容斥,钦定一些叶子不经过 \(x\),该容斥做 \(O(n^2)\) 不是很困难,记 \(f_i\) 代表钦定了 \(i\) 个叶子不经过 \(x\) 的方案数,对 \(i\) 的所有子树做背包合并即可。总时间复杂度 \(O(n^3)\)

考虑为什么做容斥,原因是要是选中的两个端点都是叶子且路径经过 \(x\) 那就错了,但是将根换到 \(x\) 以后,对于最后一棵子树我们是不存在这个限制的,因此可以不对最后一棵子树容斥,用直接计算替代之,将最后一棵子树视作 \(x\) 子树外,此时的总时间复杂度等价于树形背包,为 \(O(n^2)\)

上面写的有点不说人话了,让我们展示式子。在一开始的 \(O(n^3)\) 容斥中,忽略掉所有容斥系数 \((-1)\) 和所有不经过 \(x\) 的边(这些边选不选均不影响)后,式子为:

\[f'_{i+j}\leftarrow f_i\times\binom{leaf_y}{j}\times 2^{(s-i)(siz_y-j)} \]

\(s-i\) 为前面子树中不被限制的点的数量,\(siz_y-j\) 为当前子树中不被限制的点的数量,这些点可以任意连,对于被限制的,只能选择不经过 \(x\) 的边,我们将在最后统一进行计算。

\(O(n^2)\) 中,我们将所有原树子树内的儿子容斥完,设考虑完所有容斥系数和不经过 \(x\) 的边后,至少有 \(i\) 个叶子不经过 \(x\) 的方案数为 \(x\)。我们强制让外面的点全部经过 \(x\),不难发现结合上容斥系数这是对的:

\[sum\leftarrow f_i\times2^{p+(tot-leaf)(siz_x-i)}(2^{siz_x-i}-1)^{leaf} \]

其中 \(p\) 代表子树外不经过 \(x\) 的方案数,\(tot\) 为子树外的总点数,\(leaf\) 为子树外的叶子数。\(2^{siz_x-i}-1\) 代表子树内至少要选中一个点与子树外某个叶子匹配。这就是对于必经点的计算。

对于必经边是一样的,情况更为简单,因为一条边对应的只有两个子树。直接仿效上面的做法进行合并,让 \(g_i\) 代表至少 \(i\) 个叶子不经过 \(x\) 的方案数(显然,这 \(i\) 个叶子都应该选在某一棵子树里面):

\[g_i\leftarrow 2^{(siz_x-i)(siz_y-leaf)}(2^{siz_x-i}-1)^{leaf} \]

同样地,没有考虑不经过必经边的边。总时间复杂度 \(O(n^2)\)

如果直接从 CF1827E 的角度思考,那么会得到和 cxl 一样的做法,枚举最深的 \(anc\) 后做类似的容斥,其本质和点边容斥一样。

P6773 [NOI2020]命运

和上上题一样。

写出一个 \(O(n^2)\) 的 dp 是极容易的,\(f_{i,j}\) 代表考虑子树 \(i\),最深的没被满足的限制深度为 \(j\),一个限制的深度定义为 \(u\) 的深度。直接子树合并就有转移:\(f'_{i,j}=f_{i,j}\times(f_{x,k\le dep_i}+f_{x,k\le j})+f_{i,k<j}\times f_{x,j}\)。按照上上题直接线段树合并即可做到单 \(\log\)。注意线段树合并的时间复杂度总是和删除的节点数同阶。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define pii std::pair<int,int>
#define vint std::vector<int>
#define vpair std::vector<pii>
#define debug(...) fprintf(stderr,##__VA_ARGS__)

template<typename T>
void read(T &x){
	x=0;
	int f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9') x=x*10+(int)(c-'0'),c=getchar();
	x*=f;
}

std::stack<char>st;
template<typename T>
void print(T x){
	if(x==0) putchar('0');
	if(x<0) putchar('-'),x=-x;
	while(st.size()) st.pop();
	while(x) st.push((char)('0'+x%10)),x/=10;
	while(st.size()) putchar(st.top()),st.pop();
}

template<typename T>
void printsp(T x){
	print(x),putchar(' ');
}

template<typename T>
void println(T x){
	print(x),putchar('\n');
}

template<typename T,typename I>
bool chkmin(T &a,I b){
	if(a>b) return a=b,1;
	return 0;
}

template<typename T,typename I>
bool chkmax(T &a,I b){
	if(a<b) return a=b,1;
	return 0;
}

template<typename T,typename I>
void addedge(std::vector<I>*vec,T u,T v){
	vec[u].push_back(v);
}

template<typename T,typename I,typename K>
void addedge(std::vector<K>*vec,T u,T v,I w){
	vec[u].push_back({v,w});
}

template<typename T,typename I>
void addd(std::vector<I>*vec,T u,T v){
	addedge(vec,u,v),addedge(vec,v,u);
}

template<typename T,typename I,typename K>
void addd(std::vector<K>*vec,T u,T v,I w){
	addedge(vec,u,v,w),addedge(vec,v,u,w);
}

bool Mbe;

const int inf=1e18,MOD1=998244353,MOD2=1e9+7;

const int maxn=5e5+10;

int n,m,dep[maxn],lim[maxn],s[maxn],rt[maxn];

vint a[maxn];

void add(int &a,int b){
	a+=b;
	if(a>=MOD1) a-=MOD1;
}

namespace INIT{
	void dfs(int p,int fa){
		dep[p]=dep[fa]+1;
		for(int i:a[p]) if(i!=fa) dfs(i,p);
	}
}

// namespace BF{
// 	void dfs(int p,int fa){
// 		for(int i:a[p]) if(i!=fa) dfs(i,p);
// 		f[p][lim[p]]=1;
// 		for(int i:a[p]){
// 			if(i==fa) continue;
// 			int tot=0;
// 			for(int j=0;j<=n;j++) add(tot,f[p][j]);
// 			s[0]=f[i][0];
// 			for(int j=1;j<=n;j++) s[j]=s[j-1],add(s[j],f[i][j]);
// 			for(int j=n;j>=0;j--) add(tot,MOD1-f[p][j]),f[p][j]=(f[p][j]*s[dep[p]]%MOD1+f[p][j]*s[j]%MOD1+tot*f[i][j])%MOD1;
// 		}
// 		// for(int j=0;j<=n;j++) debug("p=%lld j=%lld f=%lld\n",p,j,f[p][j]);
// 		return ;
// 	}
// 	void solve(){
// 		dfs(1,0);
// 		println(f[1][0]);
// 	}
// }

namespace HereIsWhy{
	struct SegementTree{
		int ls[maxn*30],rs[maxn*30],s[maxn*30],lz[maxn*30],tot;
		void clear(){
			tot=0;
		}
		void push_up(int p){
			s[p]=s[ls[p]],add(s[p],s[rs[p]]);
		}
		void newnode(int &p){
			if(p) return ;
			p=++tot,lz[p]=1;
		}
		void push_down(int p){
			s[ls[p]]=s[ls[p]]*lz[p]%MOD1,s[rs[p]]=s[rs[p]]*lz[p]%MOD1,lz[ls[p]]=lz[ls[p]]*lz[p]%MOD1,lz[rs[p]]=lz[rs[p]]*lz[p]%MOD1;
			lz[p]=1;
		}
		void adD(int &p,int l,int r,int pos){
			newnode(p);
			if(l==r){
				s[p]++;
				return ;
			}
			int mid=(l+r)>>1;
			if(mid>=pos) adD(ls[p],l,mid,pos);
			else adD(rs[p],mid+1,r,pos);
			push_up(p);
		}
		void merge(int &p,int &p2,int l,int r,int a,int b){
			if(!p&&!p2) return ;
			// debug("[%lld,%lld] %lld %lld\n",l,r,a,b);
			if(!p){
				p=p2;
				s[p]=s[p]*a%MOD1,lz[p]=lz[p]*a%MOD1;
				return ;
			}
			if(!p2){
				s[p]=s[p]*b%MOD1,lz[p]=lz[p]*b%MOD1;
				return ;
			}
			if(l==r){
				// debug("l=%lld r=%lld a=%lld b=%lld\n",l,r,a,b);
				s[p]=s[p]*((b+s[p2])%MOD1)%MOD1;
				add(s[p],s[p2]*a%MOD1);
				return ;
			}
			push_down(p),push_down(p2);
			int mid=(l+r)>>1;
			merge(rs[p],rs[p2],mid+1,r,(a+s[ls[p]])%MOD1,(b+s[ls[p2]])%MOD1);
			merge(ls[p],ls[p2],l,mid,a,b);
			push_up(p);
			// debug("[%lld,%lld] %lld\n",l,r,s[p]);
		}
		int query(int p,int l,int r,int L,int R){
			if(l>=L&&r<=R) return s[p];
			push_down(p);
			int mid=(l+r)>>1,res=0;
			if(mid>=L) add(res,query(ls[p],l,mid,L,R));
			if(R>mid) add(res,query(rs[p],mid+1,r,L,R));
			return res;
		}
	}ds;
	void dfs(int p,int fa){
		for(int i:a[p]) if(i!=fa) dfs(i,p);
		ds.adD(rt[p],0,n,lim[p]);
		for(int i:a[p]){
			if(i==fa) continue;
			int val=ds.query(rt[i],0,n,0,dep[p]);
			// debug("begin %lld %lld\n",p,i);
			ds.merge(rt[p],rt[i],0,n,0,val);
		}
	}
	void solve(){
		dfs(1,0);
		println(ds.query(rt[1],0,n,0,0));
	}
}

// struct SegmentTree{
	// int ls[maxn*2],rs[maxn*2],s[maxn*2],lz[maxn*2];
// }

bool Men;

signed main(){
	debug("%.6lfMB\n",(&Mbe-&Men)/1048576.0);
	read(n);
	for(int i=1;i<n;i++){
		int u,v;
		read(u),read(v);
		addd(a,u,v);
	}
	INIT::dfs(1,0);
	read(m);
	for(int i=1;i<=m;i++){
		int u,v;
		read(u),read(v);
		chkmax(lim[v],dep[u]);
	}
	// BF::solve();/
	HereIsWhy::solve();
	debug("%.6lfms\n",1e3*clock()/CLOCKS_PER_SEC);
}

至此世界线收束。


posted @ 2025-04-26 22:32  BYR_KKK  阅读(120)  评论(0)    收藏  举报