A:
n个数,每次随机两个数合并,贡献是这两个数的和,求总贡献的期望乘
单独考虑每个数对答案的贡献,发现不管他是否合并,他都一直在这些数里面,因此剩余
个数的时候他被选中的概率就是
,因此
轮后他贡献的总概率就是
所以
code:
#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const int mod = 1e9+7;
const int maxn = 210000;
inline void add(int &a,const int &b){a+=b;if(a>=mod)a-=mod;}
int pw(int x,int k)
{
int re=1;
for(;k;k>>=1,x=(ll)x*x%mod) if(k&1)
re=(ll)re*x%mod;
return re;
}
int inv(int x){ return pw(x,mod-2); }
int s[maxn],invs[maxn],invi[maxn];
void pre()
{
s[0]=1; for(int i=1;i<maxn;i++) s[i]=(ll)s[i-1]*i%mod;
invs[maxn-1]=inv(s[maxn-1]);
for(int i=maxn-2;i>=0;i--) invs[i]=(ll)invs[i+1]*(i+1)%mod;
for(int i=1;i<maxn;i++) invi[i]=(ll)s[i-1]*invs[i]%mod;
}
int n;
int main()
{
freopen("huffman.in","r",stdin);
freopen("huffman.out","w",stdout);
pre();
scanf("%d",&n);
int ans1=0,ans2=0,ans3=1;
for(int i=1;i<=n;i++)
{
int x; scanf("%d",&x); add(ans1,x);
if(i>1)
{
add(ans2,invi[i]);
ans3=(ll)ans3*i%mod*(i-1)%mod*invi[2]%mod;
}
}
ans2=(ll)ans2*2%mod;
printf("%lld\n",(ll)ans1*ans2%mod*ans3%mod);
return 0;
}
B:
n个数的序列,m个操作,每个操作形如将
内的数改成这个区间的最大值,q次修改/询问,每次更改原始数组中的一个数,或者询问如果执行
内的操作,数组第
个位置的值是多少
执行若干次操作后,数组中位置 的值会取到原始数组里一个区间 内的最大值,且一定有 ,我们可以看成每个位置 有个二元组 ,我们只要求出执行 内的操作后位置 的二元组 就能直接在维护原始序列的线段树上找解了
一开始
,考虑一次操作的影响,对
操作,会将
内的
和
取min,
和
取max
我们只需要考虑怎么求这些操作后的
,
同理
问题变成了序列一开始每个位置有一个值 ,每个操作是把 内的值和 取min,求执行 内的操作后 的值是多少
不妨二分
,那么可以将序列看成一个01序列,一开始左边
个数是1,每次操作如果
内有1,就会把
全部变成1
因为是01序列且一定是左边一段1,剩下全是0,我们可以直接用一个数
代表这个序列当前的状态:左边
个数是1。每次操作就是把
的
改成
那么一次询问二分后就变成了一个数字
,我们要对他执行
内的所有操作,考虑到数字是很好维护的,我们不妨将所有询问放在一起二分(注意这不是也不能是整体二分,因为每个询问的二分区间都是不同的,我们把他们放在一起二分只是因为数字可以一起维护),正着扫一遍所有操作,对于一个操作区间是
的询问,我们在第
次操作加入,第
次操作取出他的值,更改二分的上下界
维护的话,可以直接把这些数字放进一个multiset里面维护,每次操作就把
内的值全部取出来,用并查集合并到一起改成
再放进去,因为总合并次数是
的所以复杂度是对的
那么我们就可以做到
比赛的时候我觉得这样就能过了(对常数太自信….)然后T掉了
怎么优化到一个log呢
考虑倒着做,每个询问开始的数字
,求
我们就不断的将
中的
和
取min,
同理
就可以去掉二分啦
复杂度
code:
#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
#define pb push_back
#define SZ(x) (int)x.size()
#define lc (x<<1)
#define rc (x<<1|1)
using namespace std;
inline void read(int &x)
{
char c; while(!((c=getchar())>='0'&&c<='9'));
x=c-'0';
while((c=getchar())>='0'&&c<='9') (x*=10)+=c-'0';
}
const int maxn = 110000;
int n,m,q;
int ai[maxn];
int op[maxn][2];
struct Qu
{
int l,r,k;
int nows,ansl,ansr;
}qq[maxn];
namespace Task1
{
vector<int>Vin[maxn],Vout[maxn];
int fa[maxn],s[maxn];
int findfa(const int x){return fa[x]==x?x:fa[x]=findfa(fa[x]);}
struct node
{
int x,i;
friend inline bool operator <(const node x,const node y){return x.x<y.x;}
};
multiset<node>S;
multiset<node>::iterator it,it2;
int t[maxn],tp;
void main()
{
for(int i=1;i<=n;i++) Vin[i].clear(),Vout[i].clear();
for(int i=1;i<=q;i++) if(qq[i].k)
Vin[qq[i].r].pb(i),Vout[qq[i].l-1].pb(i);
for(int i=1;i<=q;i++) s[i]=qq[i].k,fa[i]=i;
S.clear();
for(int i=m;i>=0;i--)
{
for(int j=0;j<SZ(Vout[i]);j++)
{
int ii=Vout[i][j];
qq[ii].nows=s[findfa(ii)];
}
for(int j=0;j<SZ(Vin[i]);j++)
{
int ii=Vin[i][j];
node temp=(node){s[ii],ii};
S.insert(temp);
}
node temp=(node){op[i][0],0};
it=S.lower_bound(temp);
while(it!=S.end()&&(*it).x<op[i][1])
{
it2=it; it2++;
t[++tp]=(*it).i;
S.erase(it);
it=it2;
}
if(tp)
{
int newi=t[tp]; s[newi]=op[i][1];
while(tp) fa[t[tp--]]=newi;
S.insert((node){s[newi],newi});
}
}
for(int i=1;i<=q;i++) if(qq[i].k) qq[i].ansr=qq[i].nows;
for(int i=1;i<=q;i++) s[i]=qq[i].k,fa[i]=i;
S.clear();
for(int i=m;i>=0;i--)
{
for(int j=0;j<SZ(Vout[i]);j++)
{
int ii=Vout[i][j];
qq[ii].nows=s[findfa(ii)];
}
for(int j=0;j<SZ(Vin[i]);j++)
{
int ii=Vin[i][j];
node temp=(node){s[ii],ii};
S.insert(temp);
}
node temp=(node){op[i][1],0};
it=S.upper_bound(temp);
int nex=(it==S.begin())?0:1;
if(nex) it2=it,it2--;
while(nex&&(*it2).x>=op[i][0])
{
it=it2; nex=(it==S.begin())?0:1;
if(nex) it2--;
t[++tp]=(*it).i;
S.erase(it);
}
if(tp)
{
int newi=t[tp]; s[newi]=op[i][0];
while(tp) fa[t[tp--]]=newi;
S.insert((node){s[newi],newi});
}
}
for(int i=1;i<=q;i++) if(qq[i].k) qq[i].ansl=qq[i].nows;
}
}
struct Segment
{
int seg[maxn<<2];
int loc,c,lx,rx;
void build(const int x,const int l,const int r)
{
if(l==r) { seg[x]=ai[l];return; }
int mid=(l+r)>>1;
build(lc,l,mid),build(rc,mid+1,r);
seg[x]=max(seg[lc],seg[rc]);
}
void upd(const int x,const int l,const int r)
{
if(l==r) { seg[x]=c;return; }
int mid=(l+r)>>1;
loc<=mid?upd(lc,l,mid):upd(rc,mid+1,r);
seg[x]=max(seg[lc],seg[rc]);
}
int query(const int x,const int l,const int r)
{
if(rx<l||r<lx) return 0;
if(lx<=l&&r<=rx) return seg[x];
int mid=(l+r)>>1;
return max(query(lc,l,mid),query(rc,mid+1,r));
}
}seg;
int main()
{
freopen("segment.in","r",stdin);
freopen("segment.out","w",stdout);
read(n),read(m),read(q);
for(int i=1;i<=n;i++) read(ai[i]);
for(int i=1;i<=m;i++) read(op[i][0]),read(op[i][1]);
for(int i=1;i<=q;i++)
{
int type; read(type);
read(qq[i].l),read(qq[i].r);
if(type==2) read(qq[i].k);
else qq[i].k=0;
}
Task1::main();
seg.build(1,1,n);
for(int i=1;i<=q;i++)
{
if(qq[i].k) seg.lx=qq[i].ansl,seg.rx=qq[i].ansr,printf("%d\n",seg.query(1,1,n));
else seg.loc=qq[i].l,seg.c=qq[i].r,seg.upd(1,1,n);
}
return 0;
}
C:
我看不懂题解….不知道
那个东西是怎么做的,只会
的
对于这类求 优解的问题一般考虑用 解决
一开始将
降序排序,最优解肯定是
对于一个解,其一定形如
一开始令
我们定义一个旋转操作
,代表令
,
~
原来的值全部右移一位,比如如果我们操作的区间内的
是1,2,3,4,5,操作完就是5,1,2,3,4
在
时,
后答案的增量
只要
是递增的,增量一定为正且有
同时可以发现任意一个排列 一定可以由起始序列 经过若干次 单调升的旋转 得到,且只要 单调增,我们每次旋转都会得到一个和之前不同的排列
然后就可以跑 ,对当前序列可行的每个 设置一个当前拓展的 并 计算增量 ,序列维护所有 增量的最小值,再维护序列增量最小值,每次旋转后拓展一个状态设置新状态旋转的 的下限,更改旧状态的 并重新计算增量
一共拓展 次,复杂度