• Atcoder Grand Contest 026 (AGC026) F


    原文链接www.cnblogs.com/zhouzhendong/AGC026F.html

    前言

    太久没有发博客了,前来水一发。

    题解

    不妨设先手是 A,后手是 B。定义 (i) 为奇数时,(a_i) 为"奇数位上的数";(i) 为偶数时, (a_i) 为"偶数位上的数"。定义左、右两端的数分别表示 (a_1)(a_n)

    考虑第一步:

    首先,如果 A 取了左右某一个端点,那么他必然能取走和他取的点奇偶性相同的所有点。

    然后,我们考虑 A 取了一个中间点后会发生什么:如果这个点左边和右边的剩余点数都是奇数,那么无论 B 取左还是右,取完某一边之后,问题规模缩小成另一边的情况,A 一定还是先手;否则 B 就可以取剩余点数为偶数的那一边,并成为先手。

    考虑 n 为偶数的情况。

    如果 A 取了一个中间点,那么一定有一边剩余奇数个,一边剩余偶数个。那么 B 一定先操作偶数个的那一边,然后获得奇数那一边的先手权,然后取最优策略。那么 A 还不如直接取偶数那一边的端点,这样做不仅取到了前一种方案能取到的,而且让 B 在另一边没有了先手选择权,一定不劣于前一种方案。

    所以,当 n 为偶数时,先手能取到的最大值为 max(奇数位之和, 偶数位之和) 。

    n 为奇数的情况较为复杂。

    但是同理,A 不会去取一个位于奇数位的数,这样会导致两边剩余个数都为偶数,不如直接取两端。

    于是,n 为奇数时,A 只有两种策略:

    • 取端点,即拿走所有奇数位的数。
    • 取某一个偶数位的数。此时,如果 B 取左边,那么 A 会继续获得右边的先手权;否则 A 获得左边的先手权。这个过程可以看作问题规模的缩小。

    如果将第二种策略用二叉树的形式表示出来,那么 B 一定会选择某一个叶子,使得最终答案最小。

    考虑先假设所有偶数位的贡献都已被 A 收取,那么 A 在一个区间执行“取端点”操作得到的收益就是这个区间的奇数位之和减去偶数位之和(注意这里的两端点一定都是奇数)。

    我们要做的是找出一个叶子集合,使得对这些叶子“取端点”的收益的最小值尽量大。

    考虑二分答案x,之后问题转化为是否可以删除某些偶数位上的数,使得剩下的序列中任意一个极大的连续段之和都不小于x。

    考虑暴力DP,枚举右端点,然后再暴力枚举前一个划分点。时间复杂度不可接受。

    由于DP信息只有“能”和“不能”,所以我们可以考虑贪心,只保留“能”的点中前缀和最小的即可。

    时间复杂度 (O(nlog sum a_i))

    代码

    #include <bits/stdc++.h>
    #define clr(x) memset(x,0,sizeof x)
    #define For(i,a,b) for (int i=(a);i<=(b);i++)
    #define Fod(i,b,a) for (int i=(b);i>=(a);i--)
    #define pb(x) push_back(x)
    #define mp(x,y) make_pair(x,y)
    #define fi first
    #define se second
    #define outval(x) cerr<<#x" = "<<x<<endl
    #define outtag(x) cerr<<"-----------------"#x"-----------------
    "
    #define outarr(a,L,R) cerr<<#a"["<<L<<".."<<R<<"] = ";
                        For(_x,L,R) cerr<<a[_x]<<" ";cerr<<endl;
    using namespace std;
    typedef long long LL;
    typedef unsigned long long ULL;
    LL read(){
        LL x=0,f=0;
        char ch=getchar();
        while (!isdigit(ch))
            f=ch=='-',ch=getchar();
        while (isdigit(ch))
            x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
        return f?-x:x;
    }
    const int N=300005;
    int n;
    int a[N],s[N];
    bool check(int x){
    	int v=0;
    	for (int i=1;i<n;i+=2)
    		if (s[i]-v>=x)
    			v=min(v,s[i+1]);
    	return s[n]-v>=x;
    }
    int main(){
    	n=read();
    	For(i,1,n)
    		a[i]=read();
    	if (n%2==0){
    		int s0=0,s1=0;
    		For(i,1,n)
    			if (i&1)
    				s0+=a[i];
    			else
    				s1+=a[i];
    		cout<<max(s0,s1)<<" "<<min(s0,s1)<<endl;
    		return 0;
    	}
    	For(i,1,n)
    		if (i&1)
    			s[i]=s[i-1]+a[i];
    		else
    			s[i]=s[i-1]-a[i];
    	int L=1,R=n*1000,mid,ans=L;
    	while (L<=R){
    		mid=(L+R)>>1;
    		if (check(mid))
    			L=mid+1,ans=mid;
    		else
    			R=mid-1;
    	}
    	For(i,1,n)
    		if (i%2==0)
    			ans+=a[i];
    	int s=0;
    	For(i,1,n)
    		s+=a[i];
    	cout<<ans<<" "<<s-ans<<endl;
        return 0;
    }
    
  • 相关阅读:
    App.js – 用于移动 Web App 开发的 JS 界面库
    【入门必备】最佳的 Node.js 学习教程和资料书籍
    Fort.js – 时尚、现代的表单填写进度提示效果
    单页网站不是梦,几款国外的单页网站创建工具
    Numeral.js – 格式化和操作数字的 JavaScript 库
    ShortcutMapper – 热门应用程序的可视化快捷键
    Origami – 用于 Quartz 的免费的交互设计框架
    20款时尚的 WordPress 简洁主题【免费下载】
    JSCapture – 基于 HTML5 实现的屏幕捕捉库
    推荐12款实用的 JavaScript 书页翻转效果插件
  • 原文地址:https://www.cnblogs.com/zhouzhendong/p/AGC026F.html
Copyright © 2020-2023  润新知