ABC324题解(F,G)
F - Beautiful Path
题意:给定一张无向图,其中每条边 e i e_i ei有两个边权 a i a_i ai和 b i b_i bi,任意一条路径有一个“美丽值”定义为 X = ∑ a i ∑ b i X=\frac{\sum{a_i}}{\sum{b_i}} X=∑bi∑ai,求从节点1到节点N的路径最大的“美丽值”
解答:将式子做变换,一条路径的美丽值满足 ∑ a i − b i X = 0 \sum{a_i-b_iX}=0 ∑ai−biX=0,X是节点1-N所有路径最大的美丽值当且仅当对任意从1-N的路径, m a x ( ∑ a i − b i X ) = 0 max(\sum{a_i-b_iX})=0 max(∑ai−biX)=0,所以对X进行二分搜索,对每一个X,用DP来计算1-N节点 m a x ( ∑ a i − b i X ) max(\sum{a_i-b_iX}) max(∑ai−biX)
代码:
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
constexpr double EPS = 1e-11;
constexpr double inf = 1e10;
struct Edge{
int u,v;
int b,c;
int next=-1;
};
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,m;
cin>>n>>m;
vector<int> head(n,-1);
vector<Edge> e(m);
for(int i=0;i<m;i++){
int u,v,b,c;
cin>>u>>v>>b>>c;
u--;v--;
e[i]={u,v,b,c,head[v]};
head[v]=i;
}
double l=0,r=1e10;
function<bool(double)> check=[&](double x){
vector<double> dp(n,-inf);
dp[0]=0;
for(int i=1;i<n;i++){
for(int j=head[i];j!=-1;j=e[j].next){
int u=e[j].u;
int b=e[j].b;
int c=e[j].c;
dp[i]=max(dp[i],dp[u]+(double)b-x*c);
}
}
if(dp[n-1]<0){
return false;
}
return true;
};
while(l<r){
if(r-l<EPS) break;
double mid=l+(r-l)/2;
if(check(mid)){
l=mid;
}
else{
r=mid;
}
}
cout<<fixed<<setprecision(10)<<r<<'\n';
return 0;
}
G - Generate Arrays
题意:给定一个1-N的排列作为序列0,然后不断从此前某个序列中要么移除第k个元素之后的所有元素组成新序列,要么移除大于某个数的所有元素构成新序列。问每次操作后得到的新序列的长度。
解答:
法一:直接模拟
用set来表示序列,移除元素增加元素就用erase和insert,重点是时间复杂度的分析。
原操作相当于对序列不断的分拆,我们倒过来考虑对序列不断的合并,而且总让序列短的合并进序列长的,被合并的元素由于每次合并后所在序列长度增加一倍,最多被合并 log N \log{N} logN次,而每次合并复杂度为 log N \log{N} logN,又有N个元素,所以复杂度为 O ( N log 2 N ) O(N\log^2{N}) O(Nlog2N)。为了让移除大于某个数操作的erase操作最少,可以用两个set以对顶堆的形式维护中位数,若给定数大于中位数,则把原序列不断弹出最大的,若小于中位数,则交换两序列位置,不断弹出最小的元素给原序列的位置。
代码:
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
struct Sequence{
using value = pair<int,int>;
set<value> seq,large,small;
void balance(){
while(small.size()>large.size()){
large.emplace(*prev(small.end()));
small.erase(prev(small.end()));
}
while(small.size()+1<large.size()){
small.emplace(*large.begin());
large.erase(large.begin());
}
return;
}
void insert(const value& v){
insert(v.first,v.second);
return;
}
void insert(int i,int v){
seq.emplace(i,v);
if(large.empty()||v>=large.begin()->first){
large.emplace(v,i);
}
else{
small.emplace(v,i);
}
balance();
return;
}
value pop_front(){
value v=*seq.begin();
seq.erase(seq.begin());
large.erase(make_pair(v.second,v.first));
small.erase(make_pair(v.second,v.first));
balance();
return v;
}
value pop_back(){
value v=*prev(seq.end());
seq.erase(prev(seq.end()));
large.erase(make_pair(v.second,v.first));
small.erase(make_pair(v.second,v.first));
balance();
return v;
}
value pop_max(){
value v=*prev(large.end());
large.erase(prev(large.end()));
seq.erase(make_pair(v.second,v.first));
balance();
return make_pair(v.second,v.first);
}
value pop_min(){
value v=*(small.empty()?large:small).begin();
if(small.empty()){
large.erase(large.begin());
}
else{
small.erase(small.begin());
}
seq.erase(make_pair(v.second,v.first));
balance();
return make_pair(v.second,v.first);
}
int mid(){
return large.begin()->first;
}
int max(){
return prev(large.end())->first;
}
int min(){
return (small.empty()?large:small).begin()->first;
}
int size(){
return seq.size();
}
int empty(){
return seq.empty();
}
};
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin>>n;
vector<Sequence> seqs(1);
for(int i=0;i<n;i++){
int t;
cin>>t;
seqs[0].insert(i,t);
}
int q;
cin>>q;
seqs.resize(q+1);
for(int i=1;i<=q;i++){
int t,s,x;
cin>>t>>s>>x;
int m=seqs[s].size();
if(t==1){
if(x*2<m){
swap(seqs[s],seqs[i]);
for(int j=0;j<x;j++){
seqs[s].insert(seqs[i].pop_front());
}
}
else if(x<m){
for(int j=0;j<m-x;j++){
seqs[i].insert(seqs[s].pop_back());
}
}
}
else{
if(seqs[s].empty()){
cout<<"0\n";
continue;
}
if(x>seqs[s].mid()){
while(!seqs[s].empty()&&seqs[s].max()>x){
seqs[i].insert(seqs[s].pop_max());
}
}
else{
swap(seqs[i],seqs[s]);
while(!seqs[i].empty()&&seqs[i].min()<=x){
seqs[s].insert(seqs[i].pop_min());
}
}
}
cout<<seqs[i].size()<<'\n';
}
return 0;
}
法二:可持久化线段树(主席树)
考虑把序列每一个元素看成 ( i , a i ) (i,a_i) (i,ai),即在二维平面上的一个点,而序列就是在这个平面上的一块区域,每次操作就是在分拆区域。移除大于a的元素操作很简单,就是把区域以y=a为界分为两块就行。移除一个序列内第k个数之后的所有数有一点麻烦。这里我们选择“侧着”建立主席树,即值域不是序列内元素而是元素的下标。我们以元素从小到大的顺序依次插入它们的下标,这样在查询 d ≤ a i ≤ u d\leq a_i\leq u d≤ai≤u组成的序列的第k个元素的下标其实就是求主席树 ( d , u ) (d,u) (d,u)区间第k小,复杂度为 log N \log{N} logN。每次查询区域内元素个数复杂度也是 log N \log{N} logN,最终复杂度为 O ( N log N ) O(N\log{N}) O(NlogN)
代码:
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
struct PersistentSegmentTree{
struct Node{
int l=-1,r=-1,val=0;
};
int n,tot;
vector<Node> tree;
vector<int> root;
PersistentSegmentTree(int _n): n(_n),tot(0) {
tree.resize(n<<5);
int r=build(0,n-1);
root.push_back(r);
}
int build(int l,int r){
int root=++tot;
if(l==r) return root;
int mid=(l+r)/2;
tree[root].l=build(l,mid);
tree[root].r=build(mid+1,r);
return root;
}
int update(int k,int p,int l,int r){
int rt=++tot;
tree[rt]=tree[p];
tree[rt].val++;
if(l==r){
return rt;
}
int mid=(l+r)/2;
if(k<=mid){
tree[rt].l=update(k,tree[rt].l,l,mid);
}
else{
tree[rt].r=update(k,tree[rt].r,mid+1,r);
}
return rt;
}
void update(int k){
int pre=root.back();
int rt=update(k,pre,0,n-1);
root.push_back(rt);
}
int query(int ceil,int pl,int pr,int l,int r){
if(ceil>=r){
return tree[pr].val-tree[pl].val;
}
else if(ceil<l){
return 0;
}
int mid=(l+r)/2;
if(mid>=ceil){
return query(ceil,tree[pl].l,tree[pr].l,l,mid);
}
else{
return tree[tree[pr].l].val-tree[tree[pl].l].val+query(ceil,tree[pl].r,tree[pr].r,mid+1,r);
}
}
// 区域内查询元素个数 O(logn)
int query(int floor,int ceil,int l,int r){
if(floor>ceil||l>r){
return 0;
}
return query(ceil,root[l],root[r+1],0,n-1)-query(floor-1,root[l],root[r+1],0,n-1);
}
int kth(int k,int pl,int pr,int l,int r){
int mid=(l+r)/2;
if(l==r){
return l;
}
int lval=tree[tree[pr].l].val-tree[tree[pl].l].val;
if(k>lval){
return kth(k-lval,tree[pl].r,tree[pr].r,mid+1,r);
}
else{
return kth(k,tree[pl].l,tree[pr].l,l,mid);
}
}
// 区间第k小 O(logn)
int kth(int k,int l,int r){
return kth(k,root[l],root[r+1],0,n-1);
}
};
struct Area{
int l=0,r=-1,d=0,u=-1;
};
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,q;
cin>>n;
PersistentSegmentTree tree(n);
vector<int> arr(n);
for(int i=0;i<n;i++){
int t;
cin>>t;
t--;
arr[t]=i;
}
for(int i=0;i<n;i++){
tree.update(arr[i]);
}
cin>>q;
vector<Area> seq(1+q);
seq[0]={0,n-1,0,n-1};
for(int i=1;i<=q;i++){
int t,s,x;
cin>>t>>s>>x;
seq[i]=seq[s];
if(t==1){
int cnt=tree.query(seq[i].l,seq[i].r,seq[i].d,seq[i].u);
if(!x){
seq[s].l=seq[s].r+1;
}
else if(x>cnt){
seq[i].l=seq[i].r+1;
}
else{
int m=tree.query(0,seq[i].l-1,seq[i].d,seq[i].u);
seq[s].r=tree.kth(x+m,seq[s].d,seq[s].u);
seq[i].l=seq[s].r+1;
}
}
else{
x--;
if(seq[s].d>x){
seq[s].d=seq[s].u+1;
}
else if(seq[s].u>=x){
seq[s].u=x;
seq[i].d=x+1;
}
else{
seq[i].d=seq[i].u+1;
}
}
cout<<tree.query(seq[i].l,seq[i].r,seq[i].d,seq[i].u)<<'\n';
}
return 0;
}