11月杂题

颓你妈。

零.合影

和 KING_OF_TURTLE 和 CDFLS_mao_zx 在 CSP-S 那天的考场门口的合影/cy

大家可以猜猜我是哪个人(bushi

大概以后只有考场才能和 KOT 在一起了/kk

一. URAL 2124 Algebra on Segment

题意:给出序列和质数 \(P\)

  1. 区间乘。 2. 区间询问:区间内的数模 \(P\) 意义下构成的乘法群。

\(n,m\le 10^5\)

数论题。

一个 naive 的想法是:求原根,把每个数表示成 \(g^x\) 的形式,操作可转换成区间加,区间 gcd。

但因为需要多次 BSGS,复杂度飞起。

于是考虑另一个想法:求阶。算出来每个数的阶,然后 lcm 起来就是答案。

发现 \(a_1,a_2,…,a_n\)\(a_1,a_1/a_2,…,a_{n-1}/a_n\) 构成的群相同。

于是查询 \([l,r]\) 时先把 \(a_{l}/a_{l+1}\)\(a_{r-1}/a_{r}\) 的阶的 lcm 求出来,然后再单独求 \(a_l\) 并求出它的阶。

于是我们维护两线段树:

第一个,维护“商”分数组的阶的 lcm,单点修改,区间 lcm 。

第二个,维护 \(a_i\) 的值,区间乘,单点查询。

这个题就做完了,因为求阶是很快的。

view code
#include<bits/stdc++.h>
using namespace std;
int mod,n,q,a[101000];
vector<int>ys;
int qpow(int a,int b){
	int c=1;
	for(;b;b>>=1){
		if(b&1)c=1ll*a*c%mod;
		a=1ll*a*a%mod;
	}
	return c;
}
int o[30],pc[30],m;
int qj(int x){
	int oi=mod-1;
	for(int i=1;i<=m;i++){
		for(int j=1;j<=pc[i];j++){
			if(qpow(x,oi/o[i])==1)oi/=o[i];
			else break;
		}
	}
	return oi;
}
int cf[100100],gc[401000],tag[401000];
int gcd(int a,int b){
	if(!b)return a;
	return gcd(b,a%b);
}
int lcm(int a,int b){
	return a/gcd(a,b)*b;
}
void build(int p,int l,int r){
	if(l==r){
		gc[p]=qj(cf[l]);
		return;
	}
	int mid=(l+r)>>1;
	build(p<<1,l,mid),build(p<<1|1,mid+1,r);
	gc[p]=lcm(gc[p<<1],gc[p<<1|1]);
}
void up(int p,int l,int r,int x){
	if(l==r){
		gc[p]=qj(cf[l]);
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid)up(p<<1,l,mid,x);
	else up(p<<1|1,mid+1,r,x);
	gc[p]=lcm(gc[p<<1],gc[p<<1|1]);
}
void uk(int p,int l,int r,int x,int y,int z){
	if(x<=l&&r<=y){
		tag[p]=1ll*tag[p]*z%mod;
		return;
	}
	if(y<l||x>r)return;int mid=(l+r)>>1;
	uk(p<<1,l,mid,x,y,z),uk(p<<1|1,mid+1,r,x,y,z);
}
int ask(int p,int l,int r,int x){
	if(l==r)return tag[p];
	int mid=(l+r)>>1;
	return 1ll*tag[p]*((x<=mid)?ask(p<<1,l,mid,x):ask(p<<1|1,mid+1,r,x))%mod; 
}
int ask2(int p,int l,int r,int x,int y){
	if(x<=l&&r<=y)return gc[p];
	int mid=(l+r)>>1;
	if(y<=mid)return ask2(p<<1,l,mid,x,y);
	if(x>mid)return ask2(p<<1|1,mid+1,r,x,y);
	return lcm(ask2(p<<1,l,mid,x,y),ask2(p<<1|1,mid+1,r,x,y));
}
int main(){
	scanf("%d%d%d",&mod,&n,&q);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i<=4*n;i++)tag[i]=1;
	for(int i=1;i<n;i++)cf[i]=1ll*a[i]*qpow(a[i+1],mod-2)%mod;
	int u=mod-1;
	for(int i=2;i*i<=u;i++)if(!(u%i)){
		o[++m]=i;
		while(!(u%i))u/=i,pc[m]++;
	}
	if(u>1)o[++m]=u,pc[m]=1;
	if(n!=1)build(1,1,n-1);
	for(int i=1,op,l,r,x;i<=q;i++){
		scanf("%d%d%d",&op,&l,&r);
		if(op==1){
			scanf("%d",&x);
			uk(1,1,n,l,r,x);
			if(l>1)cf[l-1]=1ll*cf[l-1]*qpow(x,mod-2)%mod,up(1,1,n-1,l-1);
			if(r<n)cf[r]=1ll*cf[r]*x%mod,up(1,1,n-1,r);
		}
		//lcm(a,b)
		else{
			int os=qj(1ll*a[l]*ask(1,1,n,l)%mod);
			if(l<r)os=lcm(os,ask2(1,1,n-1,l,r-1));
			printf("%d\n",os);
		}
	}
	return 0;
}

二. TopCoder14648 TreasureOfWinedag

题意:给你一个小写字母串,让你划分成非空的 \(k\) 段,一段的权值是不同字母个数,求出最小的总权值。

\(1\le k \le n\le 10^5\)

为了方便陈述和思考,我们重定义权值是 不同字母个数-1 。

那么观察到答案不超过 \(25\) ,因为一个显然的构造是先分 \(k-1\) 个长度为 \(1\) 的段,再分 \(1\) 个长度为 \(n-k+1\) 的段。

于是考虑把这个权值塞到状态里。

观察到随着 \(k\) 增加,答案单调不增,这个可以简单证明的,略了。

于是定义 \(dp_{i,j}\) 为考虑了前 \(i\) 个数,权值是 \(j\) ,需要划分的最小段数。

直接转移还是不行的。

观察到 \(dp_{i,k}\le dp_{j,k}(i<j)\) ,这个也能简单证明,略了。

所以求 \(dp_{i,*}\) 时,以 \(i\) 为右端点,在权值相同的情况下,一定会让左端点尽量靠左。

这里决策就只有最多 \(26\) 种了,转移即可。

复杂度 \(O(n|S|^2)\)\(|S|\) 是字符集大小。

这份代码比 在 vjudge 交的其他人 快了 \(100\) 倍左右,我不知道为什么,做法是一样的呀。

view code
#include<bits/stdc++.h>
using namespace std;
int n,k,a[100010],pr[26],t[30];
int dp[100100][30];
class TreasureOfWinedag{
	public:
	int solvePuzzle(int N, int K, int M, int c0,vector<int>c1,vector<int>c2,vector<int>c3,vector<int>c4, string s){
		int L=s.size();
		for(int i=1;i<=L;i++)a[i]=s[i-1]-'a';
		n=N;k=K;
		for(int i=L;i<n;i++){
			int t=1ll*i*c0%M;a[i+1]=25;
			for(int j=0;j<=24;j++)if(t>=c3[j]&&t<=c4[j]&&t%c1[j]==c2[j])
				{a[i+1]=j;break;}
		}
		memset(dp,0x3f,sizeof(dp));
		dp[0][0]=0;
		for(int i=1;i<=n;i++){
			pr[a[i]]=i;
			for(int j=0;j<26;j++)t[j]=pr[j];
			sort(t,t+26);reverse(t,t+26);
			for(int j=1;j<27;j++){
				for(int k=0;j+k-1<26;k++)dp[i][k+j-1]=min(dp[i][k+j-1],dp[t[j]][k]+1);
				if(!t[j])break;
			}
		}
		for(int i=0;i<=25;i++)if(dp[n][i]<=K)return i+K;
		return -1;
	}
};

三.Hackerrank:Triomino Tiling

题目链接

题意:不太好描述,可以自己看原题。

神题。

考虑从 \(1\)\(nm/3\) 填的过程中 轮廓线 的变化。

考虑从左下到右上的这根线,把竖着的看成 \(0\) ,横着的看成 \(1\) ,串成一个 01 序列。

那他一开始就是 \(000…00111…11\) 的形式,其中有 \(n\)\(0\) , \(m\)\(1\) ,你要把 \(1\) 全都移动到最左边。

神来之笔:观察发现,操作等同于把 \(s_i\)\(s_{i+3}\) 交换,其中 \(s_i=0,s_{i+3}=1\)

我们就把问题拆分成了三个子问题。

观察每个子问题,形如 \(000…111\) 的序列,\(n\)\(0\)\(m\)\(1\) ,每次可以把相邻的 \(01\) 变成 \(10\) ,然后要变成 \(111…000\) ,求方案数。

相当于每一个 \(1\) 要往左走 \(n\) 步,每一时刻第 \(i\)\(1\) 走的步数要大于等于第 \(i+1\)\(1\) 走的步数。

这实际上就是填一个 \(n*m\) 的杨表。使用钩子公式即可解决。

view code
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int qpow(int a,int b){
	int c=1;
	for(;b;b>>=1){
		if(b&1)c=1ll*a*c%mod;
		a=1ll*a*a%mod;
	}
	return c;
}
int jc[50001000],ij[50001000];
int calc(int x,int y){
	int ans=jc[x*y];
	for(int i=1;i<=x;i++)ans=1ll*ans*ij[i+y-1]%mod*jc[i-1]%mod;
	return ans;
}
void sol(){
	int n,m,t[3][2]={{0,0},{0,0},{0,0}};
	scanf("%d%d",&n,&m);
	if((n*m)%3){puts("0");return;}
	for(int i=1;i<=n;i++)t[i%3][0]++;
	for(int i=1;i<=m;i++)t[(i+n)%3][1]++;
	int as=0;
	for(int i=0;i<3;i++)as+=t[i][0]*t[i][1];
	int ans=jc[as];
	for(int i=0;i<3;i++)ans=1ll*ans*calc(t[i][0],t[i][1])%mod*ij[t[i][0]*t[i][1]]%mod;
	printf("%d\n",ans);
}
const int P=5e7;
int main(){
	jc[0]=1;
	for(int i=1;i<=P;i++)jc[i]=1ll*i*jc[i-1]%mod;
	ij[P]=qpow(jc[P],mod-2);
	for(int i=P;i;i--)ij[i-1]=1ll*ij[i]*i%mod;
	int T;cin>>T;
	while(T--)sol();
	return 0;
}

四.gym102201J Jealous Teachers

神奇的题,想了挺久。

下文中令老师为左端点,学生为右端点

直接建图跑最大流显然过不了。

先思考怎么判断答案合法:尝试用霍尔定理判定这个东西:右端点选了一个集合 \(S\),则能到达的左端点的集合 \(T\) 要满足 \((n-1)|S|\le n|T|\)

\(S\) 不是全集时,这个条件等同于 \(|S|\le |T|\)

不难发现它等价于:把任何一个右端点删掉,剩下的图都有完美匹配。这是一个经典问题。

现在我们考虑如何构造方案,发现你在计算每个点是否是必经点时的 dfs 的过程中帮你连出来的边,和原来的匹配边,刚好满足左端点每个点连出去了两条边。

建一个新图,把这两个点 \((u,v)\) 之间连一条边,则 \(n\) 个右端点形成了一棵树。

直接自下往上取就好了。

view code
#include<bits/stdc++.h>
using namespace std;
int head[401000],n,m,nxt[1001000],to[1001000],flow[1001000],c=1;
void ad(int u,int v){
	to[++c]=v,flow[c]=1,nxt[c]=head[u],head[u]=c;
	to[++c]=u,flow[c]=0,nxt[c]=head[v],head[v]=c;
}
queue<int>q;
int d[401000];
bool bfs(){
	memset(d,0,sizeof(d));
	d[1]=1;q.push(1);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=head[u];i;i=nxt[i])if(flow[i]&&!d[to[i]])
			d[to[i]]=d[u]+1,q.push(to[i]);
	}
	return d[2*n+1]>0;
}
int dfs(int x,int in){
	if(x==2*n+1)return in;
	int out=0;
	for(int i=head[x];i&&in;i=nxt[i])if(flow[i]&&d[to[i]]==d[x]+1){
		int o=dfs(to[i],min(in,flow[i]));
		flow[i]-=o,flow[i^1]+=o;
		in-=o,out+=o;
	}
	if(!out)d[x]=0;
	return out;
}
int U[401000],V[400100];
bool vis[401000],vi[400100];
int tk[401000];
vector<int>g[401000],G[400010];
struct ed{int v,w1,w2;};
vector<ed>ko[401000];
int ru[401000];
void df(int x){
	vi[x]=1;
	for(int i=head[x+n];i;i=nxt[i])if(to[i]<=n&&!vi[V[tk[to[i]-1]]])
		G[to[i]-1].push_back(i/2),df(V[tk[to[i]-1]]);
}
int ans[401000];
const int I=1e9;
int sz[401000];
void fds(int x,int f){
	sz[x]=1;
	for(ed t:ko[x])if(t.v!=f){
		fds(t.v,x);
		ans[t.w2]=n-sz[t.v];
		ans[t.w1]=sz[t.v];
		sz[x]+=sz[t.v];
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d",&U[i],&V[i]);
		ad(1+U[i],n+V[i]);
		g[V[i]].push_back(U[i]);
	}
	for(int i=1;i<n;i++)ad(1,1+i);
	for(int i=1;i<=n;i++)ad(n+i,2*n+1);
	int kp=0;
	while(bfs())kp+=dfs(1,I);
	if(kp!=n-1){
		printf("-1");
		return 0;
	}
	for(int i=1;i<=m;i++)if(!flow[2*i])
		vis[V[i]]=1,tk[U[i]]=i,G[U[i]].push_back(i); 
	int start=0;
	for(int i=1;i<=n;i++)if(!vis[i])start=i,df(i);
	for(int i=1;i<=n;i++)if(!vi[i]){
		printf("-1");
		return 0;
	}
	for(int i=1;i<n;i++){
		int A=G[i][0],B=G[i][1];
		ko[V[A]].push_back((ed){V[B],A,B});
		ko[V[B]].push_back((ed){V[A],B,A});
	}
	fds(1,0);
	for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
	return 0;
}

五. UOJ748 机器人表演

这个没做出来比较自闭。

思考如何判定一个串能被造出来。它满足:取了一个子序列是 \(S\) ,剩下的是一个合法的括号序列。

尝试贪心的和 \(S\) 匹配。于是自然想到 \(f_{i,j}\) 表示当前和 \(S\) 匹配到第 \(i\) 个字符,并且括号序列里剩下 \(j\)\(0\) 的串个数。

但这样是错的,因为同样的 \(0\) ,移到后面和 \(S\) 匹配显然更优,因为这样能使括号序列更有可能合法。

考虑 反悔贪心 。我们依然按原来的思路贪心;

当我们想填 \(1\) ,结果 \(c_{i+1}=0\)\(j=0\) 时进行反悔:

找出一个最大的 \(p\) ,使 \([p,i]\)\(0\) 个数比 \(1\) 个数多,把 \([p,i]\) 全部加到括号序列里,就把填的这个 \(1\) 抵消了。

还有一个问题是:\([p,i]\) 这一段插进去后括号序列一定合法吗?

是一定的,因为 \([p,i]\) 所有非全串的后缀都有 \(0\)\(1\) 少, 且 \([p,i]\) 满足 \(0\)\(1\) 多,所以它所有前缀都有 \(0\)\(1\) 多。

这个题他妈的就做完了。

view code
#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
int n,m;
char c[310];
int dp[310][310],dp2[310][310],pr[310];
int main(){
	scanf("%d%d",&n,&m);
	scanf("%s",c+1);
	dp[0][0]=1; 
	for(int i=1;i<=n;i++){
		int x=0;
		for(int j=i;j;j--){
			if(c[j]=='0')x++;
			else x--;
			if(x>0){pr[i]=j;break;}
		}
	}
	for(int i=1;i<=n+2*m;i++){
		for(int j=0;j<=n;j++)for(int k=0;k<=m;k++)if(dp[j][k]){
			if(j<n&&c[j+1]=='1')(dp2[j+1][k]+=dp[j][k])%=mod;
			else if(k)(dp2[j][k-1]+=dp[j][k])%=mod;
			else if(pr[j])(dp2[pr[j]-1][k]+=dp[j][k])%=mod; 
			if(j<n&&c[j+1]=='0')(dp2[j+1][k]+=dp[j][k])%=mod;
			else (dp2[j][k+1]+=dp[j][k])%=mod;
		}
		for(int j=0;j<=n;j++)for(int k=0;k<=m;k++)dp[j][k]=dp2[j][k],dp2[j][k]=0;
	}
	return printf("%d",dp[n][0]),0;
}

六. P4002 [清华集训2017]生成树计数

题外话:

  1. 第一次看到这个题是两年半前,翻阅着 Wen_kr 和 yijan 的博客,看着他们写的题解,密密麻麻的看不懂的式子,满是崇拜。

  2. 第二次看到这个题是一年前,初二的我好高骛远,想要跟着做 sjx 在南京集训时做的题,结果连最基础的 Ln Exp 都不会,更别说写这个题了。

  3. 第三次看到这个题,我独立做出来了。我不再是那个我了。

好实际上这个题很套路(

考虑 prufer 序列是 \(d_1,d_2,……,d_n\) ,则一个序列的价值是 \(\frac{(n-2)!}{\prod d_i !}(\sum (d_i+1)^m)(\prod (d_i+1)^ma_i^{d_i+1})\) ,其中 \(\sum d_i=n-2\) ,我们要求价值和。

先把常数 \(\frac{(n-2)!}{\prod a_i !}\) 提出来变成 \((\sum (d_i+1)^m)\prod\frac{(d_i+1)^ma_i^{d_i}}{d_i !}\)

\(A(x)=\sum \frac{(i+1)^mx^i}{i!}\)

则上面的式子能化为 \(\sum\limits_{i=1}^n (d_i+1)^{2m}a_i^{d_i}[x^{n-2-d_i}]\prod\limits_{j!=i}A(xa_j)\)

对着左边这一坨,设计 \(B(x)=\sum \frac{(i+1)^{2m}x^i}{i!}\)

则式子变成了 \([x^{n-2}]\sum\limits_{i=1}^n B(xa_i) \prod\limits_{j!=i}A(xa_j)=[x^{n-2}](\sum\limits_{i=1}^n \frac{B(xa_i)}{A(xa_i)} )\prod\limits_{i=1}^nA(xa_i)\)

这个式子就很优雅了,我们分开计算 \(C(x)=\sum\limits_{i=1}^n \frac{B(xa_i)}{A(xa_i)}\)\(D(x)=\prod\limits_{i=1}^nA(xa_i)\)

求出 \(F(x)=A(x)/B(x)\) 。令 \(T_j=\sum\limits_{i=1}^n a_i^j\) ,则 \([x^i]C(x)=T_i[x^i]F(x)\)

对于 \(D(x)\) ,这种一堆多项式乘起来,考虑先求它的 \(\ln\) ,则 \([x^i]\ln D(x)=[x^i]\sum \ln A(xa_i)=T_i[x^i]\ln A(x)\) ,再 \(\exp\) 回去即可。

未解决问题: \(T\) 怎么算呢。构造 \(G(x)=\sum_{i=1}^n \frac{1}{1-a_ix}\) ,这个可以分治 NTT 算,显然 \(T_i=[x^i]G(x)\)

这个题就做完了。

view code
#include<bits/stdc++.h>
using namespace std;
const int mod=998244353,G=3,GI=(mod+1)/3;
int inv[801000];
int qpow(int a,int b){
	int c=1;
	for(;b;b>>=1){
		if(b&1)c=1ll*a*c%mod;
		a=1ll*a*a%mod; 
	}
	return c;
}
int lb[801000];
void NTT(int *a,int L,int fl){
	for(int i=0;i<L;i++){
		lb[i]=(lb[i>>1]>>1)|((i&1)?(L>>1):0);
		if(i>lb[i])swap(a[i],a[lb[i]]);
	}
	for(int o=1;o<L;o<<=1){
		int Wn=qpow(fl?G:GI,(mod-1)/(o<<1));
		for(int i=0;i<L;i+=(o<<1))for(int j=0,w=1;j<o;j++,w=1ll*w*Wn%mod){
			int x=a[i+j],y=1ll*a[i+j+o]*w%mod;
			a[i+j]=(x+y)%mod;
			a[i+j+o]=(x-y)%mod; 
		}
	}
	if(!fl){
		int I=qpow(L,mod-2);
		for(int i=0;i<L;i++)a[i]=1ll*a[i]*I%mod;
	}
}
int f[801000];
void cl(int *a,int n){for(int i=0;i<n;i++)a[i]=0;}
void Inv(int *a,int *b,int n){
	if(n==1){
		b[0]=qpow(a[0],mod-2);
		return;
	}
	Inv(a,b,(n+1)>>1);
	int L=1;while(L<=2*n)L<<=1;
	for(int i=0;i<n;i++)f[i]=a[i];
	for(int i=n;i<L;i++)f[i]=b[i]=0;
	NTT(f,L,1),NTT(b,L,1);
	for(int i=0;i<L;i++)f[i]=1ll*b[i]*(2-1ll*f[i]*b[i]%mod)%mod;
	NTT(f,L,0);
	for(int i=0;i<n;i++)b[i]=f[i];
	for(int i=n;i<L;i++)b[i]=0;
}
void qd(int *a,int n){for(int i=1;i<n;i++)a[i-1]=1ll*a[i]*i%mod;a[n-1]=0;}
void jf(int *a,int n){for(int i=n-2;i>=0;i--)a[i+1]=1ll*a[i]*inv[i+1]%mod;a[0]=0;}
int f2[801000],g2[801000];
void Ln(int *a,int *b,int n){
	int L=1;
	while(L<=n*2)L<<=1; 
	for(int i=0;i<n;i++)f2[i]=a[i],g2[i]=a[i];
	for(int i=n;i<L;i++)f2[i]=g2[i]=0;
	qd(f2,n);cl(b,L);Inv(g2,b,n);
	NTT(f2,L,1),NTT(b,L,1);
	for(int i=0;i<L;i++)b[i]=1ll*b[i]*f2[i]%mod;
	NTT(b,L,0);jf(b,n+1);
}
int f3[801001];
void Exp(int *a,int *b,int n){
	if(n==1){b[0]=1;return;}
	cl(b,n+2);
	Exp(a,b,(n+1)/2);
	cl(f3,n),Ln(b,f3,n);
	for(int i=0;i<n;i++)f3[i]=(a[i]-f3[i])%mod;
	f3[0]++;
	int L=1;while(L<=2*n)L<<=1;
	NTT(b,L,1),NTT(f3,L,1);
	for(int i=0;i<L;i++)b[i]=1ll*b[i]*f3[i]%mod;
	NTT(b,L,0);
	for(int i=n;i<L;i++)b[i]=0;
}
int n,m,a[100100],A[101000],B[101000],C[100100],ij[101000],jc[101000],F[100100];
#define vi vector<int>
vi cdq(int l,int r){
	vi now;
	if(l==r){
		now.push_back(1);
		now.push_back(-a[l]);
		return now;
	}
	int mid=(l+r)>>1; 
	vi A=cdq(l,mid),B=cdq(mid+1,r);int L=1;
	while(L<=r-l+1)L<<=1;
	for(int i=0;i<L;i++)f2[i]=f3[i]=0;
	for(int i=0;i<A.size();i++)f2[i]=A[i];
	for(int i=0;i<B.size();i++)f3[i]=B[i];
	NTT(f2,L,1),NTT(f3,L,1);
	for(int i=0;i<L;i++)f2[i]=1ll*f2[i]*f3[i]%mod;
	NTT(f2,L,0);
	for(int i=0;i<=r-l+1;i++)now.push_back(f2[i]);
	return now;
}
void solF(){
	vi o=cdq(1,n);
	int L=1;
	while(L<=2*n)L<<=1;
	for(int i=0;i<L;i++)F[i]=f3[i]=0;
	for(int i=0;i<=n;i++)F[i]=o[i];
	Inv(F,f3,n+1);
	for(int i=0;i<=n;i++)F[i]=1ll*F[i]*(n-i)%mod;
	NTT(F,L,1),NTT(f3,L,1);
	for(int i=0;i<L;i++)F[i]=1ll*F[i]*f3[i]%mod;
	NTT(F,L,0);
}
int main(){
	scanf("%d%d",&n,&m);
	jc[0]=ij[0]=1;
	for(int i=1;i<=n;i++)inv[i]=qpow(i,mod-2),scanf("%d",&a[i]),jc[i]=1ll*i*jc[i-1]%mod,ij[i]=qpow(jc[i],mod-2);
	solF();
	for(int i=0;i<n;i++)A[i]=1ll*ij[i]*qpow(i+1,m)%mod;
	for(int i=0;i<n;i++)B[i]=1ll*ij[i]*qpow(i+1,2*m)%mod;
	Inv(A,C,n);
	int L=1;
	while(L<=2*n)L<<=1;
	NTT(B,L,1),NTT(C,L,1);
	for(int i=0;i<L;i++)B[i]=1ll*B[i]*C[i]%mod;
	NTT(B,L,0);
	for(int i=0;i<n;i++)B[i]=1ll*B[i]*F[i]%mod;
	Ln(A,C,n);
	for(int i=0;i<n;i++)C[i]=1ll*C[i]*F[i]%mod;
	Exp(C,A,n);
	int ans=0;
	for(int i=0;i<=n-2;i++)(ans+=1ll*B[i]*A[n-2-i]%mod)%=mod;
	for(int i=1;i<=n;i++)ans=1ll*ans*a[i]%mod;
	return printf("%d",(1ll*ans*jc[n-2]%mod+mod)%mod),0;
}

七. CF1735F Pebbles and Beads

考虑每个时刻维护一个凸包,凸包内的点就是 pebbles 个数 \(x\) 和 beads 个数 \(y\) 组成的二元组 \((x,y)\)。注意到 \(x\) 固定时 \(y\) 尽量大,所以我们只需维护上凸包。那么每次操作,相当于 \((-p_i,q_i)\)\((p_i,-q_i)\) 构成的凸包,与原凸包的闵可夫斯基和。我们以斜率为关键字,用 \(set\) 维护凸包即可。同时记录凸包头尾的坐标。

还有一个操作是 \(0\le x,y\) ,这个相当于砍掉凸包的部分头尾,也是能用 set 做到的。

查询就是末端点的 \(x\) 坐标。

复杂度 \(O(n\log n)\) ,想清楚了很好写。好像很卡精度,但我不知道为啥我一发过了。

view code
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n;
ll a,b,p[300100],q[301000];
double px,qy,qx,lx[301000],ly[301000];
struct cmp{
    bool operator ()(int x,int y){
        if(q[x]*p[y]==p[x]*q[y])return x>y;
        return q[x]*p[y]<p[x]*q[y];
    }
};
set<int,cmp>s;
void sol(){
    s.clear();
    scanf("%d%lld%lld",&n,&a,&b);
    px=qx=a,qy=b;
    for(int i=1;i<=n;i++)scanf("%lld",&p[i]),lx[i]=p[i]*2;
    for(int i=1;i<=n;i++)scanf("%lld",&q[i]),ly[i]=q[i]*2;
    for(int i=1;i<=n;i++){
        px-=p[i],qx+=p[i],qy-=q[i];
        s.insert(i);
        while(px<0&&!s.empty()){
            int id=*s.begin();
            if(-px>=lx[id])px+=lx[id],s.erase(s.begin());
            else {lx[id]+=px,ly[id]+=px*q[id]/p[id],px=0;break;}
        }
        while(qy<0&&!s.empty()){
            int id=*(--s.end());
            if(-qy>=ly[id])
            qy+=ly[id],qx-=lx[id],s.erase(--s.end());
            else {ly[id]+=qy,lx[id]+=qy*p[id]/q[id],qx+=qy*p[id]/q[id],qy=0;break;}
        }
        printf("%.8lf\n",qx);
    }
}
int main(){
    int T;scanf("%d",&T);while(T--)sol(); 
    return 0;
}

八. UOJ 498 新年的追逐战

GF 练习题。

考虑两个连通块乘起来得到什么。

一个新图里的点 \((a,b)\) ,它能到达的点相当于,\(a\)\(b\) 各自在它们的图里走一步。

显然这个图的乘法,能拆成两两连通块的乘积相加。

首先如果一边是孤立点,那就能得到 \(|S1||S2|\) 个孤立点。

孤立点的贡献,考虑 \(n_i\) 个点的图,钦定 \(1\) 个点,它孤立的期望是 \((\frac{1}{2})^{n_i-1}\) 。则孤立点期望个数 \(a_i\)\(n_i(\frac{1}{2})^{n_i-1}\)

最后孤立点个数为 \(\prod\limits_{i=1}^T n_i - \prod\limits_{i=1}^T (n_i-a_i)\) ,这个的期望就很好算了。

孤立点外,手玩得三种情况:

  1. 两个二分图,得到两个二分图。

  2. 一个二分图和一个普通图,得到一个二分图。

  3. 两个普通图,得到一个普通图。

那如果对于一个图,它所有方案下二分图联通块个数和为 \(a\) ,联通块个数和为 \(b\) (注意不包含孤立点)

则两个图相乘有: \((a,b)\)\((c,d)\) 得到 \((bc+ad,bd+ac)\)

现在问题就是求每个图的 \((a,b)\)

求联通块期望个数已经是一个被出烂的东西了。

那么二分图连通块是类似的:

首先 \(f_i\) 表示 \(i\) 个点的二分联通图的个数。

则答案为 \(\sum\limits_{i=2}^n f_i\dbinom{n}{i}2^{\tbinom{n-i}{2}}\) ,这是一个卷积的事。

至于 \(f\) ,我们构造 \(g_i\) 为:\(i\) 个点的图,先黑白染色,再在黑白间任意连边的方案数。

\(g_n=\sum\limits_{i=0}^n \dbinom{n}{i} 2^{i(n-i)}\) 。这个也能卷积求出,注意 \(i(n-i)=\tbinom{n}{2}-\tbinom{i}{2}-\tbinom{n-i}{2}\)

\(g\) 的 EGF \(G\)\(f\) 的 EGF \(F\) 满足:

\(F=(\ln G)/2\) 。这个题就做完了。

view code
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353,N=1e5,W=3,GI=(mod+1)/3,inv2=(mod+1)/2;
int L,lb[1<<19]; 
void init(int n){
	L=1;
	while(L<=n)L<<=1;
	for(int i=0;i<L;i++)lb[i]=(lb[i>>1]>>1)|((i&1)?(L>>1):0); 
}
int qpow(int a,ll b){
	int c=1;
	for(;b;b>>=1){
		if(b&1)c=1ll*a*c%mod;
		a=1ll*a*a%mod;
	}
	return c;
}
void NTT(int *a,int fl){
	for(int i=0;i<L;i++)if(i<lb[i])swap(a[i],a[lb[i]]);
	for(int o=1;o<L;o<<=1){
		int Wn=qpow(fl?W:GI,(mod-1)/(o<<1));
		for(int i=0;i<L;i+=(o<<1))for(int j=0,w=1;j<o;j++,w=1ll*w*Wn%mod){
			int x=a[i+j],y=1ll*w*a[i+j+o]%mod;
			a[i+j]=(x+y)%mod,a[i+j+o]=(x-y)%mod;
		}
	}
	if(!fl){
		int I=qpow(L,mod-2);
		for(int i=0;i<L;i++)a[i]=1ll*a[i]*I%mod;
	}
}
int fz1[1<<19],fz2[1<<19];
void Inv(int *a,int *b,int n){
	if(n==1){
		b[0]=qpow(a[0],mod-2);
		return;
	}
	int m=(n+1)>>1;
	Inv(a,b,m);
	//f(x)=2g(x)-h(x)g(x)^2
	init(n*2);
	for(int i=0;i<L;i++)fz1[i]=fz2[i]=0;
	for(int i=0;i<m;i++)fz1[i]=b[i];
	for(int i=0;i<n;i++)fz2[i]=a[i];
	NTT(fz1,1),NTT(fz2,1);
	for(int i=0;i<L;i++)fz1[i]=1ll*fz1[i]*(2-1ll*fz1[i]*fz2[i]%mod)%mod;
	NTT(fz1,0);
	for(int i=0;i<n;i++)b[i]=fz1[i];
	for(int i=n;i<L;i++)b[i]=0;
}
void cl(int *a,int n){for(int i=0;i<n;i++)a[i]=0;}
void qd(int *a,int n){for(int i=1;i<n;i++)a[i-1]=1ll*a[i]*i%mod;a[n-1]=0;}
void jf(int *a,int n){for(int i=n-2;i>=0;i--)a[i+1]=1ll*a[i]*qpow(i+1,mod-2)%mod;a[0]=0;}
int fz3[1<<19],fz4[1<<19];
void ln(int *a,int *b,int n){
	cl(b,n+2);
	for(int i=0;i<n;i++)fz4[i]=a[i];
	qd(fz4,n);
	cl(fz3,n+2);
	Inv(a,fz3,n);
	init(2*n);
	for(int i=n;i<L;i++)fz3[i]=fz4[i]=0;
	NTT(fz3,1),NTT(fz4,1);
	for(int i=0;i<L;i++)fz3[i]=1ll*fz3[i]*fz4[i]%mod;
	NTT(fz3,0);jf(fz3,n+1);
	for(int i=0;i<n;i++)b[i]=fz3[i];
}
int F[101000],jc[101000],ij[101000],G[101000],f[101000],g[101000];
void gj(int *a,int *b){
	init(2*N);
	for(int i=0;i<L;i++)fz3[i]=fz4[i]=0;
	for(int i=2;i<=N;i++)fz3[i]=1ll*ij[i]*a[i]%mod;
	for(int i=0;i<=N;i++)fz4[i]=1ll*qpow(2,1ll*i*(i-1)/2)*ij[i]%mod;
	NTT(fz3,1),NTT(fz4,1);
	for(int i=0;i<L;i++)fz3[i]=1ll*fz3[i]*fz4[i]%mod;
	NTT(fz3,0);
	for(int i=0;i<=N;i++)b[i]=1ll*jc[i]*fz3[i]%mod;
}
void sol(){
	for(int i=0;i<=N;i++)
		F[i]=1ll*ij[i]*qpow(2,1ll*i*(i-1)/2)%mod;
	init(2*N);
	for(int i=0;i<L;i++)fz3[i]=0;
	for(int i=0;i<=N;i++)fz3[i]=qpow(1ll*jc[i]*qpow(2,1ll*i*(i-1)/2)%mod,mod-2);
	NTT(fz3,1);
	for(int i=0;i<L;i++)fz3[i]=1ll*fz3[i]*fz3[i]%mod;
	NTT(fz3,0);
	for(int i=0;i<=N;i++)G[i]=1ll*fz3[i]%mod*qpow(2,1ll*i*(i-1)/2)%mod;
	ln(F,f,N+1);
	ln(G,g,N+1);
	for(int i=0;i<=N;i++)g[i]=1ll*g[i]*inv2%mod;
	for(int i=0;i<=N;i++)f[i]=1ll*f[i]*jc[i]%mod,g[i]=1ll*g[i]*jc[i]%mod;
	gj(f,F);
	gj(g,G);
}
int main(){
	jc[0]=ij[0]=1;
	for(int i=1;i<=N;i++)jc[i]=1ll*i*jc[i-1]%mod,ij[i]=qpow(jc[i],mod-2);
	sol();
	int T,n,a,b,pr1=1,pr2=1,pr3=1;
	scanf("%d",&T);
	for(int i=1;i<=T;i++){
		scanf("%d",&n);
		pr1=1ll*pr1*n%mod;
		pr2=1ll*pr2*n%mod*(1-qpow(inv2,n-1))%mod;
		pr3=1ll*pr3*qpow(2,1ll*n*(n-1)/2)%mod;
		if(i==1)a=G[n],b=F[n];
		else{
			int c=(1ll*b*G[n]%mod+1ll*a*F[n]%mod)%mod,d=(1ll*b*F[n]%mod+1ll*a*G[n]%mod)%mod;
			a=c,b=d;
		}
	}
	int ans=(1ll*(pr1-pr2)*pr3%mod+b)%mod;
	return printf("%d",(ans+mod)%mod),0;
}

九.ABC275Ex Monster

题意:给两个正整数序列 \(A,B\) 。你可以执行若干次操作,形如选一个 \([l,r]\) ,把 \(A_l\)\(A_r\) 都减一,代价是 \(B_l\)\(B_r\)\(\max\) 。求让所有 \(A_i\le 0\) 的最小代价。

题解:一个观察是可能的 \([l,r]\) 只有 \(O(n)\) 个,因为代价确定后我们一定会尽量拓展区间。

也就是说,把 \(B\) 的笛卡尔树建出,那我们一定会取一个节点对应的区间。

我们令在节点 \(i\) 取了 \(x_i\) 次。那么就有若干形如: 根到 \(u\) 这条路径上的 \(x_i\) 的和要大于等于 \(A_u\) 的限制。 要最小化 \(\sum x_iB_i\)

这个问题是比较困难的。我不太会?其实还是能做,具体就是官方题解那样嘛,合并拐点。

但是发现它的对偶问题就是: 有若干形如: 以 \(u\) 为根的子树内的 \(y_i\) 的和要小于等于 \(B_u\) 的限制。最大化 \(\sum y_iA_i\)

这就是一个简单问题了,直接令 \(dp_{u,o}\) 表示选 \(u\)\(y_i\) 的和为 \(o\) ,代价的最大值。这个是凸的,我们维护差分数组,然后每次启发式合并即可。

合并两子树后,如果大小不到 \(B_u\) ,就往里面塞 \(A_u\) 。注意这里我们重定义 \(A_u\) 是根到 \(u\)\(A_i\)\(\max\) ,发现不影响答案且更好维护。

复杂度 \(O(n\log^2 n)\)

view code

#include<bits/stdc++.h>
using namespace std;
int n,a[101000],b[100100],sta[100100],top,ls[100100],rs[101000];
void Dfs(int x,int f){
	if(!x)return;
	if(f)a[x]=max(a[x],a[f]);
	Dfs(ls[x],x),Dfs(rs[x],x);
}
struct pi{
	int id,val,c;
	bool operator < (const pi &x) const{
		if(x.val==val)return x.id<id;
		return val>x.val;
	}
};
#define ll long long
set<pi>q[101000];
int sz[101000],si[101000];
void dfs(int x){
	if(!x)return;
	dfs(ls[x]),dfs(rs[x]);
	sz[x]=sz[ls[x]]+sz[rs[x]];
	si[x]=si[ls[x]]+si[rs[x]]+1;
	if(si[ls[x]]<si[rs[x]])swap(ls[x],rs[x]);
	if(ls[x])swap(q[x],q[ls[x]]);
	while(!q[rs[x]].empty())q[x].insert(*q[rs[x]].begin()),q[rs[x]].erase(q[rs[x]].begin());
	while(sz[x]>b[x]&&!q[x].empty()){
		pi ks=*(--q[x].end());q[x].erase(--q[x].end());
		if(sz[x]-ks.c<b[x]){ks.c-=(sz[x]-b[x]),sz[x]=b[x],q[x].insert(ks);break;}
		sz[x]-=ks.c;
	}
	if(sz[x]<b[x])q[x].insert((pi){x,a[x],b[x]-sz[x]}),sz[x]=b[x];
}

int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)scanf("%d",&b[i]);
	for(int i=1;i<=n;i++){
		bool fl=0;
		while(top&&b[sta[top]]<b[i])fl=1,top--;
		if(fl)ls[i]=sta[top+1];
		if(top)rs[sta[top]]=i;
		sta[++top]=i;
	}
	int rt=sta[1];
	Dfs(rt,0);
	dfs(rt);
	ll ans=0;
	for(pi o:q[rt])ans+=1ll*o.c*o.val;
	return printf("%lld",ans),0;
}

十(1)Public Easy Round #3 F 运算符

一句话题解:把每一位通过合理的转化成求 and/xor ,正常写 FWT 即可。

敢硬着头皮去分类讨论也是一种技巧?

十(2)集训队互测2022 D3T1 整数

考虑数位 dp 有从低到高和从高到低两种方式。

这种沾点位运算的题一般就是从低到高?如果从高到低,考虑有一些状态不合法,你怎么处理捏?

你把 dp 式子列出来发现可以套上面那个题。

这个题就做完了。

view code
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353,inv2=(mod+1)/2;
int n,c[20][2][2],A[20],T[20],B[20],Bl[20];
void sw(int *a,int i,int ty){
	if(ty==0)for(int p=0;p<(1<<n);p++)if((p>>i)&1)(a[p^(1<<i)]+=a[p])%=mod,a[p]=0;
	if(ty==1)for(int p=0;p<(1<<n);p++)if((p>>i)&1)(a[p]+=a[p^(1<<i)])%=mod,a[p^(1<<i)]=0;
	if(ty==2)for(int p=0;p<(1<<n);p++)if((p>>i)&1)swap(a[p],a[p^(1<<i)]);
}
int a[1<<20],b[1<<20],ans[1<<20];
void FWT(int *a,int fl){
	for(int w=0;w<n;w++)if(!Bl[w]){
		int o=(1<<w);
		for(int i=0;i<(1<<n);i+=(o<<1))for(int j=0;j<o;j++){
			if(!T[w]){
				if(fl)(a[i+j]+=a[i+j+o])%=mod;
				else (a[i+j]-=a[i+j+o])%=mod;
			}
			else{
				int x=a[i+j],y=a[i+j+o];
				a[i+j]=(x+y)%mod,a[i+j+o]=(x-y)%mod;
				if(!fl)a[i+j]=1ll*inv2*a[i+j]%mod,a[i+j+o]=1ll*a[i+j+o]*inv2%mod;
			}
		}
	}
}
void sol(){
	for(int i=0;i<n;i++){
		A[i]=0;
		for(int j=0;j<2;j++)for(int k=0;k<2;k++)
		A[i]+=c[i][j][k];
	}
	for(int i=0;i<n;i++){
		Bl[i]=(A[i]==0||A[i]==4);
		B[i]=T[i]=0;
		if(A[i]==1||A[i]==3){
			for(int j=0;j<2;j++)for(int k=0;k<2;k++)if(c[i][j][k]&&A[i]==1||!c[i][j][k]&&A[i]==3){
				if(!j)sw(a,i,2);
				if(!k)sw(b,i,2);
			}
			if(A[i]==3)B[i]=1;
		}
		if(A[i]==2){
			if(c[i][1][0]&&c[i][0][1]){T[i]=1;continue;}
			if(c[i][0][0]&&c[i][1][1]){T[i]=B[i]=1;continue;}
			if(c[i][1][0]==c[i][1][1]){
				sw(b,i,1);
				if(!c[i][1][0])B[i]=1;
			}
			if(c[i][0][1]==c[i][1][1]){
				sw(a,i,1);
				if(!c[i][0][1])B[i]=1;
			}
		}
	}
	FWT(a,1),FWT(b,1);
	for(int i=0;i<(1<<n);i++)ans[i]=1ll*a[i]*b[i]%mod;
	FWT(ans,0);
	for(int i=0;i<n;i++){
		if(A[i]==0)sw(ans,i,0);
		if(A[i]==4)sw(ans,i,1);
		if(B[i])sw(ans,i,2);
	}
}
int dp[1<<20];
ll R[20];
char s[1<<20];
int e[1<<20];
int main(){
	scanf("%d",&n);
	ll Mx=0;
	for(int i=0;i<n;i++)scanf("%lld",&R[i]),Mx=max(Mx,R[i]);
	int o=0;ll L=1;
	while(L<=Mx)L<<=1,o++;
	scanf("%s",s);
	for(int i=0;i<(1<<n);i++)e[i]=s[i]-'0';
	dp[0]=1;
	for(int w=0;w<o;w++){
		for(int i=0;i<n;i++)
			if((R[i]>>w)&1){
				for(int j=0;j<2;j++)for(int k=0;k<2;k++){
					if(!k)c[i][j][k]=0;
					else c[i][j][k]=j;
				}
			}
			else{
				for(int j=0;j<2;j++)for(int k=0;k<2;k++){
					if(k)c[i][j][k]=1;
					else c[i][j][k]=j;
				}
			}
		for(int i=0;i<(1<<n);i++)a[i]=dp[i];
		for(int i=0;i<(1<<n);i++)b[i]=e[i];
		sol();
		for(int i=0;i<(1<<n);i++)dp[i]=ans[i];
	}
	return printf("%d",(dp[0]+mod)%mod),0;
} 

十一.集训队互测2022 D1T1 Astral Birth

高阶板子,但因为差点不会了,所以记录一下。

首先这个题不难转化为:对每一个 \(k\) 求,把序列成非空的 \(k\) 段,第一段中为 \(x\) 的有 \(1\) 贡献,第二段中为 \(!x\) 的有 \(1\) 贡献……交替来贡献,得到的最大贡献值。

观察到这个答案是凸的。只有一个 \(k\) 的话能 wqs 二分,但多个 \(k\) 的话,考虑:

对于一个序列,我们把它分成两段,两段都算一个 \(dp(i,a,b)\) 表示取了 \(i\) 段,左端是为 \(a\) 的能贡献,右端为 \(b\) 的能贡献。

由于有凸性,所以可以闵可夫斯基和来合并。

我们发现平均分成两段即可,长成线段树的样子。

view code
#include<bits/stdc++.h>
using namespace std;
char c[300010];
int n;
int f[301000][2][2],g[301000][2][2];
void ad(int &a,int b){a=max(a,b);}
void build(int l,int r){
	if(l==r){
		f[l][0][0]=(c[l]=='0');
		f[l][1][1]=(c[l]=='1');
		f[l][0][1]=f[l][1][0]=-1e9;
		return;
	}
	int mid=(l+r)>>1;
	build(l,mid),build(mid+1,r);
	for(int i=l;i<=r;i++)for(int j=0;j<2;j++)for(int k=0;k<2;k++)g[i][j][k]=-1e9;
	for(int i=0;i<2;i++)for(int j=0;j<2;j++)for(int k=0;k<2;k++)for(int o=0;o<2;o++){
		int a=l,b=mid+1,w=(j!=k)+l;
		ad(g[w][i][o],f[a][i][j]+f[b][k][o]);
		while(a<mid&&b<r){
			w++;
			if(f[a+1][i][j]-f[a][i][j]>f[b+1][k][o]-f[b][k][o])a++;
			else b++;
			ad(g[w][i][o],f[a][i][j]+f[b][k][o]);
		}
		while(a<mid)a++,w++,ad(g[w][i][o],f[a][i][j]+f[b][k][o]);
		while(b<r)b++,w++,ad(g[w][i][o],f[a][i][j]+f[b][k][o]);
	}
	for(int i=l;i<=r;i++)for(int j=0;j<2;j++)for(int k=0;k<2;k++)f[i][j][k]=g[i][j][k];
}
int dp[301000],s1[300100];
int z[5010][2],z2[5010][2];
int main(){
	scanf("%d%s",&n,c+1);
	for(int i=1;i<=n;i++)s1[i]=s1[i-1]+(c[i]=='1');
	int e=0,an=0;
	for(int i=0;i<=n;i++){
		if(i)e+=(c[i]=='0');
		an=max(an,e+s1[n]-s1[i]);
	}
	printf("%d ",an);
	build(1,n);
	for(int i=1;i<=n;i++){
		dp[i]=-1e9;
		for(int j=0;j<2;j++)for(int k=0;k<2;k++)ad(dp[i],f[i][j][k]);
	}
	int mx=max(dp[1],dp[2]);
	for(int i=2;i<=n;i++){
		if(i<n)ad(mx,dp[i+1]);
		printf("%d ",mx);
	}
	return 0;
}

十二.CF1750F Majority

挺好一个题。自己想出来耶。我们考虑容斥。

就是让当前状态一直合并到一个无法合并的状态,这个有点像数无向联通图。

令最终状态是若干段 \(1\) ,长度分别为 \(x_1,x_2,x_p\)

\(p>1\) 为不合法状态,且 \(x_i\)\(x_{i+1}\) 之间的 \(0\) 个数大于 \(x_i+x_{i+1}\)

每个 \(x_i\) 内部的方案我们已经算出来了。

这就是一个 dp 了,然后仔细看式子,可以前缀和优化。

view code
#include<bits/stdc++.h>
using namespace std;
int g[5010],n,p2[5010],mod,f[5010][5010],h[5010];
int main(){
	scanf("%d%d",&n,&mod);
	p2[0]=1;
	for(int i=1;i<=n;i++)p2[i]=2*p2[i-1]%mod;
	for(int i=1;i<=n;i++){
		if(i>1)g[i]=p2[i-2];
		else g[i]=1;
		(h[i]+=h[i-1])%=mod;
		for(int j=1;2*j+1<=i;j++){
			f[i][j]=1ll*h[i-2*j-1]*g[j]%mod;
			(g[i]-=f[i][j])%=mod;
		}
		f[i][i]=g[i];
		for(int j=1;j<=i;j++)if(i+j<=n)(h[i+j]+=f[i][j])%=mod;
	}
	return printf("%d",(g[n]+mod)%mod),0;
} 

十三.HDU 6094 Rikka with K-Match

观察到答案是关于 \(K\) 下凸的,wqs 二分后状压 dp 即可。

差点没观察到,所以记录一下(

十四.HDU 6358 Innocence

考虑最早脱离上界限制的数,设在第 \(i\) 位脱离,那么对于 \(0\)\(i-1\) 位,把其他数先填了,发现这个数的填法是唯一确定的。

想到这个之后就是套路题了。

十五.P3774 [CTSC2017]最长上升子序列

考虑最长上升子序列个数,最长反链=最小链覆盖,所以它等于将序列分为若干不上升子序列,划分序列数的最小值。

所以就把杨表的前 \(k\) 行的长度和算出来即可。但是这样是 \(O(n^2\log n)\) 的,因为要维护整个杨表。

非常妙的一步是利用杨表的性质,把符号反转后得到的杨表就是原杨表的转置。

我们又知道,如果有一元素处在杨表的 \((a,b)\) 位置,那么 \(ab\le n\) ,也就是说 \(a\le \sqrt{n}\)\(b\le \sqrt{n}\)

至此,我们维护杨表的前 \(\sqrt{n}\) 行和转置杨表的前 \(\sqrt{n}\) 行即可。

维护杨表复杂度就是 \(O(n\sqrt{n}\log n)\) 的,单次查询 \(O(\sqrt{n})\) ,可以通过本题。

view code
#include<bits/stdc++.h>
using namespace std;
int n,q,B;
multiset<int>s[2][310];
void ins(int op,int e,int x){
	if(e>B)return;
	if(op==0){
		auto it=s[op][e].lower_bound(x);
		if(it!=s[op][e].begin())it--,ins(op,e+1,*it),s[op][e].erase(it);
		s[op][e].insert(x);
	}
	else{
		auto it=s[op][e].lower_bound(x);
		if(it!=s[op][e].end())ins(op,e+1,*it),s[op][e].erase(it);
		s[op][e].insert(x);
	}
}
int a[50100],k[201000],ans[201000];
vector<int>ip[50100];
int main(){
	scanf("%d%d",&n,&q);B=sqrt(n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	for(int m,i=1;i<=q;i++)scanf("%d%d",&m,&k[i]),ip[m].push_back(i);
	for(int T=1;T<=n;T++){
		ins(0,1,a[T]),ins(1,1,a[T]);
		for(int o:ip[T]){
			int anss=0,u=k[o];
			for(int i=1;i<=min(u,B);i++)anss+=s[0][i].size();
			if(u>B){for(int i=1;i<=B;i++)if(s[1][i].size()>B)anss+=min(u,(int)s[1][i].size())-B;else break;}
			ans[o]=anss;
		}
	}
	for(int i=1;i<=q;i++)printf("%d\n",ans[i]);
	return 0;
}

十六.HDU6642 Three Investigators

比上面那个题简单多了。

如果没有权值的要求,就直接维护杨表前 \(5\) 行,求大小即可,复杂度 \(O(5n\log n)\)

但有权值要求。考虑插入 \(a\) 时,直接拆成 \(a\)\(a\) 插进去即可。

那我们在杨表里把相同权值的合并,再来维护。

插入的时候可能会把一个段分裂成两段。所以整个段数会达到 \(O(2^5 n)\) 级别。

复杂度就是 \(O(2^5*5n\log n)\)

view code
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n;
struct qq{
    ll v,c;
    bool operator < (const qq &t) const{
        if(v==t.v)return c<t.c;
        return v<t.v;
    }
};
set<qq>t[5];
ll ans;
void ins(int e,ll v,ll c){
    if(e==5)return;
    ans+=c;
    auto it=t[e].lower_bound((qq){v,-1});
    if(it==t[e].end()){t[e].insert((qq){v,c});return;}
    ll C=c;
    if((*it).v==v)C+=(*it).c,t[e].erase(it);
    it=t[e].lower_bound((qq){v,-1});
    auto it2=it;
    qq xs=(qq){0,-1};
    while(c&&it!=t[e].end()){
        if(c>=(*it).c)ans-=(*it).c,ins(e+1,(*it).v,(*it).c),c-=(*it).c,it++;
        else ans-=c,ins(e+1,(*it).v,c),xs=(qq){(*it).v,(*it).c-c},c=0,it++;
    }
    t[e].erase(it2,it);
    if(xs.c!=-1)t[e].insert(xs);
    t[e].insert((qq){v,C});
}
void sol(){
    int n;scanf("%d",&n);
    ll a;
    ans=0;
    for(int i=0;i<5;i++)t[i].clear();
    for(int i=1;i<=n;i++){
        scanf("%lld",&a);
        ins(0,a,a);
        printf("%lld",ans);
        if(i<n)printf(" ");
    }
}
int main(){
    int T;cin>>T;
    for(int i=1;i<=T;i++){
        sol();printf("\n");
    }
    return 0;
}

已经好几天没更博客了,这周确实有点颓。(11.18)

十七.CF1628E Groceries in Meteor Town

套路题。考虑建出 kruskal 重构树,问题等同于对于 \(x\) 找出一个最浅的祖先 \(u\),使得除 \(u\) 子树内的点中有白点。

把 dfn 最小 和 dfn 最大 的白点维护出来即可。

问题就变成了区间染色和整体的白色最大/小值,线段树维护即可。

view code
#include<bits/stdc++.h>
using namespace std;
int n,q,m,fa[601000];
struct qq{int u,v,w;}a[601000];
bool cmp(qq a,qq b){return a.w<b.w;}
int F(int x){
	if(x==fa[x])return x;
	return fa[x]=F(fa[x]);
}
int ls[601000],rs[601000],val[601000],f[601000][20],dfn[601000],sz[600101];
void dfs(int x){
	dfn[x]=++dfn[0],sz[x]=1;
	for(int e=0;e<2;e++){
		int v=(e?rs[x]:ls[x]);
		if(v){
			f[v][0]=x;
			for(int i=1;i<20;i++)f[v][i]=f[f[v][i-1]][i-1];
			dfs(v),sz[x]+=sz[v]; 
		}
	}
}
int mi[1200010],mx[1201000],ni[1200100],nx[1201000],tag[1201000];
void ud(int p){
	ni[p]=min(ni[p<<1],ni[p<<1|1]);
	nx[p]=max(nx[p<<1],nx[p<<1|1]);
}
void to(int p,int z){
	tag[p]=z;
	if(z==2)ni[p]=2*n,nx[p]=0;
	else ni[p]=mi[p],nx[p]=mx[p];
}
void pd(int p){
	if(tag[p])to(p<<1,tag[p]),to(p<<1|1,tag[p]),tag[p]=0;
}
void build(int p,int l,int r){
	if(l==r){
		mi[p]=mx[p]=dfn[l];
		ni[p]=2*n,nx[p]=0;
		return;
	}
	int mid=(l+r)>>1;
	build(p<<1,l,mid),build(p<<1|1,mid+1,r);
	mi[p]=min(mi[p<<1],mi[p<<1|1]);
	mx[p]=max(mx[p<<1],mx[p<<1|1]);
	ud(p);
}
void up(int p,int l,int r,int x,int y,int z){
	if(x<=l&&r<=y)return to(p,z);
	pd(p);
	int mid=(l+r)>>1;
	if(x<=mid)up(p<<1,l,mid,x,y,z);
	if(y>mid)up(p<<1|1,mid+1,r,x,y,z);
	ud(p);
}
int main(){
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++)fa[i]=i,val[i]=-1;
	for(int i=1;i<n;i++)scanf("%d%d%d",&a[i].u,&a[i].v,&a[i].w);
	sort(a+1,a+n,cmp);
	for(int i=1;i<n;i++){
		fa[n+i]=n+i;
		ls[n+i]=F(a[i].u),rs[n+i]=F(a[i].v);
		fa[ls[n+i]]=fa[rs[n+i]]=n+i;
		val[n+i]=a[i].w;
	}
	dfs(2*n-1);
	build(1,1,n);
	for(int i=1,ty,x,l,r;i<=q;i++){
		scanf("%d",&ty);
		if(ty<3)scanf("%d%d",&l,&r),up(1,1,n,l,r,ty);
		else{
			scanf("%d",&x);
			if(ni[1]==2*n){puts("-1");continue;}
			if(ni[1]==dfn[x]&&nx[1]==dfn[x]){puts("-1");continue;}
			for(int j=19;j>=0;j--)
				if(f[f[x][j]][0]&&(ni[1]<dfn[f[x][j]]||nx[1]>=dfn[f[x][j]]+sz[f[x][j]]))
					x=f[x][j];
			printf("%d\n",val[f[x][0]]);
		}
	}
	return 0;
}

十八.CF1651F Tower Defense

套路题。考虑这个推塔的操作,你实际上可以把塔划分成若干段,每一段有三种情况:第一种是全满血,第二种是在 \(t\) 时刻被推完,第三种是单点特殊权值。

所以推的时候一段一段推,如果能推完就推。然后到了一个特殊段,二分能推到哪里即可。

用主席树维护第二种的权值,其它两种是平凡的。复杂度 \(O(n\log^2 n)\)

这个段数的分析就是 ODT 状物。

view code
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,q;
ll c[201000],k[201000];
int L[201000],R[201000],top,ty[201000];
ll cs[201000],tim[201000];
ll sk[201000],sc[201000];
int C,T[201000],ls[4010000],rs[4010000],qc[201000],cn;
ll XY;
struct qq{
	ll z1,z2;
}su[4010000];
qq operator + (const qq &x,const qq &y){return (qq){x.z1+y.z1,x.z2+y.z2};}
int F(ll x){return lower_bound(qc+1,qc+cn+1,x)-qc;}
void up(int &o,int lst,int l,int r,int x,qq z){
	o=++C;ls[o]=ls[lst],rs[o]=rs[lst],su[o]=su[lst]+z;
	if(l==r)return;
	int mid=(l+r)>>1;
	if(x<=mid)up(ls[o],ls[lst],l,mid,x,z);
	else up(rs[o],rs[lst],mid+1,r,x,z);
}
qq ask(int o,int l,int r,int x){
	if(l==r)return su[o];
	int mid=(l+r)>>1;
	if(x<=mid)return ask(ls[o],l,mid,x);
	return su[ls[o]]+ask(rs[o],mid+1,r,x);
}
ll calc(int x,ll t){
	ll now=sk[x]*t;
	int po=upper_bound(qc+1,qc+cn+1,t)-qc-1;
	if(po){
		qq pi=ask(T[x],1,cn,po);
		now+=pi.z1*t+pi.z2;
	}
	return now;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%lld%lld",&c[i],&k[i]),qc[i]=c[i]/k[i]+1,sc[i]=sc[i-1]+c[i],sk[i]=sk[i-1]+k[i];
	sort(qc+1,qc+n+1);cn=n,cn=unique(qc+1,qc+n+1)-qc-1;
	for(int i=1;i<=n;i++)up(T[i],T[i-1],1,cn,F(c[i]/k[i]+1),(qq){-k[i],c[i]});
	ll h,T,ans=0;
	top=1,L[1]=1,R[1]=n;
	ty[1]=0;
	scanf("%d",&q);
	for(int ks=1;ks<=q;ks++){
		scanf("%lld%lld",&T,&h);
		bool flag=0;
		while(top){
			ll all,l=L[top],r=R[top],t=T-tim[top];
			if(!ty[top])all=sc[r]-sc[l-1];
			else if(ty[top]==1)all=calc(r,t)-calc(l-1,t);
			else all=min(c[l],cs[top]+t*k[l]);
			if(all<h)h-=all,top--;
			else{
				if(ty[top]==2)tim[top]=T,cs[top]=all-h;
				else if(ty[top]==1){
					int pl=l,pr=r,an=r;
					while(pl<=pr){
						int mid=(pl+pr)>>1;
						if(calc(mid,t)-calc(l-1,t)>=h)an=mid,pr=mid-1;
						else pl=mid+1;
					}
					h=(calc(an,t)-calc(l-1,t))-h;
					top--;
					if(an<r)top++,ty[top]=1,L[top]=an+1,R[top]=r,tim[top]=T-t;
					top++,ty[top]=2,L[top]=R[top]=an,cs[top]=h,tim[top]=T;
					if(l<an)top++,ty[top]=1,L[top]=l,R[top]=an-1,tim[top]=T;
				}
				else{
					int pl=l,pr=r,an=r;
					while(pl<=pr){
						int mid=(pl+pr)>>1;
						if(sc[mid]-sc[l-1]>=h)an=mid,pr=mid-1;
						else pl=mid+1;
					}
					h=(sc[an]-sc[l-1])-h,top--;
					if(an<r)top++,ty[top]=0,L[top]=an+1,R[top]=r,tim[top]=T-t;
					top++,ty[top]=2,L[top]=R[top]=an,cs[top]=h;tim[top]=T;
					if(l<an)top++,ty[top]=1,L[top]=l,R[top]=an-1,tim[top]=T;
				}
				break;
			}
		}
		if(!top)ans+=h;
		if(!top||L[top]>1)top++,L[top]=1,R[top]=((top==1)?n:(L[top-1]-1)),ty[top]=1,tim[top]=T;
	}
	return printf("%lld",ans),0;
}

十九.【北大集训2021】小明的树

好题。

首先这个加边删边看着很吓人。

这个“美丽树”,我们如果去对着涂黑的点看,那将非常难受,正难则反,发现白色的点恰好形成了一个联通块。

也就是说,两端都是白点的边个数=白点个数-1。

以时间为下标 ,发现我们可以把每个时间点的 白点个数-两端都是白点的边个数 这个值算出来。

因为每个点都是形如:在一个前缀时间段是白点,之后是黑点的形式,所以删/加边就能转化成了区间修改。

然后每个时间点的权值就是 一端白,一端黑 的点个数。这个也可以类似的统计。

总结一下,我们需要维护两个数组 \(a\)\(b\) ,支持 \(a,b\) 的区间修改,以及 \(\sum b_i [a_i=\min\limits_{j=1}^n a_j]\)

这个用线段树实现即可。复杂度 \(O(n\log n)\)

view code
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,m,U[501000],V[501000],ti[501000],mi[2001000],mc[2010000];
ll mk[2010000];
int tag1[2010000],tag2[2010000];
void uk(int p){
	mi[p]=min(mi[p<<1],mi[p<<1|1]);
	mk[p]=mc[p]=0;
	if(mi[p]==mi[p<<1])mk[p]+=mk[p<<1],mc[p]+=mc[p<<1];
	if(mi[p]==mi[p<<1|1])mk[p]+=mk[p<<1|1],mc[p]+=mc[p<<1|1]; 
}
void build(int p,int l,int r){
	if(l==r){
		mi[p]=l-1,mc[p]=1;
		return;
	}
	int mid=(l+r)>>1;
	build(p<<1,l,mid),build(p<<1|1,mid+1,r);
	uk(p);
}
void ad1(int p,int z){tag1[p]+=z;mi[p]+=z;}
void ad2(int p,int z){tag2[p]+=z;mk[p]+=1ll*z*mc[p];}
void pd(int p){
	ad1(p<<1,tag1[p]),ad1(p<<1|1,tag1[p]),tag1[p]=0;
	ad2(p<<1,tag2[p]),ad2(p<<1|1,tag2[p]),tag2[p]=0;
}
void up(int p,int l,int r,int x,int y,int z,int ty){
	if(x<=l&&r<=y){
		if(ty==1)return ad1(p,z);
		return ad2(p,z);
	}
	if(y<l||x>r)return;pd(p);int mid=(l+r)>>1;
	up(p<<1,l,mid,x,y,z,ty),up(p<<1|1,mid+1,r,x,y,z,ty);
	uk(p); 
}
void ins(int u,int v,int z){
	if(ti[u]>ti[v])swap(u,v);
	up(1,1,n,ti[v],n,-z,1);
	up(1,1,n,ti[u],ti[v]-1,z,2);
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++)scanf("%d%d",&U[i],&V[i]);
	ti[1]=1;
	for(int i=1,a;i<n;i++)scanf("%d",&a),ti[a]=n+1-i;
	build(1,1,n);
	for(int i=1;i<n;i++)ins(U[i],V[i],1);
	printf("%lld\n",mk[1]);
	for(int u,v,i=1;i<=m;i++){
		scanf("%d%d",&u,&v),ins(u,v,-1);
		scanf("%d%d",&u,&v),ins(u,v,1);
		printf("%lld\n",mk[1]);
	}
	return 0;
}
posted @ 2022-11-02 16:23  grass8woc  阅读(277)  评论(1编辑  收藏  举报