• 【7.8做题记录】


    先把晚上打的 CF 题目写上:

    A

    考虑看题猜答案,显然是 (abs(a-b).)

    考虑移动的步数,令 (t=abs(a-b),min step=left{amod t,t-amod t ight}.)

    B

    考虑猜结论,首先一个结论显然是要将车辆平均分,所以先将总人数算出来 (mod n)

    观察样例发现,答案恰好是余数乘以 (n-rest.)

    于是结论就对了。

    C

    看到 (v) 的范围发现选的次数肯定很少,考虑爆搜。

    注意 double 的精度问题,要设置 eps=1e-10 来确保不会出现精度损失。

    还有,这是求期望,求出概率记得乘以步数。

    D1

    考虑从 (i) 开始到 (n-1) 每次询问 i xor (i-1) 这样每次密码的影响都会被异或运算消除。

    所以一直询问即可。但是会发现如果密码是 (0) 那么这 (n-1) 次询问都不会问到,这时的密码应该等于 (sum_{xor} i.)

    于是维护异或和即可。

    DP:

    [ZJOI2006]三色二叉树

    考虑递归建树,至于只有一个孩子的节点默认左边就行。设 (f[i][0/1/2]) 表示三种颜色分贝对应的最少 (2) 颜色出现次数(默认绿色是 (2) 了。)

    同时令 (g) 表示最大颜色个数。细节上注意取 (min) 的时候不要让 (0)(f) 值参与。

    #include<bits/stdc++.h>
    using namespace std;
    const int MAXN=5e5+10;
    const int dyx=(1<<30);
    int ls[MAXN],rs[MAXN],node,rt;
    int f[MAXN][3],g[MAXN][3];
    inline int Min(int x,int y){return x<y?x:y;}
    inline int Max(int x,int y){return x>y?x:y;}
    void Read(int &x){
    	x=++node;
    	char c;
    	cin>>c;
    	if(c=='0')return;
    	if(c=='1'){
    		Read(ls[x]);
    		return;
    	}
    	Read(ls[x]);
    	Read(rs[x]);
    	return;
    }
    void dfs(int x){
    	if(ls[x])dfs(ls[x]);
    	if(rs[x])dfs(rs[x]);
    //	printf("%d:ls:%d rs:%d
    ",x,ls[x],rs[x]);
    	f[x][0]=f[x][1]=0;
    	g[x][0]=g[x][1]=0;
    	f[x][2]=g[x][2]=1;
    	//f->min g->max
    	if(!ls[x]&&!rs[x])return;
    	else if(ls[x]&&!rs[x]){
    		f[x][0]=Min(f[ls[x]][1],f[ls[x]][2]);
    		f[x][1]=Min(f[ls[x]][0],f[ls[x]][2]);
    		f[x][2]=Min(f[ls[x]][0],f[ls[x]][1])+1;
    		g[x][0]=Max(g[ls[x]][1],g[ls[x]][2]);
    		g[x][1]=Max(g[ls[x]][0],g[ls[x]][2]);
    		g[x][2]=Max(g[ls[x]][0],g[ls[x]][1])+1;
    	}
    	else if(rs[x]&&!ls[x]){
    		f[x][0]=Min(f[rs[x]][1],f[rs[x]][2]);
    		f[x][1]=Min(f[rs[x]][0],f[rs[x]][2]);
    		f[x][2]=Min(f[rs[x]][0],f[rs[x]][1])+1;
    		g[x][0]=Max(g[rs[x]][1],g[rs[x]][2]);
    		g[x][1]=Max(g[rs[x]][0],g[rs[x]][2]);
    		g[x][2]=Max(g[rs[x]][0],g[rs[x]][1])+1;
    	}
    	else{
    		f[x][0]=Min(f[ls[x]][1]+f[rs[x]][2],f[ls[x]][2]+f[rs[x]][1]);
    		f[x][1]=Min(f[ls[x]][0]+f[rs[x]][2],f[ls[x]][2]+f[rs[x]][0]);
    		f[x][2]=Min(f[ls[x]][1]+f[rs[x]][0],f[ls[x]][0]+f[rs[x]][1])+1;
    		g[x][0]=Max(g[ls[x]][1]+g[rs[x]][2],g[ls[x]][2]+g[rs[x]][1]);
    		g[x][1]=Max(g[ls[x]][0]+g[rs[x]][2],g[ls[x]][2]+g[rs[x]][0]);
    		g[x][2]=Max(g[ls[x]][1]+g[rs[x]][0],g[ls[x]][0]+g[rs[x]][1])+1;
    	}
    //	printf("%d:
    ",x);
    //	for(int i=0;i<3;++i)printf("%d ",f[x][i]);
    //	puts("");
    //	for(int i=0;i<3;++i)printf("%d ",g[x][i]);
    //	puts("");
    }
    int main(){
    	Read(rt);
    	f[0][1]=f[0][2]=f[0][0]=dyx;
    	g[0][0]=g[0][1]=g[0][2]=-dyx;
    	dfs(rt);
    //	cout<<f[4][0]<<" "<<f[4][1]<<" "<<f[4][2]<<endl;
    //	cout<<ls[4]<<" "<<rs[4]<<endl;
    //	cout<<f[ls[4]][1]+f[rs[4]][2]<<" "<<f[ls[4]][2]+f[rs[4]][1]<<endl;
    	int ansA=-1,ansB=dyx;
    	for(int i=0;i<3;++i)ansA=Max(ansA,g[rt][i]),ansB=Min(ansB,f[rt][i]);
    	printf("%d %d
    ",ansA,ansB);
    	return 0;
    }
    

    [SCOI2009]粉刷匠

    考虑区间 (dp) 处理出每一行的区间 ([l,r])(k) 次的最多合法格子数目。

    考虑预处理出每一行区间 ([l,r]) 直接扫一遍能扫到的最大合法格子数。于是 (dp) 的时候考虑枚举分界点和扫的次数,有:

    [dp[l][r][k]=max dp[l][p][k-1]+maxlen[k][r] ]

    复杂度 (O(n^5))

    发现最后我们只需要知道 (f[1,m]) 所以可以考虑用线性 (dp) 优化掉一个 (n)

    做完之后发现每一行都变成了背包中的物品,分组背包即可。

    dwt Orz

    #include<bits/stdc++.h>
    using namespace std;
    const int MAXN=51;
    int a[51][51],n,m;
    int T,dp[MAXN][MAXN][MAXN];
    int maxlen[MAXN][MAXN][MAXN];
    int f[MAXN*MAXN],g[MAXN*MAXN];
    int sum[MAXN][MAXN],v[MAXN][MAXN];
    inline int Max(int x,int y){return x>y?x:y;}
    inline int Min(int x,int y){return x<y?x:y;}
    int main(){
    	scanf("%d%d%d",&n,&m,&T);
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=m;++j){
    			char ch;
    			cin>>ch;
    			a[i][j]=ch-'0';
    		}
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=m;++j){
    			sum[i][j]=sum[i][j-1]+a[i][j];
    		}
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=m;++j)
    			maxlen[i][j][j]=1;
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=m;++j)
    			for(int k=j+1;k<=m;++k)
    				maxlen[i][j][k]=Max(sum[i][k]-sum[i][j-1],k-j+1-sum[i][k]+sum[i][j-1]);
    	for(int i=1;i<=n;++i){
    		v[i][0]=0;
    		for(int j=1;j<=m;++j)dp[j][j][1]=1;//
    		for(int len=2;len<=m;++len){
    			for(int l=1;l<=m-len+1;++l){
    				int r=l+len-1;
    				for(int k=l;k<r;++k){
    					dp[l][r][1]=maxlen[i][l][r];//
    					for(int t=2;t<=m;++t){
    						dp[l][r][t]=Max(dp[l][r][t],dp[l][k][t-1]+maxlen[i][k+1][r]);
    					}
    				}
    			}
    		}
    		for(int k=1;k<=m;++k) v[i][k]=dp[1][m][k];//
    		for(int l=0;l<=m;++l)
    			for(int j=0;j<=m;++j)
    				for(int k=0;k<=m;++k)
    					dp[l][j][k]=0;
    	}
    	for(int i=1;i<=n;++i){
    		for(int j=0;j<=T;++j)f[j]=0;//
    		for(int j=1;j<=m;++j){
    			for(int k=T;k>=j;--k){
    				f[k]=Max(f[k],g[k-j]+v[i][j]);
    			}
    		}
    		for(int j=0;j<=T;++j)g[j]=Max(g[j],f[j]);//
    	} 
    	printf("%d
    ",g[T]);
    	return 0;
    }
    //dwt Orz
    

    代码中细节:循环的值搞反了,以及区间枚举分界点的时候少了一种不分界的情况,还有最外层的背包初始化。

    [BJOI2019]排兵布阵

    考虑预处理出每次派出兵的数量对城堡 (i) 的影响,发现每次派兵的人数一定是某个玩家驻守的人数的 (2cdot x+1.)

    于是可以预处理出每种方案“体积”和“价值”,设计 (dp[i][j]) 表示前 (i) 城堡派出 (j) 兵力的最大收获,就变成一个分组背包了。

    傻逼错误:忘记继承了上一次的状态,因为可以考虑对当前城堡不派兵。

    #include<bits/stdc++.h>
    using namespace std;
    const int MAXN=2e4+10;
    int s,n,m;
    int a[200][200];
    int dp[101][20001];
    int v[200][200];
    inline int Min(int x,int y){return x<y?x:y;}
    inline int Max(int x,int y){return x>y?x:y;}
    int main(){
    	scanf("%d%d%d",&s,&n,&m);
    	for(int i=1;i<=s;++i)
    		for(int j=1;j<=n;++j)
    			scanf("%d",&a[j][i]);
    	for(int i=1;i<=n;++i){
    		for(int j=1;j<=s;++j){
    			int val=(a[i][j]<<1)+1;
    			for(int k=1;k<=s;++k){
    				if(val>2*a[i][k])v[i][j]+=i;
    			}
    		}
    	}
    	for(int i=1;i<=n;++i){
    		for(int j=1;j<=s;++j){
    			for(int k=(a[i][j]<<1)+1;k<=m;++k){
    				dp[i][k]=Max(dp[i][k],dp[i-1][k-a[i][j]-a[i][j]-1]+v[i][j]);
    			}
    			for(int k=0;k<=m;++k) dp[i][k] = Max(dp[i][k], dp[i-1][k]);
    			//
    		}
    	}
    	int ans=-1;
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=m;++j)
    			ans=Max(ans,dp[i][j]);
    	cout<<ans<<endl;
    	return 0;
    }
    

    [HAOI2006]数字序列

    第一问用经典转化,减去下标就变成了最长不降子序列,拿线段树搞搞就好。

    关键在于第二问:可以考虑维护每一个 (dp) 状态可以从哪些位置转移过来。

    那么,有一个结论:对于更改的新序列,里面的值一定在 (a) 中出现。

    所以,对于一个区间 ([l,r]) 需要更改,首先它一定由两个最优决策点构成。

    所以我们暴力枚举决策点,再暴力计算这一段区间修改的最小贡献。

    考虑枚举区间断点 (k) 其左边覆盖为 (a[l]) 右边覆盖为 (a[r])

    如果强行枚举复杂度 (O(n^4)) 无法接受。

    所以考虑 (O(len)) 计算区间最小代价:预处理前缀代价和后缀代价再枚举断点即可。

    注意代码细节,传入的区间端点一定要合乎计算函数中的实现。

    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    const int MAXN=2e6+10;
    const int dyx=(1LL<<50);
    int a[MAXN],b[MAXN],n;
    int dp[MAXN],node,rt;
    int ls[MAXN],rs[MAXN],maxn[MAXN];
    int blen,bcnt,g[MAXN];
    int pre[MAXN],suf[MAXN];
    vector<int>vec[MAXN];
    inline int Min(int x,int y){return x<y?x:y;}
    inline int Max(int x,int y){return x>y?x:y;}
    inline int Abs(int x){if(x<0)x=-x;return x;}
    inline void pushup(int x){maxn[x]=Max(maxn[ls[x]],maxn[rs[x]]);}
    inline int getpos(int x){return lower_bound(b+1,b+blen+1,x)-b;}
    void change(int &x,int l,int r,int pos,int v){
    	if(!x)x=++node;
    	if(l==r){
    		maxn[x]=Max(maxn[x],v);
    		return;
    	}
    	int mid=(l+r)>>1;
    	if(pos<=mid)change(ls[x],l,mid,pos,v);
    	else change(rs[x],mid+1,r,pos,v);
    	pushup(x);
    }
    int query(int x,int L,int R,int l,int r){
    	if(L>=l&&R<=r)return maxn[x];
    	int mid=(L+R)>>1,res=-1;
    	if(l<=mid)res=query(ls[x],L,mid,l,r);
    	if(mid<r)res=Max(res,query(rs[x],mid+1,R,l,r));
    	return res;
    }
    int calc(int l,int r){
    	for(int i=l;i<=r;++i)pre[i]=pre[i-1]+Abs(b[a[i]]-b[a[l]]);
    	for(int i=r;i>=l;--i)suf[i]=suf[i+1]+Abs(b[a[i]]-b[a[r]]);
    	int R=dyx;
    	for(int k=l;k<=r;++k)R=Min(R,pre[k]+suf[k+1]);
    	for(int i=l;i<=r;++i)suf[i]=pre[i]=0;
    	return R;
    }
    signed main(){
    	scanf("%lld",&n);bcnt=n+1;
    	for(int i=1;i<=n;++i)scanf("%lld",&a[i]);a[++n]=dyx;
    	for(int i=1;i<=n;++i)a[i]+=n-i+1;
    	for(int i=1;i<=n;++i)b[i]=a[i];
    	for(int i=1;i<=n;++i)b[++bcnt]=a[i]-1;
    	sort(b+1,b+bcnt+1);blen=unique(b+1,b+bcnt+1)-b-1;
    	for(int i=1;i<=n;++i)a[i]=getpos(a[i]);
    	vec[0].push_back(0);
    //	change(rt,1,n,a[1],1);
    	for(int i=1;i<=n;++i){
    		dp[i]=query(rt,1,blen,1,a[i])+1;
    		change(rt,1,blen,a[i],dp[i]);
    		vec[dp[i]].push_back(i);
    	}
    	int ANS=-1;
    	for(int i=1;i<=n;++i)ANS=Max(ANS,dp[i]);
    	cout<<n-ANS<<endl;
    //	for(int i=1;i<=n;++i)cout<<b[a[i]]<<" ";
    //	puts("");
    	ANS=dyx;
    	calc(1,2);
    //	cout<<calc(3,4)<<endl;
    //	cout<<calc(1,4)<<endl;
    //	cout<<calc(1,4)<<endl;
    //	puts("??");
    //	for(int i=0;i<=n;++i){
    //		for(int j=i;j<=n;++j){
    //			printf("calc(%lld %lld)=%lld:",i,j,calc(i,j));
    //		}
    //		puts("");
    //	}
    	for(int i=1;i<=n;++i){
    		g[i]=dyx;
    		for(int j=0;j<(int)vec[dp[i]-1].size();++j){
    			int v=vec[dp[i]-1][j];
    			if(a[v]>a[i])continue;
    			int val=calc(v,i);
    			g[i]=Min(g[i],g[v]+val);
    		}
    	}
    	cout<<g[n]<<endl;
    	return 0;
    }
    

    代码细节:传入的区间端点,以及:离散化的数组有没有排序、询问的时候应该是小于等于 (a[i]) 的数、维护决策点可以用 vector 优化枚举复杂度、每个状态都是可以从 (0) 转移过来、calc函数的边界以及 pre,suf 的清空。

    还要注意:状态设计的是 (b[i]=j) 的最小代价,但是 (b[n]) 不一定等于 (a[n]) 所以可以添加一个最大值到序列末尾来进行转移以保证 (n) 可以都被转移到。

    关于加入一个最大值的作用:答案应该是求一个位置 (pos) 使得 (dp[pos]+calc(pos,n)) 最小。

    如果我们加入一个 (infty), 那么这个 (calc) 函数就一定会被非 (infty) 的数覆盖。而 (g[n+1]) 又正好求出了这个东西的最优解。所以答案就是 (g[n+1].) 利用了 (g) 的定义:以 (i) 为结尾的修复最小代价,以及利用的 (calc) 函数可以利用最大值恰好计算一个后缀除去最大值的性质,以达到恰好计算到最优解(答案)的目的。

    [BOI2003]Gem 气垫车

    dwt Orz

    考虑设 (f_i) 表示凑齐数 (i) 的最小点数,所以 (f_isum f_j[j<i])

    所以这玩意是 (log) 级别的(虽然很难卡满)

    这题以防万一就开 (f[i][15]) 表示节点 (i) 染色是 (k) 的最小权值。

    暴力枚举所有颜色转移即可。(O(nlog^2 n)) 可以记录信息做到 (O(n))

    #include<bits/stdc++.h>
    using namespace std;
    const int MAXN=2e4+10;
    const int dyx=(1<<30);
    int dp[MAXN][15];
    int n,head[MAXN],tot;
    struct E {
    	int nxt,to;
    } e[MAXN];
    inline void add(int x,int y) {
    	e[++tot]=(E) {
    		head[x],y
    	};
    	head[x]=tot;
    }
    inline int Min(int x,int y) {
    	return x<y?x:y;
    }
    void dfs(int x,int fa) {
    	for(int i=1; i<=14; ++i)dp[x][i]=i;
    	for(int i=head[x]; i; i=e[i].nxt) {
    		int j=e[i].to;
    		if(j==fa)continue;
    		dfs(j,x);
    	}
    	for(int k=1; k<=14; ++k) {
    		for(int i=head[x]; i; i=e[i].nxt) {
    			int j=e[i].to;
    			if(j==fa)continue;
    			int M=dyx;
    			for(int l=1;l<=14;++l){
    				if(l==k)continue;
    				M=Min(M,dp[j][l]);
    			}
    			dp[x][k]+=M;
    		}
    	}
    
    }
    int main() {
    	scanf("%d",&n);
    	for(int i=1; i<n; ++i) {
    		int x,y;
    		scanf("%d%d",&x,&y);
    		add(x,y);
    		add(y,x);
    	}
    	for(int i=1; i<=n; ++i) {
    		for(int j=1; j<=14; ++j)
    			dp[i][j]=dyx;
    	}
    	dfs(1,0);
    	int ans=dyx;
    	for(int i=1; i<=14; ++i)ans=Min(ans,dp[1][i]);
    	cout<<ans<<endl;
    	return 0;
    }
    

    [NOIP2003 提高组] 加分二叉树

    看着就是区间 (dp.)

    观察到是中序遍历,所以对于一个区间 ([l,r]) 根一定在其中。

    所以合并区间可以看成选择一个根并合并两棵子树。注意可以直接看成一棵树。就是根等于区间边界的时候;

    否则直接暴力枚举断点转移即可。

    至于输出路径,可以开和 (dp) 一样的数组记录一个区间更新最优的根在哪里,递归输出路径即可。

    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    const int dyx=4e9+10;
    int n,a[50];
    int f[51][51];
    int pre[51][51];
    inline int Max(int x,int y){return x>y?x:y;}
    void print(int l,int r,int p){
    	printf("%lld ",p);
    	if(l==r)return;
    	if(pre[l][p-1])print(l,p-1,pre[l][p-1]);
    	if(pre[p+1][r])print(p+1,r,pre[p+1][r]);
    }
    signed main(){
    	scanf("%lld",&n);
    	for(int i=1;i<=n;++i)scanf("%lld",&a[i]);
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=n;++j)
    			f[i][j]=-dyx;
    	for(int i=1;i<=n;++i)f[i][i]=a[i],pre[i][i]=i;
    	for(int len=2;len<=n;++len){
    		for(int l=1;l<=n-len+1;++l){
    			int r=l+len-1;
    			if(f[l][r-1]+a[r]>=f[l][r]){
    				pre[l][r]=r;
    				f[l][r]=f[l][r-1]+a[r];
    			}
    			if(f[l+1][r]+a[l]>=f[l][r]){
    				pre[l][r]=l;
    				f[l][r]=f[l+1][r]+a[l];
    			}
    			for(int k=l+1;k<r;++k){
    				if(f[l][k-1]*f[k+1][r]+a[k]>=f[l][r]){
    					f[l][r]=f[l][k-1]*f[k+1][r]+a[k];
    					pre[l][r]=k;
    				}
    			}
    		}
    	}
    	cout<<f[1][n]<<endl;
    	print(1,n,pre[1][n]);
    	return 0;
    }
    

    傻逼问题:输出路径的时候区间端点传错参数了……

    [USACO09MAR]Cow Frisbee Team S

    同余背包问题。考虑将物品体积 (mod f.)

    注意这样的话 (01) 背包的倒叙枚举不成立了——因为同余下的大小关系是不确定的。

    所以再开一个数组记录上一次转移的值就好了。

    #include<bits/stdc++.h>
    using namespace std;
    int n,f,r[3000],dp[5000],g[5000];
    const int mod=1e8;
    inline int add(int x,int y){return (x+y)%mod;}
    inline int mul(int x,int y){return 1ll*x*y%mod;}
    int main(){
    //	freopen("111.in","r",stdin);
    	scanf("%d%d",&n,&f);
    	for(int i=1;i<=n;++i)scanf("%d",&r[i]);
    	for(int i=1;i<=n;++i)r[i]%=f;
    	g[r[1]]=1;g[0]=1;
    	for(int i=2;i<=n;++i){
    		for(int j=0;j<f;++j){
    			dp[j]=add(g[(j-r[i]+f)%f],g[j]);
    		}
    		for(int j=0;j<f;++j)g[j]=dp[j],dp[j]=0;
    	}
    	cout<<(g[0]+mod-1)%mod<<endl;
    	return 0;
    }
    

    最后别忘了取模,以及减一。去除 (g[0]=1) 的影响。

    [CSP-S2019] 括号树

    考虑单纯维护一个 (dp) 值应该怎么做:维护一个栈,里面维护左括号的位置,右括号匹配的时候相当于 (dp[i]=dp[pos]+1.)

    所以到树上是类似的,维护一个栈,处理 (dp.) 但是题目给出的定义是从根到某个节点的子串数目,所以还需要做一个前缀和。

    注意维护栈的细节:当前点如果匹配到了一个左括号,回去的时候记得将左括号还回去;如果进入了一个左括号,走的时候要出去。

    (dp)(ans) 数组的转移是不一样的:一个是 (i) 以结尾匹配的子串数目,另一个是对它的前缀和。

    注意开 long long.

    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    const int MAXN=5e5+10;
    namespace Hws {
    	inline int read() {
    		int s=0,w=1;
    		char ch=getchar();
    		while(!isdigit(ch)) {
    			if(ch=='-')w=-1;
    			ch=getchar();
    		}
    		while(isdigit(ch))s=s*10+ch-48,ch=getchar();
    		return w*s;
    	}
    	int top,pa[MAXN],n,cnt,st[MAXN];
    	int head[MAXN],tot,val[MAXN];
    	int ans[MAXN],fg[MAXN],dp[MAXN];
    	struct E {
    		int nxt,to;
    	} e[MAXN];
    	inline void add(int x,int y) {
    		e[++tot].to=y;
    		e[tot].nxt=head[x];
    		head[x]=tot;
    	}
    	void dfs(int x,int fa){
    		int T=0;
    		if(val[x]==1)st[++top]=x;
    		else{
    			if(top)dp[x]=dp[pa[st[top]]]+1,T=st[top--];
    		}
    		ans[x]=ans[fa]+dp[x];
    		for(int i=head[x];i;i=e[i].nxt){
    			int j=e[i].to;
    			dfs(j,x);
    		}
    		if(val[x]==1)--top;
    		else if(T)st[++top]=T;
    	}
    	void Init() {
    		n=read();
    		for(int i=1;i<=n;++i){
    			char ch;
    			cin>>ch;
    			val[i]=(ch=='(');
    		}
    		for(int i=2;i<=n;++i)pa[i]=read(),add(pa[i],i);
    		dfs(1,0);
    		int A=ans[1];
    		for(int i=2;i<=n;++i)A^=(i*ans[i]);
    		cout<<A<<endl;
    	}
    }
    signed main() {
    // 	freopen("111.in","r",stdin);
    	Hws::Init();
    	return 0;
    }
    

    饥饿的奶牛

    找区间不重集合让覆盖长度最大……按照右端点排序,将点离散化用线段树维护 (dp) 值,一个线段的 (dp) 值位移到右端点,每次查询一条线段左端点之前的最大 (dp) 值加上当前线段长度即可。

    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    const int MAXN=3e5+10;
    int n,rt,dp[MAXN];
    struct line {
    	int x,y,len;
    	bool operator<(const line&B)const{
    		return y<B.y;
    	}
    } p[MAXN];
    const int N=2e6+10;
    int ls[N],rs[N],maxn[N],node;
    int b[MAXN],bcnt,blen;
    inline int getpos(int x){return lower_bound(b+1,b+blen+1,x)-b;}
    inline int Max(int x,int y){return x>y?x:y;}
    inline void pushup(int x){maxn[x]=Max(maxn[ls[x]],maxn[rs[x]]);}
    void change(int &x,int l,int r,int pos,int v){
    	if(!x)x=++node;
    	if(l==r){
    		maxn[x]=Max(maxn[x],v);
    		return;
    	}
    	int mid=(l+r)>>1;
    	if(pos<=mid)change(ls[x],l,mid,pos,v);
    	else change(rs[x],mid+1,r,pos,v);
    	pushup(x);
    }
    int query(int x,int L,int R,int l,int r){
    	if(L>=l&&R<=r)return maxn[x];
    	int mid=(L+R)>>1;
    	int res=-1;
    	if(l<=mid)res=query(ls[x],L,mid,l,r);
    	if(mid<r)res=Max(res,query(rs[x],mid+1,R,l,r));
    	return res;
    }
    signed main() {
    // 	freopen("P1868_3.in","r",stdin);
    	scanf("%lld",&n);
    	for(int i=1; i<=n; ++i) {
    		scanf("%lld%lld",&p[i].x,&p[i].y);
    		p[i].x++;p[i].y++;
    		p[i].len=p[i].y-p[i].x+1;
    		b[++bcnt]=p[i].x;
    		b[++bcnt]=p[i].y;
    		b[++bcnt]=p[i].x-1;
    	}
    	b[++bcnt]=0;
    	b[++bcnt]=1;
    	b[++bcnt]=3000010;
    	sort(b+1,b+bcnt+1);
    	blen=unique(b+1,b+bcnt+1)-b-1;
    	int p1=getpos(1);
    	sort(p+1,p+n+1);
    	for(int i=1;i<=n;++i){
    // 		cout<<p[i].x<<" "<<p[i].y<<endl;
    		dp[i]=query(rt,1,blen,p1,getpos(p[i].x-1))+p[i].len;
    		change(rt,1,blen,getpos(p[i].y),dp[i]);
    	}
    	int ans=-1;
    	for(int i=1;i<=n;++i)ans=Max(ans,dp[i]);
    	cout<<ans<<endl;
    	return 0;
    }
    

    注意出现 (0.) 以及查询的时候记得 getpos .

    CF713C Sonya and Problem Wihtout a Legend

    这题看到严格递增先同时减去一个下标,仔细一看这不是跟前两天做的考试题一样嘛……设 (dp[i][j]) 表示 (b[i]=a[j]) 的最小代价,维护一个前缀 (min) 就可以做到 (O(n^2)) 了。

    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    const int MAXN=5001;
    const int dyx=(1LL<<60);
    int dp[MAXN][MAXN];
    int n,a[MAXN];
    int b[MAXN],blen,minn[MAXN];
    inline int getpos(int x){return lower_bound(b+1,b+blen+1,x)-b;}
    inline int Abs(int x){return x<0?-x:x;}
    inline int Min(int x,int y){return x<y?x:y;}
    signed main(){
    //	freopen("CF_in.txt","r",stdin);
    	scanf("%lld",&n);
    	for(int i=1;i<=n;++i)scanf("%lld",&a[i]);
    	for(int i=1;i<=n;++i)a[i]-=i;
    	for(int i=1;i<=n;++i)b[i]=a[i];
    	sort(b+1,b+n+1);
    	blen=unique(b+1,b+n+1)-b-1;
    	for(int i=1;i<=n;++i)a[i]=getpos(a[i]);
    //	for(int i=1;i<=n;++i)cout<<a[i]<<" ";
    //	puts("");
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=n;++j)
    			dp[i][j]=dyx;
    	for(int i=1;i<=n;++i){
    		for(int j=1;j<=n;++j){
    			dp[i][a[j]]=minn[a[j]]+Abs(b[a[i]]-b[a[j]]);
    		}
    		for(int j=1;j<=n;++j)minn[j]=dyx;
    		for(int j=1;j<=n;++j)minn[a[j]]=Min(minn[a[j]],dp[i][a[j]]);
    		for(int j=2;j<=n;++j)minn[j]=Min(minn[j],minn[j-1]);
    	}
    	int ans=dyx;
    	for(int i=1;i<=n;++i)ans=Min(ans,dp[n][i]);
    	cout<<ans<<endl;
    	return 0;
    }
    
  • 相关阅读:
    linux挂载windows共享文件夹
    Cython
    python并行编程
    数据库学习----MySQL 存储引擎
    数据库学习----MySQL 日志
    数据库学习----从文件l数据到数据库
    Golang 学习 ---- 编译打包
    数字转换成千字符
    el-select选择框宽度与输入框相同
    git常用命令总结
  • 原文地址:https://www.cnblogs.com/h-lka/p/14988258.html
Copyright © 2020-2023  润新知