E
模拟 对顶堆
每次把序列里ax=ya_x=yax=y,问每次修改后序列的前k大?
k固定,但是带修改元素的前k大,考虑类似对顶堆的思路,维护两个set,第一个只存前k大,第二个存剩余元素。
每次修改,可以视为先删除,再插入,插入后还要考虑集合大小是否合法。具体来说,用一个数组记录每个元素在哪个集合里,如果修改的元素在前k大集合里,先删除,然后插入非前k大集合,这时前k大集合缺个元素,从非前k大集合里选最大的,插入。如果修改的在非前k大集合,先删除,再插入非前k大集合,然后把非前k大最大元素插入前k大,此时前k大集合元素多了一个,取出前k大里最小的插入非前k大
void solve(){
int n,k,q;
cin>>n>>k>>q;
vector<int>a(n+1),vis(n+1);
multiset<pair<int,int>>s[2];
for(int i=1;i<=n;i++){
if(i<=k){
s[0].insert({a[i],i});
}
else{
s[1].insert({a[i],i});
vis[i]=1;
}
}
int ans=0;
auto add=[&](int x,int id)->void{
vis[x]=id;
if(id==0){
ans+=a[x];
}
s[id].insert({a[x],x});
};
auto del=[&](int x,int id)->void{
if(id==0){
ans-=a[x];
}
s[id].erase({a[x],x});
};
for(int i=1;i<=q;i++){
int x,y;
cin>>x>>y;
if(vis[x]==0){
del(x,0);
a[x]=y;
add(x,1);
auto p=*s[1].rbegin();
del(p.se,1);
add(p.se,0);
}
else{
del(x,1);
a[x]=y;
add(x,1);
auto p=*s[1].rbegin();
del(p.se,1);
add(p.se,0);
p=*s[0].begin();
del(p.se,0);
add(p.se,1);
}
cout<<ans<<'\n';
}
}
F
贡献法 计数 ds
n个集合,两两组合,每个组合集合的权值为,把集合中元素排序,其中属于第一个集合的元素的下标和。问所有组合的权值和
考虑很多组合方案的权值和,但每个方案都是基本元素的贡献组成的,考虑贡献法,计算每个元素的贡献。
这就清晰了,一对集合组合,其中每个元素的贡献就等于两个集合中小于这个元素的元素个数。这可以推广,第iii个集合的一个元素xxx的贡献,等于所有[i+1,n][i+1,n][i+1,n]的集合中比它小的元素的个数,加上集合iii中比它小的元素个数×(n−i)×(n-i)×(n−i)
枚举集合,计算贡献,然后加入/删除这个集合中的元素即可(取决于枚举顺序)
void solve(){
int n,m;
cin>>n>>m;
vector<vector<int>>a(n+1,vector<int>(m+1));
map<int,int>mp;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
mp[a[i][j]]++;
}
}
int cnt=0;
for(auto &[k,v]:mp){
v=++cnt;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
a[i][j]=mp[a[i][j]];
// cout<<a[i][j]<<' ';
add(a[i][j],1);
}
// cout<<'\n';
}
int ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
add(a[i][j],-1);
}
for(int j=1;j<=m;j++){
ans+=ask(a[i][j]);
}
ans+=(n-i)*(1+m)*m/2;
}
cout<<ans;
}