zoukankan      html  css  js  c++  java
  • AtCoder Regular Contest 126

    ( ext{C - Maximize GCD})

    解法

    乍一看很想二分,不过这不满足单调性。但是计算出序列的 (max)(x),发现大于等于 (x) 的值是可以二分的。事实上这一部分也不需要二分,直接用表达式计算即可。

    现在问题就是判断属于 ([1,x)) 的答案是否合法。我比较 ( m nt),想了个分块:对于小于 (sqrt n) 的数,(mathcal O(n)) 计算是否合法;反之,假设枚举的数为 (t),可以按照 (kt) 为界将数列分段,容易发现不超过 (frac{x}{t}) 段。

    但是直接分段其实就可以了,因为 (sum_{t=1}^x frac{x}{t}) 就是 (xlog x) 级别啊。

    代码

    #include <cstdio>
    #define print(x,y) write(x),putchar(y)
    
    template <class T> 
    inline T read(const T sample) {
    	T x=0; char s; bool f=0;
    	while((s=getchar())>'9' or s<'0')
    		f|=(s=='-');
    	while(s>='0' and s<='9')	
    		x=(x<<1)+(x<<3)+(s^48),
    		s=getchar();
    	return f?-x:x;
    }
    
    template <class T>
    inline void write(const T x) {
    	if(x<0) {
    		putchar('-');
    		write(-x);
    		return;
    	}
    	if(x>9) write(x/10);
    	putchar(x%10^48);
    }
    
    #include <iostream>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    
    const int maxn=3e5+5,B=700;
    
    int n,a[maxn],mx;
    ll k,pre[maxn];
    
    signed main() {
    	n=read(9),k=read(9ll); ll s=0;
    	for(int i=1;i<=n;++i)
    		a[i]=read(9),mx=max(mx,a[i]),s+=a[i];
    	if(s+k>=1ll*mx*n) {
            printf("%lld
    ",(s+k)/n);
            return 0;
        }
    	sort(a+1,a+n+1);
    	for(int i=1;i<=n;++i)
    		pre[i]=pre[i-1]+a[i];
    	ll ans=1;
    	for(int i=mx;i>=1;--i) {
    		ll ret=0;
    		for(int j=1;(j-1)*i<mx;++j) {
    			int l=upper_bound(a+1,a+n+1,(j-1)*i)-a;
    			int r=upper_bound(a+1,a+n+1,j*i)-a-1;
    			if(j*i>mx) r=n;
    			if(l>r or a[l]>j*i)
    				continue;
    			ret+=1ll*j*i*(r-l+1)-(pre[r]-pre[l-1]);
    		}
    		if(ret<=k) {
    			ans=i;
    			break;
    		}
    	}
    	print(ans,'
    ');
    	return 0;
    }
    

    ( ext{D - Pure Straight})

    解法

    首先令 (m=lceil k/2 ceil),初始下标集合 (I=(i_1,i_2,...i_k))(升序),最终下标集合 (J=(j_1,j_2,...j_k))(这是连续的)。

    假如 (I,J) 已经选定,设 (d(p)=|i_p-j_p|)(dis(I,J)=sum_{p=1}^k d(p))( m inv)(I) 对应序列的逆序对数。那么最小步数就是 (dis(I,J)+ m inv)。证明就是先花费 (dis(I,J))(I) 挪到 (J) 肯定是最优的,这个证明比较经典,就不再赘述。接下来需要在 (I) 内部进行交换,使得 (A_{I_1},A_{I_2},...A_{I_k}) 变成升序,由于此时 (I_i,I_{i+1}) 都只差 (1),实际上就是逆序对数。

    有这样的结论:对于 给定(I),最终最优的 (J) 一定有 (i_m=j_m)。具体可以这样证明:

    • 如果 (i_m<j_m),将 (J) 集合整体向左移。对于 (pin[1,m]) 都有 (d(p):=d(p)-1)。这是由于 (j_1)(j_m) 的空隙是最小的情况,故对于 (pin[1,m]) 在移动前都有 (i_p<j_p)。对于 (pin (m,k]) 则有 (d(p)) 至多增加 (1)
    • 如果 (i_m>j_m),将 (J) 集合整体向右移。证明方法类似。

    由于每次保证 (d(p)) 减一的区间都大于或等于 (d(p)) 至多增加 (1) 的区间,所以可以证明将 (j_m) 往靠近 (i_m) 移动,结果会变得更优。

    证明了这些结论,终于可以开始愉快地写代码啦!笑死,根本写不来

    首先可以明确只需要选定合适的 (I) 即可,因为此时 (J) 是固定的。先假设 (dp_S) 为选数的权值集合为 (S) 的最小步数,(cnt(S))(S)(1) 的个数。如果我们从 (1)(n) 加入数字,就可以惊喜地发现,当 (cnt(S)=m) 时,此时新加入的数的下标即为 (i_m)真的好特喵妙啊!

    (mathtt{dp}) 的一个瓶颈就在于 (d(p)) 很不好计算,既然我们知道了 (i_m),是否能将 (d(p)) 拆一下,将一些值拖到 (i_m) 时再计算呢?

    • (pin[1,m))(d(p)=j_p-i_p=(j_m+p-m)-i_p)
    • (pin(m,k])(d(p)=i_p-j_p=i_p-(j_m+p-m))

    其中 (p,m,i_p) 都可以即时计算。对于 (j_m),当 (k) 为奇数时其实就抵消了;为偶数时 (i_{m+1}) 没被抵消,所以在计算到 (i_m) 时需要减去 (i_m)

    总复杂度 (mathcal O(n2^k))

    代码

    #include <cstdio>
    #define print(x,y) write(x),putchar(y)
    
    template <class T> 
    inline T read(const T sample) {
    	T x=0; char s; bool f=0;
    	while((s=getchar())>'9' or s<'0')
    		f|=(s=='-');
    	while(s>='0' and s<='9')	
    		x=(x<<1)+(x<<3)+(s^48),
    		s=getchar();
    	return f?-x:x;
    }
    
    template <class T>
    inline void write(const T x) {
    	if(x<0) {
    		putchar('-');
    		write(-x);
    		return;
    	}
    	if(x>9) write(x/10);
    	putchar(x%10^48);
    }
    
    #include <cstring>
    #include <iostream>
    using namespace std;
    
    int n,k,a[205],dp[1<<16];
    
    int main() {
    	n=read(9),k=read(9);
    	for(int i=1;i<=n;++i)
    		a[i]=read(9);
    	int m=k+1>>1;
    	memset(dp,0x3f,sizeof dp);
    	dp[0]=0;
    	for(int i=1;i<=n;++i)
    		for(int j=(1<<k)-1;j>=0;--j) {
    			if(!((j>>a[i]-1)&1))
    				continue;
    			int cnt=__builtin_popcount(j),fr=j^(1<<a[i]-1);
    			int add=__builtin_popcount(j>>a[i]);
    			if(cnt<m)
    				dp[j]=min(dp[j],dp[fr]+add-i+cnt-m);
    			else if(cnt>m)
    				dp[j]=min(dp[j],dp[fr]+add+i-cnt+m);
    			else
    				dp[j]=min(dp[j],dp[fr]+add+((k&1)?0:-1)*i);
    		}
    	print(dp[(1<<k)-1],'
    ');
    	return 0;
    }
    

    ( ext{E - Infinite Operations})

    解法

    (F(A)=sum_{i<j}|A_i-A_j|),我们可以证明 (lim_{n ightarrow infty}f(n)=F(A)/2)

    首先证明 (lim_{n ightarrow infty}f(n)le F(A)/2)。可以分类讨论:

    • 如果对 权值 相邻的 (A_i,A_j) 进行操作,得到了 (x) 分。(F(A)) 减少 (2x)
    • 如果对 权值 不相邻的 (A_i,A_j) 进行操作,得到了 (x) 分。(F(A)) 至少减少 (2x),因为对于 (A_i,A_j) 权值中间的数 (x),由于 (A_i,A_j) 行走的距离相等,当 (A_i,A_j) 都没有触及 (x) 时,(x)(A_i,A_j) 的距离是减小的;触及 (x) 之后,距离就不变了。所以整体是减小的。

    证明了答案的上界后,我们只需要证明总有一种方案可以达到 (F(A)/2)。因为每次用权值相邻的 (A_i,A_j) 操作对 (A_i,A_j) 对其它数的距离没有影响,所以直接对这两个数操作就行了啊。

    代码

    代码太丑了,就不挂了。

  • 相关阅读:
    【心情】12月22日
    【心情】12月12日
    【心情】12月8日
    【转载】信息学竞赛知识地图
    SharePoint 2013 工作流之年假审批Designer配置篇
    SharePoint 2013 状态机工作流之扩展自定义状态
    SharePoint 2013 状态机工作流之UpdateItemActivity
    SharePoint 2013 状态机工作流之日常报销示例
    SharePoint 2013 对二进制大型对象(BLOB)进行爬网
    SharePoint 2013 图文开发系列之应用程序页
  • 原文地址:https://www.cnblogs.com/AWhiteWall/p/15314677.html
Copyright © 2011-2022 走看看