队内训练3 Codeforces1557D 线段树存pair优化dp
题目链接link.
题意:给你一个n行的01矩阵,m个连续的11…1序列。要求你去删一些行,让最后得到的新的矩阵,每两行之间都有相同的位置有1。问你最少删除多少行以及删除哪些行。多种方案则输出任意一种。
首先,这个题必然不是贪心,直观的感受就是dp。可以很容易想到 O ( N 2 ) O(N^2) O(N2) 的dp做法。 d p [ i ] dp[i] dp[i] 代表前 i i i 行最多留多少行可以保证题目要求。转移方程为 d p [ i ] = m a x ( d p [ i ] , d p [ j ] + 1 ) dp[i]=max(dp[i],dp[j]+1) dp[i]=max(dp[i],dp[j]+1)。其中 j j j 是 1 1 1到 i − 1 i-1 i−1中与 i i i 相邻符合条件的。但是n和m范围都是1e5, O ( n 2 ) O(n^2) O(n2)肯定 tle的头都飞了。考虑用数据结构来维护一下这个转移。
可以考虑以权值建线段树。tree[i]表示当前第i个位置上有1的能保留的行数的最大值。那么我们只需要找每一行有1的tree[i]的最大值更新就好。
但是还有一个比较棘手的问题就是,需要记录dp的路径。也就是说要找到我们取得的最大值的行号。解决方案就是用pair去存行号。操作和普通的线段树是一样的,更新也是更新max,但是行号跟着pair也被记录下来了,这样就可以解决输出删除方案的问题。
每天一个线段树魔改小技巧
AC代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;
int n,m;
map<int,int> ok;
int lsh[600010];
struct oxy
{
vector<pair<int,int>> haha;
}a[300010];
int dp[300010];
pair<int,int> max(pair<int,int>x,pair<int,int>y)
{
if(x.first>y.first) return x;
return y;
}
struct QwQ
{
int l,r;
pair<int,int> value;
pair<int,int> lazy_tag={0,0};
}tree[4*600010];
void build(int x,int l,int r)
{
tree[x].l=l;
tree[x].r=r;
tree[x].lazy_tag={0,0};
if(l==r)
{
tree[x].value={0,0};
return ;
}
int mid=(l+r)/2;
build(x*2,l,mid);
build(x*2+1,mid+1,r);
tree[x].value=max(tree[x*2].value,tree[x*2+1].value);
}
void update(int x,pair<int,int> w)
{
tree[x].value= max(tree[x].value,w);
tree[x].lazy_tag=w;
return ;
}
void push_down(int x)
{
update(x*2,tree[x].lazy_tag);
update(x*2+1,tree[x].lazy_tag);
tree[x].lazy_tag={0,0};
}
void push_up(int x)
{
tree[x].value=max(tree[x*2].value ,tree[x*2+1].value);
}
void change(int x,int l,int r,pair<int,int> w)
{
if(tree[x].l==l&&tree[x].r==r)
{
update(x,w);
return ;
}
if(tree[x].lazy_tag.second!=0) push_down(x);
int mid=(tree[x].l+tree[x].r)/2;
if(r<=mid) change(x*2,l,r,w);
else if(l>=mid+1) change(x*2+1,l,r,w);
else
{
change(x*2,l,mid,w);
change(x*2+1,mid+1,r,w);
}
push_up(x);
}
pair<int,int> query(int x,int l,int r)
{
if(tree[x].l==l&&tree[x].r==r)
{
return tree[x].value;
}
if(tree[x].lazy_tag.second!=0) push_down(x);
int mid=(tree[x].l+tree[x].r)/2;
if(r<=mid)
{
return query(x*2,l,r);
}
else if(l>=mid+1)
{
return query(x*2+1,l,r);
}
else
{
return max(query(x*2,l,mid),query(x*2+1,mid+1,r));
}
}
int pre[300010];
int miao[300010];
int main( )
{
scanf("%d%d",&n,&m);
int sum=0;
int maxx=0;
for(int i=1;i<=m;i++)
{
int k,l,r;
scanf("%d%d%d",&k,&l,&r);
if(ok[l]==0)
{
lsh[++sum]=l;
ok[l]++;
}
if(ok[r]==0)
{
lsh[++sum]=r;
ok[r]++;
}
a[k].haha.push_back({l,r});
if(k>=maxx) maxx=k;
}
sort(lsh+1,lsh+sum+1);
for(int i=1;i<=sum;i++)
ok[lsh[i]]=i;
int ans=0;
int anss=0;
build(1,1,sum);
int maxxx=0;
for(int i=1;i<=maxx;i++)
{
maxxx=0;
for(auto j:a[i].haha)
{
int l=j.first;
int r=j.second;
l=ok[l];
r=ok[r];
int xixi=query(1,l,r).first+1;
int orz=query(1,l,r).second;
//cout<<"xixi="<<xixi<<" orz="<<orz<<endl;
if(xixi>=maxxx) maxxx=xixi,pre[i]=orz;
}
for(auto j:a[i].haha)
{
int l=j.first;
int r=j.second;
l=ok[l];
r=ok[r];
change(1,l,r,{maxxx,i});
}
//cout<<"pre["<<i<<"]="<<pre[i]<<endl;
if(maxxx>ans) ans=maxxx,anss=i;
}
printf("%d\n",n-ans);
if(n-ans==0) return 0;
miao[anss]++;
while(1)
{
anss=pre[anss];
miao[anss]++;
if(anss<=0) break;
//cout<<"anss="<<anss<<endl;
}
int okkk=0;
for(int i=1;i<=n;i++)
{
if(miao[i]==0)
{
if(okkk!=0) printf(" ");
okkk++,printf("%d",i);
}
}
return 0;
}