雅礼集训2019 Day1

three

bzoj原题
就是一道 d p dp 状态很妙的长链剖分优化 d p dp


permutation

毒瘤题。
在这里插入图片描述
n , k 1 e 5 n,k\le1e5
考试的时候只写了 k 2 k\le2 的部分分。
下来直接 O r z Orz 题解了。
勉强看懂大概是这样的。
首先显然可以将 A A 升序排序,然后当 P P 数组降序排列的时候显然是最小的(排序不等式
然后现在我们将问题转化为 P P 数组不动,只改变 A A 数组的顺序。
现在我们用类似求 k k 短路的方法,从最小值开始拓展,每次取当前最优解再次拓展一直到前 k k 大都取完。
我们如果要将一个排列变成另一个价值稍微大一点的排列,最优情况相当于对一个区间进行一次循环右移操作,代价是 i = l r A r A i \sum_{i=l}^{r}A_r-A_i
我们记 ( p o s , l e n ) (pos,len) 表示一次将区间 [ p o s l e n + 1 , p o s ] [pos-len+1,pos] 这段区间进行一次循环右移, g ( p o s , l e n ) g(pos,len) 表示其代价,显然有 g ( p o s , l e n ) g ( p o s , l e n + 1 ) g(pos,len)\le g(pos,len+1) ,我们只需要对每个还未确定的 p o s pos 维护其 g ( p o s , l e n ) g(pos,len) 这个属性。
然后有两种新的扩展:

  1. 执行完 ( p o s , l e n ) (pos,len) 之后的新扩展,这会产生一个新的A序列,此时所有的剩下未固定位置len全部重新设为1,计算delta。
  2. p o s pos 位置的 l e n len 修改为 l e n + 1 len+1 ,并且计算新的 g ( p o s , l e n ) g(pos,len)

于是可以用可持久化线段树来维护。
代码比较有技巧性(貌似可以可持久化 t r e a p treap 博主咕掉了)
代码:

#include<bits/stdc++.h>
#define ri register int
using namespace std;
const int rlen=1<<18|1;
inline char gc(){
	static char buf[rlen],*ib,*ob;
	(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
	return ib==ob?-1:*ib++;
}
inline int read(){
	int ans=0;
	char ch=gc();
	while(!isdigit(ch))ch=gc();
	while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
	return ans;
}
typedef long long ll;
const int N=1e5+5,M=1e7+5;
const ll inf=1e17;
int n,k;
ll a[N];
struct node{int pos,len,siz;ll det;node(int pos=0,int len=0,int siz=0,ll det=inf):pos(pos),len(len),siz(siz),det(det){}};
namespace sgt{
	#define lc (son[p][0])
	#define rc (son[p][1])
	#define mid (l+r>>1)
	int son[M][2],tot=0;
	node T[M];
	inline void pushup(int p){
		T[p].siz=T[lc].siz+T[rc].siz;
		if(lc&&T[lc].det<T[rc].det)T[p].pos=T[lc].pos,T[p].len=T[lc].len,T[p].det=T[lc].det;
		else T[p].pos=T[rc].pos+T[lc].siz,T[p].len=T[rc].len,T[p].det=T[rc].det;
	}
	inline void build(int&p,int l,int r){
		p=++tot;
		if(l==r){T[p]=(node){1,1,1,l^1?a[l]-a[l-1]:inf};return;}
		build(lc,l,mid),build(rc,mid+1,r),pushup(p);
	}
	inline void add(int&p,int o,int l,int r,int k,ll v){
		T[p=++tot]=T[o],lc=son[o][0],rc=son[o][1];
		if(l==r){++T[p].len,T[p].det+=v;return;}
		k<=T[lc].siz?add(lc,son[o][0],l,mid,k,v):add(rc,son[o][1],mid+1,r,k-T[lc].siz,v);
		pushup(p);
	}
	inline void delet(int&p,int o,int l,int r,int k){
		if(!k)return;
		T[p=++tot]=T[o],lc=son[o][0],rc=son[o][1];
		if(k<T[lc].siz)delet(lc,son[o][0],l,mid,k);
		else delet(rc,son[o][1],mid+1,r,k-T[lc].siz),lc=0;
		pushup(p);
	}
	inline void update(int&p,int o,int l,int r,int k,int v1,ll v2){
		T[p=++tot]=T[o],lc=son[o][0],rc=son[o][1];
		if(l==r){T[p].siz=v1,T[p].det=v2;return;}
		k<=T[lc].siz?update(lc,son[o][0],l,mid,k,v1,v2):update(rc,son[o][1],mid+1,r,k-T[lc].siz,v1,v2);
		pushup(p);
	}
	inline ll query(int p,int l,int r,int k){return l==r?a[l]:(k<=T[lc].siz?query(lc,l,mid,k):query(rc,mid+1,r,k-T[lc].siz));}
	#undef lc
	#undef rc
	#undef mid
}
struct sta{int st,ed;ll ans;friend inline bool operator>(const sta&a,const sta&b){return a.ans+sgt::T[a.ed].det>b.ans+sgt::T[b.ed].det;}};
priority_queue<sta,vector<sta>,greater<sta> >q;
inline void expand(sta u){
	int nw=u.st,pos=sgt::T[u.ed].pos,len=sgt::T[u.ed].len;
	if(pos+1<=sgt::T[nw].siz)sgt::update(nw,nw,1,n,pos+1,1,sgt::query(nw,1,n,pos+1)-sgt::query(nw,1,n,pos-1));
	sgt::update(nw,nw,1,n,pos,0,inf);
	if(pos-len>1)sgt::delet(nw,nw,1,n,pos-len-1);
	sgt::update(nw,nw,1,n,1,1,inf);
	q.push((sta){nw,nw,u.ans+sgt::T[u.ed].det});
	nw=u.ed;
	if(pos-len>1)sgt::add(nw,nw,1,n,pos,sgt::query(nw,1,n,pos)-sgt::query(nw,1,n,pos-len-1));
	else sgt::update(nw,nw,1,n,pos,1,inf);
	q.push((sta){u.st,nw,u.ans});
}
int main(){
	n=read(),k=read();
	for(ri i=1;i<=n;++i)a[i]=read();
	sort(a+1,a+n+1);
	ll sum=0;
	for(ri i=1;i<=n;++i)sum+=(ll)a[i]*(n-i+1);
	cout<<sum<<'\n';
	sta u;
	sgt::build(u.st,1,n);
	u.ed=u.st,u.ans=sum;
	q.push(u);
	for(--k;k;--k){
		u=q.top();
		q.pop();
		cout<<u.ans+sgt::T[u.ed].det<<'\n';
		expand(u);
	}
	return 0;
}

math

在这里插入图片描述
高考与 O I OI 的结合?
首先看出这题是一个划分问题可以用 d p dp 搞(然而之前考试写背包+ f f t fft 优化爆精度了233333)
我们定义将 n n 个划分成 m m 个的总贡献为 f ( n , m ) f(n,m)
由于代价的计算是一种诡异的形式,因此我们只用看以下两种划分的转移。

  1. m m 个中有一个 k i k_i 1 1 ,则 s i n ( x ) f ( n 1 , m 1 ) f ( n , m ) sin(x)*f(n-1,m-1)\rightarrow f(n,m)
  2. 没有一个 k i k_i 1 1 ,那么考虑对之前某一个 k i k_i 加上一求出贡献,后面会乘一坨奇奇怪怪的系数,我们来算一下。

当前这个新的 k i ki 的贡献是 s i n ( k x ) sin(kx)
由于 s i n ( k x ) = s i n ( ( k 1 ) x ) c o s ( x ) + c o s ( ( k 1 ) x ) s i n ( x ) sin(kx)=sin((k-1)x)cos(x)+cos((k-1)x)sin(x) ,注意到前面的 s i n ( ( k 1 ) x ) sin((k-1)x) 对应了 f ( n 1 , m ) f(n-1,m) ,因此只需要把后面那一坨转成可以与 f ( t , m ) f(t,m) 对应的形式即可。
我们继续做三角函数恒等变形:
= s i n ( ( k 1 ) x ) c o s ( x ) + s i n ( x ) c o s ( x ) c o s ( ( k 2 ) x ) s i n 2 ( x ) s i n ( ( k 2 ) x ) =sin((k-1)x)cos(x)+sin(x)cos(x)cos((k-2)x)-sin^2(x)sin((k-2)x)
= s i n ( ( k 1 ) x ) c o s ( x ) + s i n ( x ) c o s ( x ) c o s ( ( k 2 ) x ) + ( c o s 2 ( x ) 1 ) s i n ( ( k 2 ) x ) =sin((k-1)x)cos(x)+sin(x)cos(x)cos((k-2)x)+(cos^2(x)-1)sin((k-2)x)
= s i n ( ( k 1 ) x ) c o s ( x ) + c o s ( x ) ( s i n ( x ) c o s ( ( k 2 ) x ) + c o s ( x ) s i n ( ( k 2 ) x ) ) s i n ( ( k 2 ) x ) =sin((k-1)x)cos(x)+cos(x)(sin(x)cos((k-2)x)+cos(x)sin((k-2)x))-sin((k-2)x)
= s i n ( ( k 1 ) x ) c o s ( x ) + c o s ( x ) s i n ( ( k 1 ) x ) + c o s ( x ) s i n ( ( k 2 ) x ) ) s i n ( ( k 2 ) x ) =sin((k-1)x)cos(x)+cos(x)sin((k-1)x)+cos(x)sin((k-2)x))-sin((k-2)x)
= 2 c o s ( x ) s i n ( ( k 1 ) x ) s i n ( ( k 2 ) x ) =2cos(x)sin((k-1)x)-sin((k-2)x)
这样相当于对应了一种转移: 2 c o s ( x ) f ( n 1 , m ) f ( n 2 , m ) f ( n , m ) 2cos(x)*f(n-1,m)-f(n-2,m)\rightarrow f(n,m)
于是 f ( n , m ) = s i n ( x ) f ( n 1 , m 1 ) + 2 c o s ( x ) f ( n 1 , m ) f ( n 2 , m ) f(n,m)=sin(x)*f(n-1,m-1)+2cos(x)*f(n-1,m)-f(n-2,m)
可以矩阵快速幂优化。
代码:

#include<bits/stdc++.h>
#define ri register int
using namespace std;
inline int read(){
	#define gc getchar
	int ans=0;
	char ch=gc();
	while(!isdigit(ch))ch=gc();
	while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
	return ans;
	#undef gc
}
const int N=70;
int lim;
struct Mat{
	double a[N][N];
	Mat(double x=0){memset(a,0,sizeof(a));for(ri i=1;i<=lim;++i)a[i][i]=x;}
	friend inline Mat operator*(const Mat&a,const Mat&b){
		Mat ret;
		for(ri i=1;i<=lim;++i)for(ri k=1;k<=lim;++k)if(a.a[i][k])
		for(ri j=1;j<=lim;++j)ret.a[i][j]+=a.a[i][k]*b.a[k][j];
		return ret;
	}
}ret,trans;
int n,m;
double x,t1,t2;
inline void print(double x){
	if(x<0)putchar('-'),x=-x;
	else putchar('+');
	while(x<1)x*=10;
	while(x>10)x/=10;
	cout<<(int)x<<'\n';
}
int main(){
	for(ri tt=read();tt;--tt){
		lim=(m=read())<<1,n=read(),scanf("%lf",&x);
		memset(ret.a,0,sizeof(ret.a));
		memset(trans.a,0,sizeof(trans.a));
		t1=sin(x),t2=2.0*cos(x);
		ret.a[1][1]=t1,ret.a[1][m+1]=sin(2*x),ret.a[1][m+2]=t1*t1;
		for(ri i=m+1;i<=lim;++i){
			trans.a[i-m][i]=-1;
			trans.a[i][i-m]=1;
			trans.a[i][i]=t2;
			if(i^lim)trans.a[i][i+1]=t1;
		}
		for(n--;n;n>>=1,trans=trans*trans)if(n&1)ret=ret*trans;
		print(ret.a[1][m]);
	}
	return 0;
}