bzoj原题
就是一道 d p dp dp状态很妙的长链剖分优化 d p dp dp。
毒瘤题。
n , k ≤ 1 e 5 n,k\le1e5 n,k≤1e5
考试的时候只写了 k ≤ 2 k\le2 k≤2的部分分。
下来直接 O r z Orz Orz题解了。
勉强看懂大概是这样的。
首先显然可以将 A A A升序排序,然后当 P P P数组降序排列的时候显然是最小的(排序不等式)
然后现在我们将问题转化为 P P P数组不动,只改变 A A A数组的顺序。
现在我们用类似求 k k k短路的方法,从最小值开始拓展,每次取当前最优解再次拓展一直到前 k k k大都取完。
我们如果要将一个排列变成另一个价值稍微大一点的排列,最优情况相当于对一个区间进行一次循环右移操作,代价是 ∑ i = l r A r − A i \sum_{i=l}^{r}A_r-A_i ∑i=lrAr−Ai。
我们记 ( p o s , l e n ) (pos,len) (pos,len)表示一次将区间 [ p o s − l e n + 1 , p o s ] [pos-len+1,pos] [pos−len+1,pos]这段区间进行一次循环右移, g ( p o s , l e n ) g(pos,len) 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) g(pos,len)≤g(pos,len+1),我们只需要对每个还未确定的 p o s pos pos维护其 g ( p o s , l e n ) g(pos,len) g(pos,len)这个属性。
然后有两种新的扩展:
于是可以用可持久化线段树来维护。
代码比较有技巧性(貌似可以可持久化 t r e a p treap treap博主咕掉了)
代码:
#include
#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;
}
高考与 O I OI OI的结合?
首先看出这题是一个划分问题可以用 d p dp dp搞(然而之前考试写背包+ f f t fft fft优化爆精度了233333)
我们定义将 n n n个划分成 m m m个的总贡献为 f ( n , m ) f(n,m) f(n,m)。
由于代价的计算是一种诡异的形式,因此我们只用看以下两种划分的转移。
当前这个新的 k i ki ki的贡献是 s i n ( k x ) sin(kx) 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) sin(kx)=sin((k−1)x)cos(x)+cos((k−1)x)sin(x),注意到前面的 s i n ( ( k − 1 ) x ) sin((k-1)x) sin((k−1)x)对应了 f ( n − 1 , m ) f(n-1,m) f(n−1,m),因此只需要把后面那一坨转成可以与 f ( t , 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) =sin((k−1)x)cos(x)+sin(x)cos(x)cos((k−2)x)−sin2(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) =sin((k−1)x)cos(x)+sin(x)cos(x)cos((k−2)x)+(cos2(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) =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) =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) =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) 2cos(x)∗f(n−1,m)−f(n−2,m)→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) f(n,m)=sin(x)∗f(n−1,m−1)+2cos(x)∗f(n−1,m)−f(n−2,m)
可以矩阵快速幂优化。
代码:
#include
#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;
}