没看数据之前,第一想法是dp,复杂度O(mn)。看了数据后意识到这种想法是不行的。
能到达一个点,就到能到达他的左边或到达上面。进一步逐行考虑,其实能否到达这一行某个区域,就是要判断能否从上面到达这个区域。
下面是我的思维过程:
这种思想还是很重要的,一行一行的看,从无序变有序
类似的思想在P1985 [USACO07OPEN]翻转棋 Fliptile S中也有体现。
标记的操作要用线段树完成,这边要开两个线段树,分别表示当前要处理的行和前一行。
query操作有点特殊,表示被查询区间最靠左的一个不为0的端点。我也认为这题比较难的地方就是写这个query。
这是我最开始写的版本,时间复杂度太高,高到O(n),对于大一点的数据就t飞了。
ll query(int num, int L, int R, int l, int r, int rt) //t飞了
{
if (!tree[num][rt].sum) return INF;
if (l==r) return l;
int mid=(l+r)/2;
pushdown(num,rt,mid-l+1,r-mid);
ll temp1=INF, temp2=INF;
if (L<=mid) temp1=query(num,L,R,l,mid,rt<<1);
if (R>mid) temp2=query(num,L,R,mid+1,r,rt<<1|1);
return min(temp1,temp2);
}
然后我对这个query进行了一些剪枝
int query(int num, int L, int R, int l, int r, int rt)
{
if (!tree[num][rt].sum) return INF;
if (l==r) return l;
int mid=(l+r)/2;
pushdown(num,rt,mid-l+1,r-mid);
if (L<=l && R>=r)
{
if (tree[num][rt<<1].sum) return query(num,L,R,l,mid,rt<<1);
else return query(num,L,R,mid+1,r,rt<<1|1);
}
else
{
ll temp1=INF, temp2=INF;
if (L<=mid) temp1=query(num,L,R,l,mid,rt<<1);
if (R>mid) temp2=query(num,L,R,mid+1,r,rt<<1|1);
return min(temp1,temp2);
}
}
完整代码:
#include<bits/stdc++.h>
#define FAST ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define INF 0x3f3f3f3f
typedef long long ll;
const int maxn = 2e6+5;
using namespace std;
int kase;
int n,m,k;
vector<int> node[maxn];
struct seg_tree
{
int l,r;
ll sum,update,change;
}tree[3][maxn];
void pushup(int num, int rt)
{
tree[num][rt].sum=tree[num][rt<<1].sum+tree[num][rt<<1|1].sum;
}
void pushdown(int num, int rt, int lson, int rson)
{
if (tree[num][rt].update)
{
tree[num][rt<<1].sum=tree[num][rt].change*lson;
tree[num][rt<<1|1].sum=tree[num][rt].change*rson;
tree[num][rt<<1].change=tree[num][rt].change;
tree[num][rt<<1|1].change=tree[num][rt].change;
tree[num][rt<<1].update=tree[num][rt<<1|1].update=1;
tree[num][rt].update=tree[num][rt].change=0;
}
}
void build(int num, int l, int r, int rt)
{
if (l>r) return;
tree[num][rt].l=l, tree[num][rt].r=r;
if (l==r) return;
int mid=(l+r)/2;
build(num,l,mid,rt<<1);
build(num,mid+1,r,rt<<1|1);
return;
}
void update(int num, int L, int R, int val, int l, int r, int rt)
{
if (L<=l && R>=r)
{
tree[num][rt].sum=val*(r-l+1);
tree[num][rt].change=val;
tree[num][rt].update=1;
return;
}
int mid=(l+r)/2;
pushdown(num,rt,mid-l+1,r-mid);
if (L<=mid) update(num,L,R,val,l,mid,rt<<1);
if (R>mid) update(num,L,R,val,mid+1,r,rt<<1|1);
pushup(num,rt);
}
int query(int num, int L, int R, int l, int r, int rt)
{
if (!tree[num][rt].sum) return INF;
if (l==r) return l;
int mid=(l+r)/2;
pushdown(num,rt,mid-l+1,r-mid);
if (L<=l && R>=r)
{
if (tree[num][rt<<1].sum) return query(num,L,R,l,mid,rt<<1);
else return query(num,L,R,mid+1,r,rt<<1|1);
}
else
{
ll temp1=INF, temp2=INF;
if (L<=mid) temp1=query(num,L,R,l,mid,rt<<1);
if (R>mid) temp2=query(num,L,R,mid+1,r,rt<<1|1);
return min(temp1,temp2);
}
}
int main()
{
FAST;
cin>>kase;
while(kase--)
{
memset(tree,0,sizeof(tree));
cin>>n>>m>>k;
for (int i=1; i<=n; i++) node[i].clear();
for (int i=1; i<=k; i++)
{
int x,y;
cin>>x>>y;
node[x].push_back(y);
}
ll ans=0;
build(0,1,m,1);
build(1,1,m,1);
update(0,1,1,1,1,m,1);
for (int i=1; i<=n; i++)
{
node[i].push_back(m+1);
sort(node[i].begin(),node[i].end());
int l=0;
for (auto &p:node[i])
{
if (p-1>=l+1)
{
int pos=query((i&1)^1,l+1,p-1,1,m,1);
if (pos!=INF)
{
update(i&1,pos,p-1,1,1,m,1);
ans+=p-pos;
}
}
l=p;
}
update((i&1)^1,1,m,0,1,m,1);
}
cout<<ans<<endl;
}
return 0;
}