USACO 20FEB Help Yourself P 题解

题目传送门

题目大意: 在数轴上有 n n n 条线段,如果两条线段相交则称其连通,连通块数量的 K K K 次方称为该线段集合的贡献,求这 n n n 条线段的所有子集(一共 2 n 2^n 2n 个)的贡献之和。

题解

这个 K K K 次方不好处理,按套路用第二类斯特林数转化一下:
∑ P ∈ S a n s ( P ) K = ∑ i = 1 K i ! S ( K , i ) ∑ P ∈ S C a n s ( P ) i \sum_{P\in S}ans(P)^K=\sum_{i=1}^Ki!S(K,i)\sum_{P\in S}C_{ans(P)}^i PSans(P)K=i=1Ki!S(K,i)PSCans(P)i

f [ i ] [ j ] f[i][j] f[i][j] 表示子集中最右的端点在 i i i 位置时的 ∑ C a n s ( P ) j \sum C_{ans(P)}^j Cans(P)j,将线段按左端点从小到大排序,考虑每条线段的贡献。

对于一条线段 [ l , r ] [l,r] [l,r],贡献有三类:

  1. 对于 i ∈ [ 1 , l ) i\in [1,l) i[1,l),加上 [ l , r ] [l,r] [l,r] 后连通块数会 + 1 +1 +1,用 C n m = C n − 1 m + C n − 1 m − 1 C_n^m=C_{n-1}^m+C_{n-1}^{m-1} Cnm=Cn1m+Cn1m1 来求出新增的贡献,即用 f [ i ] [ j ] + f [ i ] [ j − 1 ] f[i][j]+f[i][j-1] f[i][j]+f[i][j1] 来贡献 f [ r ] [ j ] f[r][j] f[r][j]
  2. 对于 i ∈ [ l , r ] i\in [l,r] i[l,r],加上 [ l , r ] [l,r] [l,r] 后连通块数不变,直接用 f [ i ] [ j ] f[i][j] f[i][j] 来贡献 f [ r ] [ j ] f[r][j] f[r][j] 即可。
  3. 对于 i ∈ ( r , n ] i\in (r,n] i(r,n],注意到如果 f [ i ] [ j ] > 0 f[i][j]>0 f[i][j]>0,那么一定存在一条线段 [ x , y ] [x,y] [x,y] 满足 x ≤ l , y > r x\leq l,y>r xl,y>r,即 [ l , r ] [l,r] [l,r] 是被 [ x , y ] [x,y] [x,y] 包含的,那么 [ l , r ] [l,r] [l,r] 选不选无所谓,令 f [ i ] [ j ] f[i][j] f[i][j] 乘以 2 2 2 即可。

用线段树维护一下 f f f 就做完了,代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 100010
#define mod 1000000007

int n,k;
struct seg{int l,r;}a[maxn];
bool cmp(seg x,seg y){return x.l<y.l;}
void add(int &x,int y){x=(x+y>=mod?x+y-mod:x+y);}
int add(int x){return x>=mod?x-mod:x;}
#define zuo ch[0]
#define you ch[1]
struct node{
	int l,r,mid,c[11],lazy;node *ch[2];
	node(int x,int y):l(x),r(y),mid(l+r>>1),lazy(1){
		for(int i=0;i<=k;i++)c[i]=0;
		if(x<y){
			zuo=new node(l,mid);
			you=new node(mid+1,r);
		}else zuo=you=NULL;
	}
	void update(int x){
		lazy=1ll*lazy*x%mod;
		for(int i=0;i<=k;i++)c[i]=1ll*c[i]*x%mod;
	}
	void pushdown(){
		if(lazy!=1){
			if(zuo)zuo->update(lazy),you->update(lazy);
			lazy=1;
		}
	}
	void change(int x,int *w){
		pushdown();
		for(int i=0;i<=k;i++)add(c[i],w[i]);
		if(l<r)ch[x>=mid+1]->change(x,w);
	}
	void change(int x,int y,int z){
		pushdown();
		if(l==x&&r==y)return update(z);
		if(y<=mid)zuo->change(x,y,z);
		else if(x>=mid+1)you->change(x,y,z);
		else zuo->change(x,mid,z),you->change(mid+1,y,z);
		for(int i=0;i<=k;i++)c[i]=add(zuo->c[i]+you->c[i]);
	}
	void ask(int x,int y,int *w){
		pushdown();
		if(l==x&&r==y){
			for(int i=0;i<=k;i++)add(w[i],c[i]);
			return;
		}
		if(y<=mid)zuo->ask(x,y,w);
		else if(x>=mid+1)you->ask(x,y,w);
		else zuo->ask(x,mid,w),you->ask(mid+1,y,w);
	}
}*root=NULL;
int tmp[11],d[11];
int S[11][11],ans=0;

int main()
{
	scanf("%d %d",&n,&k);
	for(int i=1;i<=n;i++)scanf("%d %d",&a[i].l,&a[i].r);
	sort(a+1,a+n+1,cmp);
	root=new node(0,2*n);
	tmp[0]=1;root->change(0,tmp);
	for(int i=1;i<=n;i++){
		for(int j=0;j<=k;j++)d[j]=0;root->ask(0,a[i].l-1,d);
		tmp[0]=d[0];for(int i=1;i<=k;i++)tmp[i]=add(d[i]+d[i-1]);
		for(int j=0;j<=k;j++)d[j]=0;root->ask(a[i].l,a[i].r,d);
		for(int i=0;i<=k;i++)add(tmp[i],d[i]);
		if(a[i].r<2*n)root->change(a[i].r+1,2*n,2);
		root->change(a[i].r,tmp);
	}
	S[0][0]=1;
	for(int i=1;i<=k;i++){
		for(int j=1;j<=i;j++){
			S[i][j]=add(S[i-1][j-1]+1ll*j*S[i-1][j]%mod);
		}
	}
	int fac=1;
	for(int i=1;i<=k;i++){
		fac=1ll*fac*i%mod;
		add(ans,1ll*fac*S[k][i]%mod*root->c[i]%mod);
	}
	printf("%d",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值