zoukankan      html  css  js  c++  java
  • CF878E Numbers on the blackboard

    一、题目

    点此看题

    二、解法

    (3300) 的题啊,就差临门一脚了 (...)

    直接做有点难,我们观察操作结构设计图论模型,因为这是相邻两个数配对的问题,那么如果两个数配对我们新建一个点表示它们配对后的数,然后把它们和新点连一条边,发现最后是一颗二叉树的结构。

    定义某点的深度为从根到它向右走的次数,不难发现每个叶子的贡献是 (2^{dep_i}cdot a_i),问题是最大化贡献。

    区间 (dp) 没有前途,可以先设计一个朴素 (dp),设 (dp[i][x]) 表示第 (i) 个点的深度是 (x) 剩下的最大数:

    [dp[i][x]=dp[i-1][x-1]+2^xcdot a_i,x>1 ]

    [dp[i][1]=max (dp[i-1][y])+2cdot a_i ]

    初始化 (dp[1][0]=a_1),这个 (dp) 可以很显然的降维,设 (dp[i]) 表示前 (i) 个点剩下的最大数:

    [dp[i]=sum_{j=1}^{i-1}dp[j]+cal(j+1,i) ]

    其中 (cal(l,r)=sum_{i=1}^{r-l+1}a_{i+l-1}cdot 2^{i}),其实熟练的选手可以直接设计这个一维 (dp),因为他的本质是枚举最后一段"链",所以可以考虑所有情况。

    但是本题有一个极其特殊的地方:答案对1e9+7取模,翻译一下就是不允许你使用取 (max) 之类的操作,那么自然就不能 (dp) 了。我们考虑 (dp) 转贪心,它的主要思路是考虑转移点的性质然后直接取转移点。

    考虑已经得到了 (dp[i-1]) 的转移点集合 (s),考虑如果 (a_i) 是负数那么往 (s) 里面添加转移点 (i),否则把 (i) 合并到前一个转移段是最优的,并且我们发现如果 (i) 的转移段权值为正,那么就可以直接往前合并。

    简单的证明一下:不难发现这样操作是当前最优的,我们考虑这样操作有没有后效性,由于合并的条件是条件是当前转移段权值为正,那么无论当前的合并怎样都不会影响后面的合并的。

    但是这道题还要我们解决区间问题([OID脏话]),可以套路地离线,然后固定右端点,维护每一个左端点的答案,用并查集维护转移段,再维护转移段的权值前缀和。不难发现第一个转移段可能取不满,但是这并不会对合并造成影响,所以段还是以前的那些段,那么可以直接计算权值。

    注意因为 (dp) 初始化的原因第一段的权值是要除以二的,因为第一个元素一开始直接作为根。

    三、总结

    其实 (dp) 转贪心能算做优化 (dp) 的方法,考虑转移点的性质即可。

    四、后记

    我把这道题出成模拟赛之后又多了很多新鲜做法,我把它们尽可能记录下来:

    从二叉树的结构考虑,我们把"深度"修改成真正的深度,也就是把左儿子合并到根上,那么深度的定义就是树上的深度了,举个例子:

    这棵多叉树的先序遍历就是原序列,考虑先前存在的任意一种树形结构,加入 (i) 点,如果 (a_i) 权值为正那么直接接到最后遍历到的节点上,获得最大的系数,并且和上一个点绑定。如果 (a_i) 权值为负那么直接接到根上,获得 (2) 的系数,通过上述分析我们也获得了关于转移段的结论。

    #include <cstdio>
    #include <vector>
    using namespace std;
    const int MOD = 1e9+7;
    const int M = 100005;
    #define int long long
    const int inf = 2e9;
    int read()
    {
    	int x=0,flag=1;char c;
    	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
    	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    	return x*flag;
    }
    int n,m,a[M],ans[M],pr[M],sf[M],fa[M],f[M],l[M],pw[M];
    struct node
    {
    	int x,id;
    };vector<node> q[M];
    int find(int x)
    {
    	if(x!=fa[x]) fa[x]=find(fa[x]);
    	return fa[x];
    }
    void merge(int x,int y)
    {
    	if(l[x]>31 || f[x]+(f[y]<<l[x])>inf) f[x]=inf;
    	else f[x]+=f[y]<<l[x];
    	l[x]+=l[y];
    	fa[y]=x;
    }
    int cal(int l,int r)
    {
    	return (sf[l]-sf[r+1]*pw[r-l+1])%MOD;
    }
    signed main()
    {
    	n=read();m=read();pw[0]=1;
    	for(int i=1;i<=n;i++)
    		pw[i]=pw[i-1]*2%MOD;
    	for(int i=1;i<=n;i++)
    		f[i]=a[i]=read(),fa[i]=i,l[i]=1;
    	for(int i=n;i>=1;i--)
    		sf[i]=(sf[i+1]*2+a[i])%MOD;
    	for(int i=1;i<=m;i++)
    	{
    		int l=read(),r=read();
    		q[r].push_back(node{l,i});
    	}
    	for(int i=1;i<=n;i++)
    	{
    		int x=find(i);
    		while(find(x-1) && f[x]>0)
    			merge(find(x-1),x),x=find(i);
    		pr[x]=(pr[find(x-1)]+f[x])%MOD;
    		for(auto t:q[i])
    		{
    			ans[t.id]=(pr[x]-pr[find(t.x)])*2
    			+cal(t.x,find(t.x)+l[find(t.x)]-1);
    			ans[t.id]=(ans[t.id]%MOD+MOD)%MOD;
    		}
    	}
    	for(int i=1;i<=m;i++)
    		printf("%lld
    ",ans[i]);
    }
    
  • 相关阅读:
    day50 初识JavaScript
    在C#中对Datatable排序【DefaultView的Sort方法】
    Windows Phone 中查找可视化树中的某个类型的元素
    抽象类(abstract)是否可以继承自实体类 ?
    C#遍历指定目录下的所有文件及文件夹
    Log4Net总结
    Firefox 与 IE 对Javascript和CSS的区别
    RSS 订阅
    Win8 URI 方案 ms-appX 用法大全
    ProgressIndicator显示进度条以及一些文字信息
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15136200.html
Copyright © 2011-2022 走看看