IOI2020集训队作业-19 (CF571D, CF708E, ARC093F)

A - CF571D Campus

Sol

首先对大学和警卫室分别建个Kruskal重构树并求出dfs序。询问等价于:询问某个宿舍在最后一次被清空了之后,所有对它的加操作的和。可以差分一下转化成最后一次清空和询问这两个时间点之前的加操作的和,然后对时间做扫描线,用线段树维护加操作即可。

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#define PB push_back
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
template <class T> inline void cmax(T &x,T y) { if(x<y) x=y; }
template <class T> inline void cmin(T &x,T y) { if(y<x) x=y; }
const int N=5e5+10,M=N<<1;
int n,m;
struct Tree {
	int F[M],fa[M],ch[M][2],ti[M],sz[M],ncnt;
	int dfn[M],lb[M],rb[M],id;
	void init() {
		ncnt=n;
		for(int i=1;i<=n;++i) fa[i]=i,sz[i]=1;
	}
	int find(int x) { return fa[x]==x?x:fa[x]=find(fa[x]); }
	void un(int x,int y,int t) {
		int fx=find(x),fy=find(y),u=++ncnt;
		fa[u]=fa[fx]=fa[fy]=u,ti[u]=t,sz[u]=sz[fx]+sz[fy];
		ch[u][0]=fx,ch[u][1]=fy;
	}
	void dfs(int u) {
		if(!ch[u][0]) {
			lb[u]=rb[u]=dfn[u]=++id;
		}
		else {
			lb[u]=id+1;
			dfs(ch[u][0]),dfs(ch[u][1]);
			F[ch[u][0]]=F[ch[u][1]]=u;
			rb[u]=id;
		}
	}
	void build() {
		for(int i=1;i<=ncnt;++i) if(find(i)==i) dfs(i);
		for(int i=1;i<=n;++i) fa[i]=i;
	}
	int get(int x,int t) {
		while(F[fa[x]]&&ti[F[fa[x]]]<=t) fa[x]=F[fa[x]];
		return fa[x];
	}
}A,B;

namespace seg {
	int cov[N<<2];
	inline void push_down(int c) {
		if(cov[c]) {
			cmax(cov[c<<1],cov[c]);
			cmax(cov[c<<1|1],cov[c]);
			cov[c]=0;
		}
	}
	void upd(int l,int r,int c,int L,int R,int v) {
		if(L<=l&&R>=r) return (void)(cov[c]=v);
		int mid=l+r>>1; push_down(c);
		if(L<=mid) upd(l,mid,c<<1,L,R,v);
		if(R>mid) upd(mid+1,r,c<<1|1,L,R,v);
	}
	int query(int l,int r,int c,int v) {
		if(l==r) return cov[c];
		int mid=l+r>>1; push_down(c);
		if(v<=mid) return query(l,mid,c<<1,v);
		else return query(mid+1,r,c<<1|1,v);
	}
}

namespace Bit {
	ll c[N];
	void add(int i,int t) { for(;i<=n;i+=i&-i) c[i]+=t; }
	void add(int l,int r,int t) { add(l,t),add(r+1,-t); }
	ll query(int i) { ll ans=0; for(;i;i-=i&-i) ans+=c[i]; return ans; }
}

struct OPT {
	int ty,t,x;
	OPT(int ty=0,int t=0,int x=0): ty(ty),t(t),x(x) {}
};
vector<OPT> Q;
ll ans[N];
struct Que {
	int p,id,sgn;
	Que(int p=0,int id=0,int sgn=0): p(p),id(id),sgn(sgn) {}
};
vector<Que> R[N];
char ty[20];
int main() {
	rd(n),rd(m); A.init(),B.init();
	for(int i=1,x,y;i<=m;++i) {
		scanf("%s",ty);
		if(ty[0]=='U') {
			rd(x),rd(y);
			A.un(x,y,i);
		}
		else if(ty[0]=='M') {
			rd(x),rd(y);
			B.un(x,y,i);
		}
		else {
			rd(x);
			Q.PB(OPT(ty[0]=='A'?1:(ty[0]=='Z'?2:3),i,x));
		}
	}
	A.build(),B.build();
	int qtot=0;
	for(int i=0;i<Q.size();++i)
		if(Q[i].ty==2) {
			int x=B.get(Q[i].x,Q[i].t);
			seg::upd(1,n,1,B.lb[x],B.rb[x],Q[i].t);
		}
		else if(Q[i].ty==3) {
			R[Q[i].t].PB(Que(A.dfn[Q[i].x],++qtot,1));
			int pre=seg::query(1,n,1,B.dfn[Q[i].x]);
			if(pre) R[pre].PB(Que(A.dfn[Q[i].x],qtot,-1));
		}
	int q=0;
	for(int i=1;i<=m;++i) {
		while(q<Q.size()&&Q[q].t<=i) {
			if(Q[q].ty==1) {
				int anc=A.get(Q[q].x,Q[q].t);
				Bit::add(A.lb[anc],A.rb[anc],A.sz[anc]);
			}
			q++;
		}
		for(int j=0;j<R[i].size();++j)
			ans[R[i][j].id]+=R[i][j].sgn*Bit::query(R[i][j].p);
	}
	for(int i=1;i<=qtot;++i) printf("%lld\n",ans[i]);
	return 0;
}fr

B - CF708E Student’s Camp

Sol

预处理出 k k k天之后某一侧掉了 i i i块砖的概率 p i p_i pi

一种暴力 d p dp dp:设 f i , l , r f_{i,l,r} fi,l,r表示考虑到了第 i i i行,第 i i i行最后剩下的砖是区间 [ l , r ] [l,r] [l,r],前 i i i行都连通的概率。则 f i + 1 , l , r = p l − 1 ⋅ p n − r ⋅ ∑ [ l , r ] ∩ [ l ′ , r ′ ] ≠ ∅ f i , l ′ , r ′ f_{i+1,l,r} = p_{l-1}\cdot p_{n-r} \cdot \sum_{[l,r]\cap [l',r']\not=\emptyset} f_{i,l',r'} fi+1,l,r=pl1pnr[l,r][l,r]=fi,l,r

考虑将转移中的 ∑ [ l , r ] ∩ [ l ′ , r ′ ] ≠ ∅ f i , l ′ , r ′ \sum_{[l,r]\cap [l',r']\not=\emptyset} f_{i,l',r'} [l,r][l,r]=fi,l,r更改为 ∑ f i , l ′ , r ′ − ∑ r ′ < l f i , l ′ , r ′ − ∑ l ′ > r f i , l ′ , r ′ \sum f_{i,l',r'} - \sum_{r'< l}f_{i,l',r'} - \sum_{l' > r}f_{i,l',r'} fi,l,rr<lfi,l,rl>rfi,l,r,则我们只需要知道 f l i , x = ∑ r f i , x , r fl_{i,x}=\sum_r f_{i,x,r} fli,x=rfi,x,r f r i , x = ∑ l f i , l , x fr_{i,x}=\sum_{l}f_{i,l,x} fri,x=lfi,l,x就可以更新了。

而另一方面,考虑如何直接由 f l i , f r i fl_i,fr_i fli,fri得到 f l i + 1 , f r i + 1 fl_{i+1},fr_{i+1} fli+1,fri+1

f r i + 1 , x = ∑ l f i + 1 , l , x = ∑ l p l − 1 ⋅ p n − x ⋅ ( ∑ y ∈ [ 1 , n ] f r i , y − ∑ y > x f l i , y − ∑ y < l f r i , y ) fr_{i+1,x} = \sum_{l} f_{i+1,l,x} = \sum_l p_{l-1}\cdot p_{n-x} \cdot (\sum_{y\in [1,n]}fr_{i,y} -\sum_{y>x}fl_{i,y} - \sum_{y< l}fr_{i,y}) fri+1,x=lfi+1,l,x=lpl1pnx(y[1,n]fri,yy>xfli,yy<lfri,y)

这个可以前缀和优化,复杂度 O ( n 2 ) O(n^2) O(n2)

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int mod=1e9+7,N=1510;
int Pow(int x,int y) {
	int res=1;
	while(y) {
		if(y&1) res=res*(ll)x%mod;
		x=x*(ll)x%mod,y>>=1;
	}
	return res;
}
int fac[100010],inv[100010],p;
int P[N];
void getfac(int n) {
	fac[0]=1; for(int i=1;i<=n;++i) fac[i]=fac[i-1]*(ll)i%mod;
	inv[n]=Pow(fac[n],mod-2); for(int i=n;i>=1;--i) inv[i-1]=inv[i]*(ll)i%mod;
}
int n,m,L;
int C(int n,int m) { return fac[n]*(ll)inv[m]%mod*inv[n-m]%mod; }
int fr[N][N],sfr[N][N],fl[N][N],sfl[N][N];
int s2[N],s1[N];
int main() {
	rd(n),rd(m);
	{ int a,b; rd(a),rd(b); p=a*(ll)Pow(b,mod-2)%mod; }
	rd(L),getfac(L);
	for(int i=0;i<=min(L,m);++i) P[i]=C(L,i)*(ll)Pow((mod+1-p)%mod,L-i)%mod*Pow(p,i)%mod;

	sfr[0][m]=fr[0][m]=sfl[0][1]=fl[0][1]=1;
	
	for(int i=1;i<=m;++i) s1[i]=(s1[i-1]+P[i-1])%mod;
	
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=m;++j) s2[j]=(s2[j-1]+P[j-1]*(ll)sfr[i-1][j-1])%mod;
		int tot=sfr[i-1][m];
		for(int j=1;j<=m;++j)
			fr[i][j]=P[m-j]*(ll)(s1[j]*(ll)(tot-sfl[i-1][j+1])%mod-s2[j])%mod;
		for(int j=1;j<=m;++j) {
			sfr[i][j]=(sfr[i][j-1]+fr[i][j])%mod;
			fl[i][j]=fr[i][m-j+1];
		}
		for(int j=m;j>=1;--j)
			sfl[i][j]=(sfl[i][j+1]+fl[i][j])%mod;
	}
	printf("%d",(sfr[n][m]+mod)%mod);
	return 0;
}

C - ARC093F Dark Horse

Sol

整个过程会形成一个完全二叉树的结构,每个叶子节点表示一个选手。

对于不包含 1 1 1号节点的子树,最后的胜者是其中编号最小的选手,子树内节点的排列顺序对其结果没有影响。

考虑 1 1 1向根走的过程中,会分别挑战大小为 2 0 , 2 1 , 2 2 , ⋯ 2 n − 1 2^0,2^1,2^2,\cdots 2^{n-1} 20,21,22,2n1的子树中的胜者,而 1 1 1获胜的条件是这些胜者都不是 a 1 , a 2 , ⋯ a m a_1,a_2,\cdots a_m a1,a2,am

考虑容斥,枚举 { a 1 , a 2 , ⋯ a m } \{a_1,a_2,\cdots a_m \} {a1,a2,am}的一个子集 S S S作为最终的胜者,再枚举它们所在的子树大小。则要求对于每一个 a x ∈ S a_x \in S axS,它所在的子树内没有元素比它小。所以可以按照 a x a_x ax从大到小分配与它在同一个子树内的元素,方案数就是 ( 2 n − a x − p r e s z x − 1 ) {2^n - a_x - pre\choose sz_x-1} (szx12naxpre),其中 p r e pre pre表示大于 a x a_x ax的那些 a y ∈ S a_y\in S ayS s z sz sz的和, s z x sz_x szx表示 a x a_x ax所在的子树的大小。

a a a降序排序,令 f [ i ] [ S ] f[i][S] f[i][S]表示考虑了前 i i i a a a,选择过的子树大小的和为 S S S,所有的方案数。由于子树大小都是 2 i 2^i 2i,所以 S S S同时也可以代表哪些子树大小是选择过的。枚举 a i a_i ai对应的没有选过的子树大小就可以转移。

这样算出来的方案数是没有考虑 1 1 1所在的位置的,所以最后还要乘以 2 n 2^n 2n

时间复杂度 O ( 2 n n m ) O(2^n nm) O(2nnm)

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int mod=1e9+7;
int Pow(int x,int y) {
	int res=1;
	while(y) {
		if(y&1) res=res*(ll)x%mod;
		x=x*(ll)x%mod,y>>=1;
	}
	return res;
}
inline void Add(int &x,int y) { x+=y; if(x>=mod) x-=mod; }
inline void Dec(int &x,int y) { x-=y; if(x<0) x+=mod; }
int f[1<<16],g[1<<16],sz[1<<16];
int A[20],m,n;
int N;
int fac[(1<<16)+10],inv[(1<<16)+10];
void getfac(int n) {
	fac[0]=1; for(int i=1;i<=n;++i) fac[i]=fac[i-1]*(ll)i%mod;
	inv[n]=Pow(fac[n],mod-2); for(int i=n;i>=1;--i) inv[i-1]=inv[i]*(ll)i%mod;
}
int C(int n,int m) { return fac[n]*(ll)inv[m]%mod*inv[n-m]%mod; }
int main() {
	rd(n),rd(m); N=1<<n,getfac(N);
	for(int i=1;i<=m;++i) rd(A[i]);
	f[0]=1;
	for(int i=m;i>=1;--i)
		for(int s=N-1;s>=0;--s) if(f[s]) {
			for(int j=0;j<n;++j) if(!(s>>j&1)&&N-A[i]+1>=s+(1<<j))
				Add(f[s|1<<j],f[s]*(ll)C(N-A[i]-s,(1<<j)-1)%mod);
		}
	int ans=0;
	for(int i=1;i<N;++i) sz[i]=sz[i&(i-1)]+1;
	for(int x=0;x<N;++x) {
		int tot=N-1-x;
		for(int j=n-1;j>=0;--j)
			if(tot>=(1<<j)) f[x]=f[x]*(ll)C(tot,1<<j)%mod,tot-=(1<<j);
		if(sz[x]&1) Dec(ans,f[x]);
		else Add(ans,f[x]);
	}
	for(int i=1;i<N;i<<=1) ans=ans*(ll)fac[i]%mod;
	for(int i=1;i<=n;++i) ans=ans*2ll%mod;
	printf("%d",ans);
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值