zoukankan      html  css  js  c++  java
  • Codeforces Round #670 (Div. 2) 详细题解

    Codeforces Round #670 (Div. 2) 详细题解

    A. Subset Mex

    题意

    给出 (t) 个样例, 每个样例中包含一个序列长度 (n) 以及 对应位置的值 (a_i)

    现将序列拆分为两个集合 (A)(B), 使得 (mex(A)+mex(B)) 最大

    其中 (mex( 空集 )) = (0) .

    其中 (mex()) 运算得到的值为集合中 the smallest non-negative integer that doesn't exist in the set【没有出现在集合中的最小非负整数】

    思路

    对 mex() 的性质可以得到,要是的 结果最大,即分给的两个集合最小非负整数最大。

    贪心的思想,每一次取使得mex最大即可(两次遍历)

    code

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn = 105;
    int a[maxn];
    int main(){
    	int t;
    	cin>>t;
    	while(t--){
    		int n;
    		cin>>n;
    		memset(a,0,sizeof(a));
    		for(int i=1;i<=n;i++) {
    			int x; cin>>x;
    			a[x]++;	
    		}
    		int pos1,pos2;
    		for(int i=0;i<=101;i++){
    			if(a[i] <= 0) {
    				pos1 = i;
    				break;
    			}else a[i]--;
    		}
    		for(int i=0;i<=101;i++){
    			if(a[i] <= 0) {
    				pos2 = i;
    				break;
    			}
    		}
    		cout<<pos1+pos2<<endl;
    	}
    } 
    

    B. Maximum Product

    题意

    给一个长度大于 (5) 的序列, 使得取出序列中的 (5) 个不同位置的值乘积最大

    思路

    考虑贪心,将序列排序,正数即为从大到小取的乘积,但是负数会有影响。负数的取值是从小到达【因为绝对值大小】,且要保证取的个数尽可能为偶数(奇数还是负数)

    可以采取枚举方法,在正取 (i)个和 逆向取 (n-i) 个进行组合,(i in 5)的范围,求出最值。

    code

    #include<bits/stdc++.h>
    #define IOS cin.tie(0);std::ios::sync_with_stdio(false);
    using namespace std;
    const int maxn=1e5+100;
    typedef long long LL;
    LL a[maxn],b[maxn];
    bool cmp(LL a,LL b)
    {
    	return a>b;
    }
    int main(){
        IOS
        LL t;cin>>t;
        while(t--){
            LL n;cin>>n;
            for(LL i=0;i<=n+10;i++) a[i]=b[i]=0;
            for(LL i=1;i<=n;i++) {
                cin>>a[i];b[i]=a[i];
            }
            sort(a+1,a+1+n);//小到大
            sort(b+1,b+1+n,cmp);//大到小
            LL ans=-1e18;
            for(LL i=0;i<=5;i++){//拿0,1,2,3,4,5个 
                LL j=5-i;LL sum=1;
                for(LL k=1;k<=i;k++){
                    sum*=a[k];
                }
                for(LL k=1;k<=j;k++){
                    sum*=b[k];
                }
                ans=max(ans,sum);
            } 
        	cout<<ans<<endl;
        }
    	return 0;
    }
    

    题意

    给出一棵树,你需要通过 先任意删除一条边 再任意增加一条边,使得树的重心个数只有一个,且仍满足为树的形式。

    【重心】:删除该点以及相邻边后,所形成的最大联通块【最大子树结点个数最少】最小。且一棵树最多有两个重心, 同时两个重心相邻【此题核心】

    思路

    如果这棵树只有一个重心,那么任意删除并增加同一条边即可

    如果这棵树有两个重心,那么删除两个重心路径之间的任意一边,然后增加上一边使得其中一个删除后连通块更大。假设两个重心为 (x,y) 同时 (x)(y) 的父节点,那么只需要 【取下(y)子树上的叶子并连接到(x)上即可】

    cut a leaf from y's subtree and link it with x. After that, x becomes the only centroid.

    还有一个问题 ,如何找到重心:

    在 DFS 中计算每个子树的大小,记录“向下”的子树的最大大小,利用总点数 - 当前子树(这里的子树指有根树的子树)的大小得到“向上”的子树的大小。在该问题中将 (1) 作为树的根节点即可

    code

    #include<bits/stdc++.h>
    #define _for(i,a,b) for( int i=(a); i<(b); ++i)
    #define _rep(i,a,b) for( int i=(a); i<=(b); ++i)
    using namespace std;
    const int maxn = 1e5+10;
    vector<int>g[maxn];
    int minn;
    int fa[maxn],siz[maxn];
    int c1,c2;
    int n;
    void dfs(int x,int f){
    	fa[x] = f,siz[x] = 1;
    	int mx = 0;
    	for(int y:g[x]){
    		if(y==f) continue;
    		dfs(y,x);
    		siz[x] += siz[y];
    		mx = max(mx,siz[y]);
    	}
    	mx = max(mx,n-siz[x]);//x最为根,上面的树大小 
    	if(mx<minn) minn = mx,c1 = x,c2 = 0;
    	else if(mx == minn) c2 = x;//第二个重心 
    }
    int L;//叶子结点 
    void dfs2(int x,int f){
    	if(g[x].size() == 1){
    		L = x;
    		return;
    	}
    	for(int y:g[x]){
    		if(y==f) continue;
    		dfs2(y,x);
    	}
    }
    int main(){
    	int t;
    	cin>>t;
    	while(t--){
    		cin>>n;
    		minn = 1e9;
    		_rep(i,1,n) g[i].clear(),fa[i] = 0;
    		_for(i,1,n){
    			int u,v;
    			cin>>u>>v;
    			g[u].push_back(v),g[v].push_back(u);	
    		}
    		dfs(1,0);
    		//只有一个重心 
    		if(!c2){
    			//删除 根 1 的任意一边 
    			cout<<"1 "<<g[1][0]<<endl;
    			cout<<"1 "<<g[1][0]<<endl;
    		}else{
    			//假设c1为c2的父节点
    			if(fa[c1] != c2) swap(c1,c2);
    			dfs2(c1,c2);
    			//删除作为父节点的子树叶子并连接在另一个重心上 
    			cout<<L<<' '<<fa[L]<<endl;
    			cout<<L<<' '<<c2<<endl;
    		}
    	}
    } 
    

    D. Three Sequences

    题意

    给定一个长为 (n) 序列, 你需要构造两个序列 (b) , (c) 使得

    • (for) $every $ (i ,(1≤i≤n) b_i+c_i = a_i)
    • b is non-decreasing, which means that for every (1<i≤n), (bi≥bi−1)must hold【b是非单调递减序列】
    • c is non-increasing, which means that for every (1<i≤n) ,(ci≤ci−1)must hold【c是非单调递增序列】

    构造出 (b) , (c) 后,你需要使得 (max(b_i,c_i)) 最小

    同时还有(q) 次操作,第 (i) 次操作使得 ([l,r]) 增加 (x) ,对于每一次修改后都要输出 (max(b_i,c_i))

    思路

    从题意中即可得 (ans = max(b_n,c_1))

    对于区间修改可以使用 树状数组,也可以使用差分

    转化一下数学模型可以将(a_i) 视为下列分布的点 , y 轴为 (a_i) 的值, x 轴为序列序号

    要使得 b,c 满足单减单增,则必须满足 (bi>a_i) 并且 (b_i leq b_{i-1}) .

    所以如下图所示,如果采用贪心的思路,将 (b_1 = a_1 = c_1) ,那么依次递推则可以得到贪心所得解.

    如果对所有的点进行一次贪心取,最有解即可得到全部解. 由于序列长 (nin 1e5) 所以,这种 (n^2) 的方法不可取

    (a_i>a_{i-1}then) (b_i=b_{i−1}+a_i−a_{i−1}) (and) (c_i=c_{i−1})

    Else if $ a_i<a_{i−1}$ (then) (b_i=b_{i−1}) (but) (c_i=c_{i−1}+a_i−a{i−1})

    所以由次规律计算 (sum max(0,a_i-a_{i-1})) 得到结果假设为 (K) ,(c_1)的值假设为 (x),那么 (b_n) 最后结果即为 (a_1-x+K),所以我们只需将(max(x,a_1-x+K)) 最小化即可

    其中(K) 为定值,所以 (x=a_1-x+K) 时即为最小,两个一次函数的交点

    所以在改变时,考虑 (a_l-a_{l-1})(a_r - a_{r-1}) 即可 【差分性质】

    code

    #include<bits/stdc++.h>
    #define IOS ios::sync_with_stdio(0); cin.tie(0);
    #define _for(i,a,b) for(int i=(a);i<=(b);i++)
    typedef long long ll;
    using namespace std;
    const int maxn = 1e5+5;
    ll a[maxn];
    ll sumg = 0,suml = 0;
    int n;
    void cg(int x,ll y){
    	if(x>n)return;
    	if(a[x]>0) sumg-=a[x];
    	a[x]+=y;
    	if(a[x]>0) sumg+=a[x];
    }
    int main(){
        IOS
        cin>>n;
        _for(i,1,n){
            cin>>a[i];
            if(i>=2){
                if(a[i]-a[i-1]>0) sumg += a[i]-a[i-1];
                //else suml += a[i-1]-a[i];
            }
        }
        for(int i=n;i;i--) a[i] = a[i] - a[i-1];
        ll a1 = a[1];
        ll ans = (ll)ceil((double)(a1+sumg)/2.0);
        cout<<ans<<endl;
        int q;
        cin>>q;
        _for(i,1,q){
            int l,r;
            ll x;
            cin>>l>>r>>x;
            if(l==1) a1+=x;
            else cg(l,x);
            cg(r+1,-x);
            //cout<<c[l]<<' '<<c[r+1]<<endl;
            ll ans = (ll)ceil((double)(a1+sumg)/2.0);
            cout<<ans<<endl;
        }
    }
    

    E. Deleting Numbers

    题意

    交互题

    给出 (n) 表示的(1,2,..n)的序列并且有一个未知的 [公式] ,接下来可以进行至多 [公式] 次询问找到 [公式] ,有三种形式的询问:

      1. [公式] :询问当前序列有多少个数是 [公式] 的倍数;
      1. [公式] :询问当前序列有多少个数是 [公式] 的倍数并将其删除,但 [公式] 永远不会被删除(此处 [公式]不能为 [公式] );
      1. [公式] :答案 [公式][公式]
    • 数据范围: [公式]

    可以理解为猜数字了

    思路

    如果我们知道一个质数因子 x ,我们可 找到 (x) 通过暴力查找的方法

    要找到这个素数因子,我们可以通过 (Bspace p) 升序查找所有的质数 (p) ,同时计算除 (x) 以外的个数,如果不符合对应个数的花,则 (x) 含有素数因子 (P)

    这样我们可以找到所有素数因子除了最小的一个

    假设 (m) 为 不超过 (n) 的素数个数

    我们可以将其分到 (sqrt m)

    每一次询问一个组,(Aspace 1) 并且确认返回值与除(x) 以外相同

    如果第一次找到不同,意味着最小的素数在这个范围中,再确认所有在这个范围中的素数

    在找到素数因子后,对于每个因子,查询(Aspace p^k), 在时间复杂度 (log(n)) 以内

    所以总的时间复杂度 (m+2sqrt m+log(n)), 即为 (O(nlogn))

    【详细参考官方题解】 链接

    Code

    http://codeforces.com/contest/1406/submission/92671740

  • 相关阅读:
    (转载)VS2010/MFC编程入门之三十七(工具栏:工具栏的创建、停靠与使用)
    (转载)VS2010/MFC编程入门之三十五(菜单:菜单及CMenu类的使用)
    (转载)VS2010/MFC编程入门之三十三(常用控件:标签控件Tab Control 下)
    (转载)VS2010/MFC编程入门之三十四(菜单:VS2010菜单资源详解)
    ubuntu下格式化u盘
    Android笔记之AppWidget
    Android笔记之ViewPager实现滑动页面
    Android笔记之获取布局中的多个子控件
    Android笔记之Actionbar制作选项卡(可滑动)
    Android笔记之Actionbar使用(二)
  • 原文地址:https://www.cnblogs.com/Tianwell/p/13674341.html
Copyright © 2011-2022 走看看