Codeforces Round #727 (Div. 2)
E. Game with Cards
题目描述
左右手初始都有卡牌 \(0\),一共 \(n\) 轮,每一轮把其中一张替换成 \(k\),左手记为 \(x\) 右手卡牌记为 \(y\),要满足 \(a_l\leq x\leq b_l,a_r\leq y\leq b_r\),问是否存在合法方案,若存在输出每一轮应该替换哪只手上的卡牌。
\(n\leq 100000,m\leq 10^9\),所有数不超过 \(m\)
解法
这道题后效性很高,所以不要往贪心方面想,就算是反悔贪心也不是很行。
那么考虑 \(dp\),但我们要记录每只手上的卡牌?其实没有这个必要,仔细想想我们的决策是每张牌放左手还是右手,那么 \(dp\) 只处理这个决策就行了,设 \(dp[i][0/1]\) 表示第 \(i\) 张牌放左手\(/\)放右手是否合法。
考虑转移,比如 \(dp[i][0]\) 的转移,实际上就是枚举前缀 \(i\) 的一段后缀使其全部都放 \(0\),这样就找到了递归子问题,对于左手 \((j,i]\) 这样放都应该合法,对于右手也要考虑 \(k_j\) 应该能让 \([j,i]\) 都合法。
对于右手的限制发现合法是单调的,那么我们可以维护所有当前所有合法的 \(j\),一旦发现 \(k_j\not\in[a_r,b_r]\) 就可以将其永久删除,对于合法的 \(j\) 我们找最右边的一个考虑转移即可,因为最右边的 \(j\) 对于左手的限制是最宽松的。
对于 \(dp[i][1]\) 的转移同理,我又写了 \(\tt 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(n\log 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]);
}