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

  • 相关阅读:
    php基础语言
    cookie和setting
    php数据连接
    php连接sql
    php提交
    今天学习了php的数据类型
    第一天进入php,这只是自己的一个心情
    02-07 (2) 自连接
    内连接 和左连接查询 02-07 (1)
    out 和ref 的区别
  • 原文地址:https://www.cnblogs.com/ycx-akioi/p/CodeForces-1334F.html
Copyright © 2011-2022 走看看