Codeforces Round #727 (Div. 2)

E. Game with Cards

题目描述

点此看题

左右手初始都有卡牌 (0),一共 (n) 轮,每一轮把其中一张替换成 (k),左手记为 (x) 右手卡牌记为 (y),要满足 (a_lleq xleq b_l,a_rleq yleq b_r),问是否存在合法方案,若存在输出每一轮应该替换哪只手上的卡牌。

(nleq 100000,mleq 10^9),所有数不超过 (m)

解法

这道题后效性很高,所以不要往贪心方面想,就算是反悔贪心也不是很行。

那么考虑 (dp),但我们要记录每只手上的卡牌?其实没有这个必要,仔细想想我们的决策是每张牌放左手还是右手,那么 (dp) 只处理这个决策就行了,设 (dp[i][0/1]) 表示第 (i) 张牌放左手(/)放右手是否合法。

考虑转移,比如 (dp[i][0]) 的转移,实际上就是枚举前缀 (i) 的一段后缀使其全部都放 (0),这样就找到了递归子问题,对于左手 ((j,i]) 这样放都应该合法,对于右手也要考虑 (k_j) 应该能让 ([j,i]) 都合法。

对于右手的限制发现合法是单调的,那么我们可以维护所有当前所有合法的 (j),一旦发现 (k_j otin[a_r,b_r]) 就可以将其永久删除,对于合法的 (j) 我们找最右边的一个考虑转移即可,因为最右边的 (j) 对于左手的限制是最宽松的。

对于 (dp[i][1]) 的转移同理,我又写了 ( t set) 又写了线段树,实际上根本没有这么麻烦。

总结

这道题如果我们考虑 (i-1)(i) 的转移就要记录权值,但是由于考虑了 (01) 的转折点所以就多获得了足够的信息,避免了权值的影响,所以 (dp) 有时候适当的多考虑一点可能会有助于解题

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <set>
using namespace std;
const int M = 100005;
const int N = 100*M;
#define point set<int>::iterator
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,l[2],r[2],a[M],sum[M][2],p[M][2];
int cnt,rt[2],s[N],ls[N],rs[N],f[M][2];
set<int> jzm[2];
void ins(int &x,int l,int r,int y,int id)
{
	if(!x) x=++cnt;
	if(l==r) {s[x]=id;return ;} 
	int mid=(l+r)>>1;
	if(mid>=y) ins(ls[x],l,mid,y,id);
	else ins(rs[x],mid+1,r,y,id);
	s[x]=max(s[ls[x]],s[rs[x]]);
}
int ask(int x,int l,int r,int L,int R)
{
	if(!x || L>r || l>R) return -1;
	if(L<=l && r<=R) return s[x];
	int mid=(l+r)>>1;
	return max(ask(ls[x],l,mid,L,R),ask(rs[x],mid+1,r,L,R)); 
}
int in(int x,int i)
{
	return l[i]<=x && x<=r[i];
}
void print(int n,int w)
{
	if(!n) return ;
	print(p[n][w],w^1);
	for(int i=p[n][w]+1;i<=n;i++)
		a[i]=w;
}
signed main()
{
	n=read();m=read();
	memset(s,-1,sizeof s);
	ins(rt[0],0,m,0,0),jzm[0].insert(0);
	ins(rt[1],0,m,0,0),jzm[1].insert(0);
	for(int i=1;i<=n;i++)
	{
		int k=read();
		for(int j=0;j<2;j++)
		{
			l[j]=read();r[j]=read();
			if(in(k,j)) sum[i][j]=1;
			sum[i][j]+=sum[i-1][j];
			//delete
			while(jzm[j].size())
			{
				point it=jzm[j].lower_bound(l[j]);
				if(it==jzm[j].begin()) break;
				else it--,ins(rt[j],0,m,*it,-1),jzm[j].erase(it);
			}
			while(jzm[j].size())
			{
				point it=jzm[j].upper_bound(r[j]);
				if(it==jzm[j].end()) break;
				else ins(rt[j],0,m,*it,-1),jzm[j].erase(it);
			}
		}
		//left
		int t=ask(rt[1],0,m,l[1],r[1]);
		if(t>=0 && sum[i][0]-sum[t][0]==i-t)
			f[i][0]=1,p[i][0]=t;
		//right
		t=ask(rt[0],0,m,l[0],r[0]);
		if(t>=0 && sum[i][1]-sum[t][1]==i-t)
			f[i][1]=1,p[i][1]=t;
		if(f[i][0]) ins(rt[0],0,m,k,i),jzm[0].insert(k);
		if(f[i][1]) ins(rt[1],0,m,k,i),jzm[1].insert(k);
		if(!f[i][0] && !f[i][1])
		{
			puts("No");
			return 0;
		}
	}
	puts("Yes");
	if(f[n][0]) print(n,0);
	else print(n,1);
	for(int i=1;i<=n;i++)
		printf("%d ",a[i]);
}

F.Strange Array

题目描述

点此看题

谜语人给了你一个长度为 (n) 个数组 (a),对于每一个 (i) 找到区间 ([l,r]) 满足 ([l,r]) 包含 (i) 并且把这个区间排序后中位数的位置 (-) 数字 (a_i) 排序后的位置 的最大值,中位数定义为排序后 (frac{l+r+1}{2}) 这个位置的数。

解法

服了,题目读成数字 (a_i) 的原始位置,根本就做不出来!

这个题好做,很容易感受出来对于固定的 (i) 肯定要最大化 (>a_i) 数和 (<a_i) 数的差值,因为这道题有 (=a_i) 的数所以还有一些小情况,如果 (i) 放在相同段的最左边那么我们让 (geq a_i) 的数尽量多即可,如果 (i) 放在相同段的最右边那么我们让 (leq a_i) 的数尽量多即可(当然这里的多指和另一方的差值),两者计算出的答案取最大即可。

然后把 (a_i) 排序,用线段树维护一下,注意询问和修改的先后关系,时间复杂度 (O(nlog n))

#include <cstdio>
#include <vector>
using namespace std;
const int M = 200005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,ans[M],s[4*M],mi[4*M],mx[4*M];vector<int> a[M];
void add(int i,int x)
{
	s[i]+=x;mi[i]+=x;mx[i]+=x; 
}
void down(int i)
{
	add(i<<1,s[i]);
	add(i<<1|1,s[i]);
	s[i]=0;
}
void upd(int i,int l,int r,int L,int R,int v)
{
	if(L>r || l>R) return ;
	if(L<=l && r<=R)
	{
		add(i,v);
		return ;
	}
	int mid=(l+r)>>1;down(i);
	upd(i<<1,l,mid,L,R,v);
	upd(i<<1|1,mid+1,r,L,R,v);
	mi[i]=min(mi[i<<1],mi[i<<1|1]);
	mx[i]=max(mx[i<<1],mx[i<<1|1]);
}
int findmi(int i,int l,int r,int L,int R)
{
	if(L>r || l>R) return 1e9;
	if(L<=l && r<=R) return mi[i];
	int mid=(l+r)>>1;down(i);
	return min(findmi(i<<1,l,mid,L,R),
	findmi(i<<1|1,mid+1,r,L,R));
}
int findmx(int i,int l,int r,int L,int R)
{
	if(L>r || l>R) return -1e9;
	if(L<=l && r<=R) return mx[i];
	int mid=(l+r)>>1;down(i);
	return max(findmx(i<<1,l,mid,L,R),
	findmx(i<<1|1,mid+1,r,L,R));
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		int x=read();
		a[x].push_back(i);
		upd(1,1,n,i,n,-1);
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<a[i].size();j++)
		{
			int p=a[i][j];
			int l=max(0,findmx(1,1,n,1,p-1));
			int r=findmi(1,1,n,p,n);
			ans[p]=max(ans[p],(l-r+2)/2-1);
		}
		for(int j=0;j<a[i].size();j++)
			upd(1,1,n,a[i][j],n,2);
		for(int j=0;j<a[i].size();j++)
		{
			int p=a[i][j];
			int l=min(0,findmi(1,1,n,1,p-1));
			int r=findmx(1,1,n,p,n);
			ans[p]=max(ans[p],(r-l)-(r-l+2)/2);
		}
	}
	for(int i=1;i<=n;i++)
		printf("%d ",ans[i]);
}
原文地址:https://www.cnblogs.com/C202044zxy/p/14916218.html