洛谷 P6144 - [USACO20FEB]Help Yourself P(二项式定理+线段树)

题面传送门

题意:
给定 \(n\) 条线段,第 \(i\) 条线段左右端点分别为 \(l_i,r_i\)
定义一个线段集合的复杂度为其形成的连通块的个数的 \(k\) 次方。
求这 \(n\) 条线段所有子集的复杂度之和。
答案对 \(10^9+7\) 取模。
\(1\leq n\leq 10^5\)\(1\leq k\leq 10\)

将所有线段按左端点从小到大排序,然后依次加入这 \(n\) 个线段。
先考虑 \(k=1\) 的情形,定义 \(f_r\) 为当前右端点最大的线段的右端点为 \(r\) 的所有集合的所形成的连通块之和。
当我们加入一条线段 \([l,r]\) 时,分三种情况:

  1. 对于右端点 \(\leq l-1\) 的集合,加入这条线段后复杂度会 \(+1\),右端点变为 \(r\),故将 \(f_1+1,f_2+1,\dots,f_{l-1}+1\)到 $ f_r$ 中。
  2. 对于右端点在 \(l\)\(r\) 之间的集合,加入这条线段后复杂度不变,右端点变为 \(r\),故将 \(f_l,f_{l+1},\dots,f_r\) 累加到 \(f_r\) 中。
  3. 对于右端点 \(>r\) 的集合。加入这条线段后复杂度也不变,右端点也不变,故令所有 \(f_i(i>r)\)\(2\)

最终答案即为所有 \(f_i\) 的和。

接下来考虑 \(k\) 不为 \(1\) 的情况。其实与 \(k=1\) 大差不差,只不过我们在情况 \(1\) 中,我们要支持维护 \((f_1+1)^k,(f_2+1)^k,\dots,(f_{l-1}+1)^k\) 的和。
这玩意儿可以用二项式定理展开为 \(\sum\limits_{i=1}^{l-1}\dbinom{k}{0}f_i^0+\dbinom{k}{1}f_i^1+\dbinom{k}{2}f_i^2+\dots+\dbinom{k}{k}f_i^k=\sum\limits_{j=0}^k\dbinom{k}{j}\sum\limits_{i=1}^{l-1}f_i^j\)
建立 \(k+1\) 棵线段树,第 \(j\) 棵的下标 \(i\) 位置维护 \(f_i^j\),支持区间加,区间乘 \(2\),区间求和。
时间复杂度 \(\mathcal O(nk\log n)\)

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
template<typename T> void read(T &x){
	char c=getchar();T neg=1;
	while(!isdigit(c)){if(c=='-') neg=-1;c=getchar();}
	while(isdigit(c)) x=x*10+c-'0',c=getchar();
	x*=neg;
}
const int MAXN=1e5;
const int MAXK=10;
const int MOD=1e9+7;
int n,m;pii p[MAXN+5];
struct segtree{
	struct node{
		int l,r,val,lz;
	} s[MAXN*8+5];
	void build(int k,int l,int r){
		s[k].l=l;s[k].r=r;s[k].lz=1;if(l==r) return;
		int mid=(l+r)>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
	}
	void update(int k){s[k].val=(s[k<<1].val+s[k<<1|1].val)%MOD;}
	void pushdown(int k){
		if(s[k].lz!=1){
			s[k<<1].val=1ll*s[k<<1].val*s[k].lz%MOD;
			s[k<<1|1].val=1ll*s[k<<1|1].val*s[k].lz%MOD;
			s[k<<1].lz=1ll*s[k<<1].lz*s[k].lz%MOD;
			s[k<<1|1].lz=1ll*s[k<<1|1].lz*s[k].lz%MOD;
			s[k].lz=1;
		}
	}
	void mul(int k,int l,int r,int x){
		if(l>r) return;
		if(l<=s[k].l&&s[k].r<=r){
			s[k].val=2ll*s[k].val%MOD;
			s[k].lz=2ll*s[k].lz%MOD;return;
		} pushdown(k);int mid=(s[k].l+s[k].r)>>1;
		if(r<=mid) mul(k<<1,l,r,x);
		else if(l>mid) mul(k<<1|1,l,r,x);
		else mul(k<<1,l,mid,x),mul(k<<1|1,mid+1,r,x);
		update(k);
	}
	void add(int k,int p,int x){
		if(s[k].l==s[k].r){s[k].val=(s[k].val+x)%MOD;return;}
		pushdown(k);int mid=(s[k].l+s[k].r)>>1;
		if(p<=mid) add(k<<1,p,x);else add(k<<1|1,p,x);
		update(k);
	}
	int query(int k,int l,int r){
		if(l>r) return 0;if(l<=s[k].l&&s[k].r<=r) return s[k].val;
		pushdown(k);int mid=(s[k].l+s[k].r)>>1;
		if(r<=mid) return query(k<<1,l,r);else if(l>mid) return query(k<<1|1,l,r);
		else return (query(k<<1,l,mid)+query(k<<1|1,mid+1,r))%MOD;
	}
} t[MAXK+2]; 
int x[MAXK+2];
int c[MAXK+2][MAXK+2];
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d%d",&p[i].fi,&p[i].se);
	for(int i=0;i<=MAXK;i++){
		c[i][0]=1;for(int j=1;j<=i;j++) c[i][j]=c[i-1][j]+c[i-1][j-1];
	}
	sort(p+1,p+n+1);
	for(int i=0;i<=m;i++) t[i].build(1,0,2*n);
	t[0].add(1,0,1);
	for(int i=1;i<=n;i++){
		for(int j=0;j<=m;j++){
			int val=t[j].query(1,p[i].fi,p[i].se);
			t[j].add(1,p[i].se,val);//condition 2
		}
		for(int j=0;j<=m;j++) x[j]=t[j].query(1,0,p[i].fi-1);
		for(int j=0;j<=m;j++){
			int val=0;
			for(int k=0;k<=j;k++) val=(val+1ll*x[k]*c[j][k]%MOD)%MOD;
			t[j].add(1,p[i].se,val);//condition 1
		}
		for(int j=0;j<=m;j++) t[j].mul(1,p[i].se+1,2*n,2);//condition 3
	}
	printf("%d\n",t[m].query(1,0,2*n));
	return 0;
}
posted @ 2020-12-20 12:08  tzc_wk  阅读(174)  评论(0)    收藏  举报