未分类 - ShinriiTin's Blog - 博主是sb

codechef Dynamic GCD

题意:给出一棵点权树,每次或者询问两点之间路径上所有点点权的gcd,或者把两点之间路径上所有点的点权同时加上一个数

考虑链上的做法,得到了链上做法之后在树上就只需要树链剖分就行了。

由更相减损术得,序列a1,a2,..,an的gcd等于序列a1,a2-a1,a3-a2,...,an-an-1的gcd。

那么对于链上我们就维护一阶差分的区间gcd,以及原数列的增量就好。

前一个信息我们用线段树维护,后一个信息我们用bit维护。

在树上的推广:用线段树维护每条重链的区间gcd,用bit维护每个点的增量。

Portal

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

#define g() getchar()
template<class Q>void Scan(Q&x){
	char c;int f=1;
	while(c=g(),c<48||c>57)if(c=='-')f=-1;
	for(x=0;c>47&&c<58;c=g())x=10*x+c-48;
	x*=f;
}

#define lch x->l,l,mid
#define rch x->r,mid+1,r
#define rep(i,a,b) for(int i=a;i<b;++i)

inline int gcd(int a,int b){
	for(;b;swap(a,b),b%=a);
	return a>0?a:-a; 
}

const int MAXN=50005;

typedef struct node*star;

struct node{
	int x;
	star l,r,p;
	inline void update(){
		x=gcd(l->x,r->x);
	}
}pool[MAXN<<1];
star tail=pool;

int a[MAXN],b[MAXN];

star pos[MAXN];

void build(star&x,int l,int r){
	x=tail++;
	if(l==r){
		x->x=b[l];
		pos[l]=x;
		return;
	}
	int mid=(l+r)>>1;
	build(lch),build(rch);
	x->l->p=x->r->p=x;
	x->update();
}

int query(star x,int l,int r,int ll,int rr){
	if(ll>rr)return 0;
	if(l==ll&&r==rr)return x->x;
	int mid=(l+r)>>1;
	if(rr<=mid)return query(lch,ll,rr);
	if(ll>mid) return query(rch,ll,rr);
	return gcd(query(lch,ll,mid),query(rch,mid+1,rr));
}

int n;

struct edge{
	int t,n;
	edge(int t=0,int n=0):t(t),n(n){}
}e[MAXN<<1];
int head[MAXN],tot;
inline void AddEdge(int s,int t){
	e[++tot]=edge(t,head[s]),head[s]=tot;
	e[++tot]=edge(s,head[t]),head[t]=tot;
}

int ch[MAXN],p[MAXN];

int dep[MAXN],siz[MAXN];

void dfs(int x){
	siz[x]=1;
	for(int i=head[x],y;i;i=e[i].n)
		if((y=e[i].t)!=p[x]){
			p[y]=x,dep[y]=dep[x]+1;
			dfs(y);
			siz[x]+=siz[y];
			if(siz[y]>siz[ch[x]])ch[x]=y;
		}
}

int dfn[MAXN],cnt; 

int bel[MAXN],top[MAXN],bel_cnt;

void dfs1(int x,int beln){
	dfn[x]=++cnt;
	bel[x]=beln;
	if(ch[x])dfs1(ch[x],beln);
	for(int i=head[x],y;i;i=e[i].n)
		if((y=e[i].t)!=p[x]){
			if(y==ch[x])continue;
			top[++bel_cnt]=y;
			dfs1(y,bel_cnt);
		}
	if(ch[x])b[dfn[ch[x]]]=a[ch[x]]-a[x];
}

star rt;

inline void Init(){
	Scan(n);
	rep(i,1,n){
		int x,y;
		Scan(x),Scan(y);
		AddEdge(++x,++y);
	}
	rep(i,1,n+1)Scan(a[i]);
	dfs(1);
	top[++bel_cnt]=1;
	dfs1(1,bel_cnt);
	build(rt,1,n); 
}

int Q;

int bit[MAXN];

inline void Add(int x,int w){
	for(;x<=n;x+=x&-x)bit[x]+=w;
}
inline void Add(int l,int r,int w){
	if(l>r)return;
	Add(l,w),Add(r+1,-w);
}

inline int Sum(int x){
	int res=0;
	for(;x;x-=x&-x)res+=bit[x];
	return res;
}

inline void Query(int u,int v){
	int ans=0;
	int bu,bv;
	while((bu=top[bel[u]])!=(bv=top[bel[v]])){
		if(dep[bu]>dep[bv]){
			swap(bu,bv),swap(u,v);
		}
		int x=query(rt,1,n,dfn[bv],dfn[v]);
		x=gcd(x,Sum(dfn[bv])+a[bv]);
		ans=gcd(ans,x);
		v=p[bv];
	}
	if(dep[u]>dep[v])swap(u,v);
	ans=gcd(ans,query(rt,1,n,dfn[u]+1,dfn[v]));
	int x=Sum(dfn[u])+a[u];
	printf("%d\n",gcd(ans,x));
}

inline void modify(int x,int c){
	star p=pos[dfn[x]];
	if(!p)return;
	p->x+=c;
	for(p=p->p;p;p=p->p)p->update();
}

inline void Modify(int u,int v,int w){
	int bu,bv;
	while((bu=top[bel[u]])!=(bv=top[bel[v]])){
		if(dep[bu]>dep[bv]){
			swap(bu,bv),swap(u,v);
		}
		Add(dfn[bv],dfn[v],w);
		modify(ch[v],-w);
		v=p[bv];
	}
	if(dep[u]>dep[v])swap(u,v);
	Add(dfn[u],dfn[v],w);
	if(u!=top[bel[u]])modify(u,w);
	modify(ch[v],-w);
}

inline void Solve(){
	Scan(Q);
	rep(i,0,Q){
		char c; 
		while(c=g(),c<'A'||c>'Z');
		int u,v,w;
		Scan(u),Scan(v);
		++u,++v;
		if(c=='F')Query(u,v);
		else Scan(w),Modify(u,v,w);
	}
}

int main(){
	Init(); Solve(); return 0;
}

codeforces round265 div1 D World of Darkraft - 2

详见zhj的15年集训队论文

Portal

#include <math.h>
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

#define rep(i,a,b) for(int i=a;i<b;++i)
typedef long double ld; 

const int MAXN=1000; 

int n,k,cur;

ld dp[2][MAXN];

int main(){
	scanf("%d%d",&n,&k);
	for(int i=n;i;--i){
		cur^=1;
		int B=701;
		rep(j,1,B){
			dp[cur][j]=(dp[cur^1][j]+(1.+j)/2)*j;
			dp[cur][j]+=(dp[cur^1][j+1]+j);
			dp[cur][j]/=j+1;
			dp[cur][j]+=dp[cur^1][j]*(k-1);
			dp[cur][j]/=k;
		}
	}
	ld ans=dp[cur][1]*k;
	printf("%.20f\n",(double)ans);
	return 0;
}

library

题意:给出一棵点权树,多次询问从一个点u走到另一个点v的路径中,第一个点权不小于w的点是什么

用lct就可以水过去了。。。

Portal 我只有数据。。。

#include <math.h>
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

#define g() getchar()
template<class Q>void Scan(Q&x){
	char c;int f=1;
	while(c=g(),c<48||c>57)if(c=='-')f=-1;
	for(x=0;c>47&&c<58;c=g())x=10*x+c-48;
	x*=f;
}

#define Max(x,y) if(x<y)x=y
#define rep(i,a,b) for(int i=a;i<b;++i)

const int MAXN=2e5+5;

typedef struct node*star;

struct node{
	int x,mx;
	bool rev;
	star ch[2],p;
	inline int d(){
		rep(i,0,2)
			if(p->ch[i]==this)return i;
		return -1;
	}
	inline void set_ch(star x,int d){
		if(~d)ch[d]=x; x->p=this;
	}
	inline void update();
	inline void down(){
		if(!rev)return;
		swap(ch[0],ch[1]);
		ch[0]->rev^=1,ch[1]->rev^=1;
		rev=0;
	}
}pool[MAXN];
star tail=pool;
inline star null(){
	star x=tail++;
	x->ch[0]=x->ch[1]=x->p=x;
	return x;
}
star nu=null();

inline void node::update(){
	mx=x;
	rep(i,0,2)
		if(ch[i]!=nu)Max(mx,ch[i]->mx);
}

inline void rot(star x){
	star y=x->p;
	int d=x->d();
	y->p->set_ch(x,y->d());
	y->set_ch(x->ch[d^1],d),y->update();
	x->set_ch(y,d^1);
}

void pd(star x){
	if(~x->d())pd(x->p); x->down();
}

inline void splay(star x){
	pd(x);
	for(int d1,d2;~(d1=x->d());rot(x))
		if(~(d2=x->p->d()))rot(d1^d2?x:x->p);
	x->update();
}

inline void Access(star x){
	star y=x,t=nu;
	for(;x!=nu;t=x,x=x->p){
		splay(x);
		x->ch[1]=t,x->update();
	}
	splay(y);
}

inline void Evert(star x){
	Access(x),x->rev^=1;
}

inline int Query(star x,star y,int w){
	Evert(x),Access(y);
	if(y->mx<w)return -1;
	while(y!=nu){
		y->down();
		if(y->ch[0]!=nu&&y->ch[0]->mx>=w)
			y=y->ch[0];
		else{
			if(y->x>=w)break;
			y=y->ch[1];	
		}
	}
	return y-pool;
}

int n,q;

struct edge{
	int t,n;
	edge(int t=0,int n=0):t(t),n(n){}
}e[MAXN<<1];
int head[MAXN],tot;
inline void AddEdge(int s,int t){
	e[++tot]=edge(t,head[s]),head[s]=tot;
	e[++tot]=edge(s,head[t]),head[t]=tot;
}

star p[MAXN];

void dfs(int x,int par=0){
	for(int i=head[x],y;i;i=e[i].n)
		if((y=e[i].t)!=par){
			p[y]->p=p[x];
			dfs(y,x);
		}
}

inline void Init(){
	Scan(n),Scan(q);
	rep(i,1,n+1){
		p[i]=tail++;
		*p[i]=*nu;
		Scan(p[i]->x);
		p[i]->mx=p[i]->x;
	}
	rep(i,1,n){
		int x,y;
		Scan(x),Scan(y);
		AddEdge(x,y);
	}
	dfs(1);
}

inline void Solve(){
	rep(i,0,q){
		int u,v,w;
		Scan(u),Scan(v),Scan(w);
		int res=Query(p[u],p[v],w);
		if(~res)printf("%d\n",res);
		else puts("sad");
	}
}

int main(){
	freopen("db.txt","r",stdin);
	freopen("st.out","w",stdout);
	Init(); Solve(); return 0;
}
/*
	4 2
	1 2 1 2
	1 2
	2 3
	3 4
	1 4 2
	3 4 2
*/

hdu4625 JZPTREE

题意:给出一棵树,边权均视为1,对于树上每一个点u,求∑v dis(u,v)k

我特意去买了本混凝土数学。。。

《具体数学》:xn=∑k S2(n,k)*A(x,k) (S2表示第二类斯特林数,我不会用Latex别D我。。。)

这个公式的正确性显然,把有x个元素的集合划分为k个集合,允许空集的方案数为xn

再做个简单的变换可以得到:xn=∑k S2(n,k)*k!*C(x,k)

则(x+1)n=∑k S2(n,k)*k!*C(x+1,k)

组合数有性质:C(n,k)=C(n-1,k)+C(n-1,k-1)

则(x+1)n=∑k S2(n,k)*k!*(C(x,k)+C(x,k-1))=xn+∑k S2(n,k)*k!*C(n,k-1)

实际上,我们容易发现 S2(n,k)*k! 这个算子一直没有发生改变,所以我们可以预处理出S2(n,k)*k!的值

令dp[x][k]=∑y(y属于以x为根的子树) C(dis(x,y),k)

则dp[x][k]=∑y(y是与x的儿子) dp[y][k]+dp[y][k-1]

先dfs 一次求出以1为根的所有dp值,再dfs一次,使根为当前节点x并计算 ∑k S2(n,k)*k!*dp[x][k]

复杂度:预处理 O(k2)+dp O(nk)

Portal

#include <math.h>
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

#define g() getchar()
template<class Q>void Scan(Q&x){
	char c;int f=1;
	while(c=g(),c<48||c>57)if(c=='-')f=-1;
	for(x=0;c>47&&c<58;c=g())x=10*x+c-48;
	x*=f;
}

#define rep(i,a,b) for(int i=a;i<b;++i)

const int MAXN=50005;
const int MAXK=505;
const int Mod=10007;

struct edge{
	int t,n;
	edge(int t=0,int n=0):
		t(t),n(n){}
}e[MAXN<<1];
int head[MAXN],tot;
inline void AddEdge(int s,int t){
	e[++tot]=edge(t,head[s]),head[s]=tot;
	e[++tot]=edge(s,head[t]),head[t]=tot;
}

int n,k;

inline void Init(){
	Scan(n),Scan(k);
	rep(i,1,n+1)head[i]=0;
	tot=0;
	rep(i,1,n){
		int u,v;
		Scan(u),Scan(v);
		AddEdge(u,v); 
	}
}

int T;

int s[MAXK][MAXK],fac[MAXK];

inline void init(){
	s[0][0]=fac[0]=1;
	rep(i,1,MAXK){
		fac[i]=fac[i-1]*i%Mod;
		rep(j,1,MAXK){
			s[i][j]=(j*s[i-1][j]+s[i-1][j-1])%Mod;
		}
	}
	rep(i,1,MAXK)
		rep(j,1,MAXK){
			s[i][j]=s[i][j]*fac[j]%Mod;
		}
}

int dp[MAXN][MAXK];

void prev_dfs(int x,int p){
	dp[x][0]=1;
	rep(i,1,k+1)dp[x][i]=0;
	for(int i=head[x],y;i;i=e[i].n)
		if((y=e[i].t)!=p){
			prev_dfs(y,x);
			rep(j,0,k+1){
				dp[x][j]+=dp[y][j];
				if(j)dp[x][j]+=dp[y][j-1];
				dp[x][j]%=Mod;
			}
		}
}

int ans[MAXN];

void dfs(int x,int p){
	ans[x]=0;
	rep(i,0,k+1)
		ans[x]=(ans[x]+s[k][i]*dp[x][i])%Mod;
	for(int i=head[x],y;i;i=e[i].n)
		if((y=e[i].t)!=p){
			rep(j,0,k+1){
				dp[x][j]+=Mod-dp[y][j];
				if(j)dp[x][j]+=Mod-dp[y][j-1];
				dp[x][j]%=Mod;
			}
			rep(j,0,k+1){
				dp[y][j]+=dp[x][j];
				if(j)dp[y][j]+=dp[x][j-1];
				dp[y][j]%=Mod;
			}
			dfs(y,x);
			rep(j,0,k+1){
				dp[y][j]+=Mod-dp[x][j];
				if(j)dp[y][j]+=Mod-dp[x][j-1];
				dp[y][j]%=Mod;
			}
			rep(j,0,k+1){
				dp[x][j]+=dp[y][j];
				if(j)dp[x][j]+=dp[y][j-1];
				dp[x][j]%=Mod;
			}
		}
}

inline void Solve(){
	prev_dfs(1,0);
	dfs(1,0);
	rep(i,1,n+1)
		printf("%d\n",ans[i]);
}

int main(){
	for(init(),Scan(T);T--;Init(),Solve());
	return 0;
}

hdu4969 Just a Joke

题意:

      有个点G在半径为R的圆O上做速率为v1的匀速圆周运动,点B从圆心出发,向点G做速率为v2的运动,已知O,B,G三点始终共线,求B和G相遇时走过的路程

由三点始终共线得B,G两点的角速度相同,w=v1/R

把v2分解为与半径平行的v2y和与半径垂直的v2x,设离圆心的距离为r,则v2x=w*r=v1*r/R

则v2y=sqrt(v22-(v1*r/R)2)

由v2y=dr/dt得:dt=dr/v2y

两边同时积分得:t(r)=R*arcsin(v1*r/(v2*R))/v1+C

则B点运动时间T为R*arcsin(v1/v2)/v1

知道了运动时间就好算路程了。。。s=v2*T

Portal

#include <math.h>
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

int T;

double v1,v2,R,D;

int main(){
	for(scanf("%d",&T);T--;){
		scanf("%lf%lf%lf%lf",&v1,&v2,&R,&D);
		double len=R*v2*asin(v1/v2)/v1;
		if(len-D>1e-9)puts("Why give up treatment");
		else puts("Wake up to code");
	}
	return 0;
}

bzoj3695 滑行

有个东西叫光路最速。。。

所以我们二分出射角度,由折射定律算出入射位置验证。

Portal

#include <math.h>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define eps 1e-6
using namespace std;

const int MAXN=105;
typedef double db;
const db pi=acos(-1.);

int n;

db x,ans,h[MAXN],v[MAXN];

inline db check(db t){
	db x=0,y;ans=0;
	for(int i=1;i<=n;++i){
		y=h[i]*tan(t);
		x+=y;
		ans+=sqrt(h[i]*h[i]+y*y)/v[i];
		if(i<n)t=asin(v[i+1]*sin(t)/v[i]);
	}
	return x;
}

int main(){
	scanf("%d%lf",&n,&x);
	for(int i=n;i;--i)scanf("%lf",h+i);
	for(int i=n;i;--i)scanf("%lf",v+i);
	db l=0,r=pi/2.;
	for(int i=0;i<50;++i){
		db mid=(l+r)/2.;
		if(check(mid)>x)r=mid;
		else l=mid;
	}
	printf("%.3f\n",ans); 
	return 0;
}
 

codeforces 372D Choosing Subtree is Fun

题意:

    选出一棵树的的一个子连通块,要求这个子连通块的大小不超过k

    并且定义这个子连通块的价值为最长的一个区间[l,r],满足区间中的点都在这个子连通块内,的长度。

枚举最大的编号,看最小的编号可以是多少。

具体做法,维护一个区间的点的虚树,每次加入一个新的点时,如果大小超过k,就删除编号最小的点。

用set维护dfs序来维护这个虚树,虚树大小=边长和+1

Portal

#include <set>
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

#define g() getchar()
template<class Q>void Scan(Q&x){
	char c;int f=1;
	while(c=g(),c<48||c>57)if(c=='-')f=-1;
	for(x=0;c>47&&c<58;c=g())x=10*x+c-48;
	x*=f;
}

#define fi first
#define se second
#define rep(i,a,b) for(int i=a;i<b;++i)
typedef long long ll;
typedef double db;

const int MAXN=1e5+5;

struct edge{
	int t,n;
	edge(int t=0,int n=0):t(t),n(n){}
}e[MAXN<<1];
int head[MAXN],tot;
inline void AddEdge(int s,int t){
	e[++tot]=edge(t,head[s]),head[s]=tot;
	e[++tot]=edge(s,head[t]),head[t]=tot;
}

int n,k;

int dep[MAXN];

int dfn[MAXN],cnt;

int p[MAXN][20];

void dfs(int x){
	dfn[x]=++cnt;
	for(int i=0;p[x][i];++i)
		p[x][i+1]=p[p[x][i]][i];
	for(int i=head[x],y;i;i=e[i].n)
		if((y=e[i].t)!=*p[x]){
			*p[y]=x;
			dep[y]=dep[x]+1;
			dfs(y);
		}
}

typedef pair<int,int>pr;
set<pr>s;
typedef set<pr>::iterator It;

int ans;

inline int swim(int x,int h){
	for(int i=0;h;++i,h>>=1)
		if(h&1)x=p[x][i];
	return x;
}

inline int lca(int x,int y){
	if(dep[x]>dep[y])swap(x,y);
	y=swim(y,dep[y]-dep[x]); 
	for(int i=19;~i;--i)
		if(p[x][i]!=p[y][i]){
			x=p[x][i];
			y=p[y][i];
		}
	return x==y?x:*p[x];
}

inline int dis(int x,int y){
	return dep[x]+dep[y]-dep[lca(x,y)]*2;
}

inline void insert(int x){
	It it=s.insert(pr(dfn[x],x)).fi;
	if(s.size()==1)return;
	It prev,next;
	It head=s.begin(),tail=--s.end();
	if(it==head)prev=tail;
	else prev=--it,++it;
	if(it==tail)next=head;
	else next=++it,--it;
	ans-=dis(prev->se,next->se);
	ans+=dis(prev->se,x);
	ans+=dis(x,next->se); 
}

inline void remove(int x){
	It it=s.find(pr(dfn[x],x));
	if(s.size()==1){
		s.erase(it);
		return;
	}
	It prev,next;
	It head=s.begin(),tail=--s.end();
	if(it==head)prev=tail;
	else prev=--it,++it;
	if(it==tail)next=head;
	else next=++it,--it;
	ans+=dis(prev->se,next->se);
	ans-=dis(prev->se,x);
	ans-=dis(x,next->se);
	s.erase(it); 
}

inline void Init(){
	Scan(n),Scan(k);
	rep(i,1,n){
		int x,y;
		Scan(x),Scan(y);
		AddEdge(x,y);
	}
	dfs(1);
}

int ans1;

inline void Solve(){
	int l=1;
	rep(r,1,n+1){
		insert(r);
		while((ans>>1)>=k)remove(l++);
		ans1=max(ans1,r-l+1);
	}
	printf("%d\n",ans1);
}

int main(){
	Init(); Solve(); return 0;
}

bzoj3570 Dzy Loves Physics I

看到题面就傻了。。。

完全弹性碰撞:m1v1+m2v2=m1v1'+m2v2'

由动能定理得:0.5*m1*v12+0.5*m2*v2=0.5*m1*v1'2+0.5*m2*v2'2

当m1=m2时,v1'=v2,v2'=v1

又有条件a*v=C,即dv/dt *v=C

两边同时积分得: 0.5*v2=C*t

解得v=sqrt(2*C*t)

所以。。。求原来k小的速度,再算出现在的速度即可

Portal

#include <math.h>
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

const int MAXN=1e5+5;

int bit[MAXN],sz;

inline void Add(int x){
	for(;x<=sz;x+=x&-x)++bit[x];
}

inline int kth(int k){
	int ans=0;
	for(int i=19;~i;--i){
		ans+=1<<i;
		if(ans>=sz||bit[ans]>=k)ans-=1<<i;
		else k-=bit[ans];
	}
	return ans+1;
}

int n,q,C,a[MAXN],h[MAXN<<1];

int opt[MAXN],t[MAXN],x[MAXN];

inline int Hash(int x){
	return lower_bound(h+1,h+1+sz,x)-h;
}

int main(){
	scanf("%d%d",&n,&C);
	for(int i=1;i<=n;++i){
		scanf("%d%*d%*d",a+i);
		h[++sz]=a[i];
	}
	scanf("%d",&q);
	for(int i=1;i<=q;++i){
		scanf("%d",opt+i);
		if(opt[i])scanf("%d%d",t+i,x+i);
		else scanf("%d%*d%*d",x+i),h[++sz]=x[i];
	}
	sort(h+1,h+1+sz);
	sz=unique(h+1,h+1+sz)-(h+1);
	for(int i=1;i<=n;++i)Add(Hash(a[i]));
	for(int i=1;i<=q;++i){
		if(opt[i]){
			int v=h[kth(x[i])];
			printf("%.3f\n",sqrt(2.*C*t[i]+1.*v*v));
		}
		else Add(Hash(x[i]));
	}
	return 0;
}
 

bzoj2048 书堆

有n本厚度不计的完全相同的书,长度均为m。

问在能放稳的情况下,最上面一本书伸出桌面最长的长度是多少?

显然重心刚好在桌子边缘最优。

令fn表示n本书时最长伸出桌面的长度,则有fn=∑i(i>0&&i<=n) m/(2*i)

n较小的时候直接暴力计算

n较大的时候用1+1/2+1/3+...+1/n=ln(n+1)+r,r=0.5772156649

Portal

#include <math.h>
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

long long n,m;

int main(){
	scanf("%lld%lld",&n,&m);
	double ans=0;
	if(n>10000){
		ans=(log(1.+n)+0.57721566490153286060651209)*m/2.;
	}
	else{
		for(int i=1;i<=n;++i)ans+=1.*m/i/2.;
	}
	printf("%d\n",int(ans-1e-10));
	return 0;
}
 

bzoj3157 国王奇遇记 && bzoj 3516 国王奇遇记加强版

求∑i(i>0&&i<=n) im*mi mod 1000000007

令fk=∑i(i>0&&i<=n) ik*mi

两边同乘(m-1),整理得:(m-1)fk=nk*mn+1+∑i(i>0&&i<=n) mi*[(i-1)k-ik]

用二项式定理展开(i-1)k,整理得:(m-1)fk=nk*mn+1+∑i(i>0&&i<=n)mi*∑j(j>=0&&j<k)C(k,j)*(-1)k-j*ij

交换一下两个循环的位置,整理得(m-1)fk=nk*mn+1+∑j(j>=0&&j<k)C(k,j)*(-1)k-j*∑i(i>0&&i<=n)ij*mi

好像发现了什么熟悉的东西。。。

(m-1)fk=nk*mn+1+∑j(j>=0&&j<k)C(k,j)*(-1)k-j*fj

这样就能O(m2)计算了

这两题做法一模一样。。。我就贴个3157的代码吧

Portal

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

const int MAXN=205;
const int Mod=1e9+7;
typedef long long ll;

int n,m;

inline ll qpow(ll x,int k){
	ll ans=1;
	for(;k;x=x*x%Mod,k>>=1)
		if(k&1)ans=ans*x%Mod;
	return ans;
}

int C[MAXN][MAXN];

inline void Init(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i){
		C[i][0]=C[i][i]=1;
		for(int j=1;j<i;++j){
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%Mod;
		}
	}
}

int ans,s[MAXN];

inline void Solve(){
	int inv=qpow(m-1,Mod-2);
	int nn=1,mm=qpow(m,n+1);
	s[0]=(qpow(m,n+1)-m+Mod)*inv%Mod;
	for(int i=1;i<=m;++i){
		nn=(ll)nn*n%Mod;
		s[i]=(ll)nn*mm%Mod;
		for(int j=0;j<i;++j){
			int res=(ll)C[i][i-j]*s[j]%Mod;
			if((i-j)&1)s[i]=(s[i]+Mod-res)%Mod;
			else s[i]=(s[i]+res)%Mod;
		}
		s[i]=(ll)s[i]*inv%Mod;
	}
	printf("%d\n",s[m]);
}

int main(){
	Init();
	if(m==1){
		ans=((ll)n*(n+1)>>1)%Mod;
		printf("%d\n",ans);
	}
	else Solve();
	return 0;
}

其实还有个再加强版。。。太神,不会做