zoukankan      html  css  js  c++  java
  • CodeForces 1334F

    eJOI做不动了,来水个以前留下的坑(

    洛谷题目页面传送门 & CodeForces题目页面传送门

    题意见洛谷里的翻译。(翻译得够清楚了吧,translated by Alex_Wei/se/qq)

    先探索(f(x)=y)(forall iin[1,|y|],y_i=x_{z_i})的充要条件。显然(z_1=1)。不妨设(z_{|y|+1}=|x|+1),那么(forall iin[1,|y|]):为了让((z_i,z_{i+1}))内所有元素都留不下来,前面必定要有元素大于等于它们,又因为(x_{z_i})留下来了,所以(x_{z_i})大于前面所有元素,于是(forall jin(z_i,z_{i+1}),x_jleq x_{z_i})是必要的;为了让(x_{z_{i+1}})(如果存在的话)留下来,它肯定要大于前面所有元素,即(forall jin(z_i,z_{i+1}),x_j<x_{z_{i+1}})是必要的。由于(y_i<y_{i+1})(题目保证),综合一下就变成了(forall iin[1,|y|],forall jin(z_i,z_{i+1}),x_jleq x_{z_i})作为必要条件。此时不难发现充分性也成立。over。

    不妨令(n=n+1,m=m+1)并令(a_n=n,b_m=m)。设(id=b^{-1})。就有了一个比较显然的DP:若(a_iin{b_j})(dp_i)有意义且表示考虑到位置(i),若(a_i)(b)的最后一位,需要花的最小代价。边界(dp_0=0),目标(dp_n)(=+infty)( exttt{No}))(体现了之前在(a,b)结尾分别追加(n+1,m+1)的意义,为了方便让(a)的最后一位也是(b)的最后一位)。

    转移的话,考虑枚举决策(j),将((j,i))之间该删的删掉,(p)值为负的也顺带删一下,则

    [dp_i=min_{jin[0,i),a_j=b_{id_{a_i}-1}}left{dp_j+sum_{k=j+1}^{i-1}left[a_k>b_{id_{a_i}-1}lor p_k<0 ight]p_k ight} ]

    这个(sum)里的东西可以分成两类:(a_k>b_{id_{a_i}-1},p_kgeq0)(p_k<0),两种都用前缀和变一下形,然后two-pointers优化一下DP即可。其中第(1)类的前缀和需要值域BIT(log)求。

    (mathrm O(nlog n))。(还是挺简单的吧)

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    const int inf=0x3f3f3f3f3f3f3f3f;
    int lowbit(int x){return x&-x;}
    const int N=500001,M=N;
    int n,m;
    int a[N+1],p[N+1],b[M+1];
    int id[N+1];
    struct bitree{//BIT
    	int sum[N+1];
    	void init(){memset(sum,0,sizeof(sum));}
    	void add(int x,int v){
    		while(x<=n)sum[x]+=v,x+=lowbit(x);
    	}
    	int Sum(int x){
    		int res=0;
    		while(x)res+=sum[x],x-=lowbit(x);
    		return res;
    	}
    	int _sum(int l,int r){return Sum(r)-Sum(l-1);}
    }bit;
    int now[M+1];//two-pointers优化DP 
    int dp[N+1];
    signed main(){
    	cin>>n;
    	for(int i=1;i<=n;i++)scanf("%lld",a+i);
    	n++;a[n]=n;
    	for(int i=1;i<n;i++)scanf("%lld",p+i);
    	cin>>m;
    	for(int i=1;i<=m;i++)scanf("%lld",b+i),id[b[i]]=i;
    	b[++m]=n;id[n]=m;//追加 
    	bit.init();
    	memset(now,0x3f,sizeof(now));
    	now[0]=0;
    	int ne=0;//目前第2类的前缀和 
    	for(int i=1;i<=n;i++){//DP 
    		if(id[a[i]]){
    			if(now[id[a[i]]-1]<inf)dp[i]=now[id[a[i]]-1]+bit._sum(b[id[a[i]]-1]+1,n)+ne;
    			else dp[i]=inf;//计算DP值 
    			if(p[i]>=0)bit.add(a[i],p[i]);
    			else ne+=p[i];//加入a[i],更新某类的前缀和 
    			if(dp[i]<inf)now[id[a[i]]]=min(now[id[a[i]]],dp[i]-bit._sum(a[i]+1,n)-ne);//two-pointers 
    //			printf("dp[%lld]=%lld
    ",i,dp[i]);
    		}
    		else{//加入a[i],更新某类的前缀和 
    			if(p[i]>=0)bit.add(a[i],p[i]);
    			else ne+=p[i];
    		}
    	}
    	if(dp[n]<inf)cout<<"YES
    "<<dp[n];
    	else puts("NO");//目标 
    	return 0;
    }
    

    然鹅听wjz鸽鸽说有(mathrm O(n))的方法但是不会(orz),为了吊打他,我当然要把(mathrm O(n))方法做出来啊(

    于是苦思冥想,想怎么不带(log)地求第(1)类的前缀和,结果发现这就是个二维数点啊怎么可能不带(log)。。就zbl。于是去看(mathrm O(n))题解,恍然大悟,太妙了吧/qiang

    不妨重构状态转移方程。从整体来考虑,计算(dp_i)的时候从一开始一共删去了哪些第(1)类元素呢(第(2)类照原来方法计算)?以下标为横坐标,值为纵坐标,不难发现删去的是一个直角顶点在左上角的直角三角形框住的内部,因为随坐标增大,删除的值的下限(b_{id_{a_i}-1})越高。原来的状态转移方程是延(y)轴的平行线切片的,现在我们延(x)轴的平行线切片,每到计算(dp_i)时就加上切出来的那一片的纵坐标段内所有的元素(因为横坐标是一个前缀),然后two-pointers稍微维护一下即可。至于怎么知道每个元素属于哪个切片?直观上应该是lower_bound的,但那样是带(log)的,于是离线装桶+two-pointers即可线性。

    我知道你不懂我在讲什么,没关系的,我自己看懂就可以了((

    (mathrm O(n))

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    #define pb push_back
    const int inf=0x3f3f3f3f3f3f3f3f;
    const int N=500001,M=N;
    int n,m;
    int a[N+1],p[N+1],b[M+1];
    int id[N+1];
    vector<int> pos[N+1];
    int lwb[N+1];//two-pointers算出来的lower_bound值 
    int add[M+1];//此切片目前应该加的 
    int now[M+1];//two-pointers优化DP 
    int dp[N+1];
    signed main(){
    	cin>>n;
    	for(int i=1;i<=n;i++)scanf("%lld",a+i);
    	n++;a[n]=n;
    	for(int i=1;i<n;i++)scanf("%lld",p+i);
    	cin>>m;
    	for(int i=1;i<=m;i++)scanf("%lld",b+i),id[b[i]]=i;
    	b[++m]=n;id[n]=m;//追加
    	for(int i=1;i<=n;i++)pos[a[i]].pb(i);//装桶 
    	for(int i=1,j=1;i<=n;i++){//two-pointers计算lwb 
    		if(b[j]<i)j++;
    		lwb[i]=j;
    	}
    	memset(now,0x3f,sizeof(now));
    	now[0]=0;
    	int ne=0;//目前第2类的前缀和 
    	for(int i=1;i<=n;i++){//DP 
    		if(id[a[i]]){ 
    			if(now[id[a[i]]-1]<inf)dp[i]=now[id[a[i]]-1]+add[id[a[i]]]+ne;
    			else dp[i]=inf;//计算DP值 
    			if(p[i]>=0)add[lwb[a[i]]]+=p[i];
    			else ne+=p[i];//加入a[i],更新某类的前缀和 
    			if(dp[i]<inf)now[id[a[i]]]=min(now[id[a[i]]],dp[i]-ne);//two-pointers 
    //			printf("dp[%lld]=%lld
    ",i,dp[i]);
    		}
    		else{//加入a[i],更新某类的前缀和 
    			if(p[i]>=0)add[lwb[a[i]]]+=p[i];
    			else ne+=p[i];
    		}
    	}
    	if(dp[n]<inf)cout<<"YES
    "<<dp[n];
    	else puts("NO");//目标 
    	return 0;
    }
    

    最终还是要尛Alex_Wei啊,没有他我根本get不到这题的精髓/cy

  • 相关阅读:
    一致性哈希算法
    Discourse 的标签(Tag)只能是小写的原因
    JIRA 链接 bitbucket 提示错误 Invalid OAuth credentials
    JIRA 如何连接到云平台的 bitbucket
    Apache Druid 能够支持即席查询
    如何在 Discourse 中配置使用 GitHub 登录和创建用户
    Apache Druid 是什么
    Xshell 如何导入 PuTTYgen 生成的 key
    windows下配置Nginx支持php
    laravel连接数据库提示mysql_connect() :Connection refused...
  • 原文地址:https://www.cnblogs.com/ycx-akioi/p/CodeForces-1334F.html
Copyright © 2011-2022 走看看