• [省选联考2022] 序列变换


    好题。

    考虑先对括号包含关系对括号序列进行构树。

    那么考虑实际上一/二操作的实质是:

    在同一层中:每次可以选择两个点 \((A,B)\),以 \(val_A * x + val_B * y\) 代价把 \(B\) 丢到下一层,要求每一层只有一个数。

    考虑分类讨论:

    设本层有 \(m\) 个,最小值是 \(mn\),最大值是 \(mx\) .

    \(x = 1,y = 1\)
    一个显然的最优方案是:
    每个数都会有一次代价,然后每下放一个数都会有一个另外的数的代价,那么答案有:
    \((m - 2) * mn + sum\)
    即每次留在这层的都是 \(mx\)

    \(x = 0,y = 1\)
    显然也每次留在这层的是 \(mx\),答案有:\(sum - mx\)

    \(x = 1,y = 0\)
    听说原本只有这么一种情况的。

    考虑一种贪心:每次都往下以最小值的代价传数,最后看保留的是哪一位数:
    考虑每个数最终都会到一个位置保留,除了最后一个位置不用付代价外都要付代价
    那么自然想到把最大值给留到最后。
    但是我们发现这样在只有两个的时候不成立,因为如果传最大值,而不传最小值有可能影响后续取数答案。
    那么考虑找到第一个加上上面传下的数量大于\(2\)的位置然后分段,那么从前面来的要么是前缀最大值,要么是前缀最小值,考虑两种情况都跑即可。
    小细节:如果有全局最大值,取位置最后的,来保证最小值影响范围大。

    点击查看代码
    //晦暗的宇宙,我们找不到光,看不见尽头,但我们永远都不会被黑色打倒。——Quinn葵因
    #include<bits/stdc++.h>
    #define ll long long
    #define N 400005
    
    char s[N << 1];
    
    int n,x,y;
    
    inline int read(){int x;scanf("%d",&x);return x;}
    
    int now = 0;
    
    using std::vector;
    
    vector<ll>G[N];
    
    using std::multiset;
    
    multiset<ll>S;
    
    ll siz,sum,mn,mx;
    
    #define IT std::set<ll>::iterator
    
    ll ans;
    
    inline void sub1(){
    	for(int i = 1;i <= n;++i){ 
    		for(auto v : G[i])
    		S.insert(v),siz ++ ,sum += v;
    		IT it = S.begin();mn = *it;it = S.end();--it;mx = *it;
    		ans = ans + sum + (siz - 2) * mn;
    		S.erase(it);siz -= 1;sum -= mx;
    	}
    }
    
    ll MX;
    
    inline void sub2(){
    	for(int i = 1;i <= n;++i){
    		for(auto v : G[i])
    		S.insert(v),siz ++ ,sum += v;
    		IT it = S.begin();mn = *it;it = S.end();--it;mx = *it;
    		ans = ans + sum - mx;
    		S.erase(it);siz -= 1;sum -= mx;
    	}
    }
    
    int d;
    
    inline ll work(int now){
    //	std::cout<<"WORK "<<now<<"\n";
    	ll res = 0;
    	for(int i = now;i <= n;++i){
    		for(auto v : G[i])
    		S.insert(v),siz ++ ,sum += v;
    		IT it = S.begin();mn = *it;it = S.end();--it;mx = *it;
    		if(i >= d){--it,mx = *it;res = res + (siz - 2) * mn + mx;S.erase(it);siz -= 1;sum -= mx;}
    		else{res = res + (siz - 2) * mn + mx;S.erase(it);siz -= 1;sum -= mn;}
    //		std::cout<<"DEP "<<mx<<" "<<mn<<" "<<res<<"\n";		
    	}
    	return res; 
    }
    
    int flg;
    
    ll SI;
    
    ll MN;
    
    inline void sub3(){
    	//nothing haha
    	for(int i = 1;i <= n;++i)
    	for(auto v : G[i]){if(v > MX)MX = v,d = i;}
    	ans = work(1);
    	S.clear();sum = siz = 0;
    	flg = 0;
    	for(int i = 1;i <= n;++i){
    		if(G[i].size() != 1 && flg){
    			MX = 0;
    			MN = *S.begin();
    			for(int j = i;j <= n;++j)
    			for(auto v : G[j]){if(v > MX)MX = v,d = i;} 			
    			ans = std::min(ans,SI - MN + work(i));return ;
    		}
    		if(G[i].size() == 2)flg = 1;		
    		for(auto v : G[i])
    		S.insert(v),siz ++ ,sum += v,SI += flg * v;
    		IT it = S.end();--it;mx = *it;
    		S.erase(it);siz -= 1;sum -= mx;	
    	}	
    } 
    
    int main(){
    //	freopen("bracket.in","r",stdin);
    //	freopen("bracket.out","w",stdout);	
    	scanf("%d%d%d",&n,&x,&y);
    	scanf("%s",s + 1);
    	for(int i = 1;i <= n * 2;++i){
    		if(s[i] == '(')now ++ ;
    		if(s[i] == ')')now -- ;
    		if(s[i] == '(')G[now].push_back(read());
    	}
    	if(x == 1 && y == 1)sub1();
    	if(x == 0 && y == 1)sub2();
    	if(x == 1 && y == 0)sub3();
    	std::cout<<ans<<"\n";
    }
    
  • 相关阅读:
    21-while里的break简单用法
    20-使用while循环求从1累加至100的值
    19-random猜数
    18-random猜数,直到正确。
    17-简化后的石头剪刀布
    16-if实现石头剪刀布
    生成随机数
    转换数字的进制(Integer、Long)
    数字的舍入
    格式化数字
  • 原文地址:https://www.cnblogs.com/dixiao/p/16171948.html
Copyright © 2020-2023  润新知