这题做了好久,性质很早就推出来了,一直不敢打…感觉总有什么地方不对劲…
事实上并没有…只是我线段树都写挂…
有n条向上的街道,每条街道上有些向左或向右的,通向相邻的街道的边,让你添加至多k条边,问最多有多少条街道能到达所有街道
首先,注意到我们从某条路出发,只能向上、左、右走,不能向下
然后每条边只通向相邻的街道,不会跨越
那么可以转化一下,把所有街道分成当前街道左边的,右边的,因为不能跨越,也不能向下,所以我们走到左边的街道再走到右边的街道这种操作是不存在的(因为不会比直接向右走更优
同时可以发现,只要向左能走到第1条街道=能到达左边的所有街道,右边同理
所以相当于问,添加K条边后,最多有多少条街道既能到1,又能到n
接着还有个很显然的性质:若i能到1,则i+1可能可以到1,但是若i不能到1,i+1就一定不能到1。 向右同理
所以若我们将添加进去的K条边分成向左的和向右的
那么对于向左的边,我们一定是先让2能到1,再让3能到1…..向右的同理
就是说,如果我们能算出分配i条边向左,最多能使1~f[i]都能到1,向右也这样算g[i],我们就能够通过枚举寻找最大值
接下来我们考虑怎么计算f[i]
设dp[i][j]表示第i条街道,在j的位置出发,最少要添加多少条边才能到1
直接转移肯定不行,考虑优化转移
从j出发,要么直接在j添加一条i->i-1的边,要么向上走到一条i->i-1的边
对于j往上走遇到的第一条街道,假设他在k的位置
dp[i][j]=min(dp[i-1][j]+1,dp[i-1][k])
我们发现,对于两条边之间的j,他们取min的对象是一样的,都是dp[i-1][k]
不妨用线段树做这件事,每个下标j维护当前的dp[i][j],对于两条边之间的部分,先区间+1,再对dp[i-1][k]取min
处理完当前的dp[i]后,判一下是否能用dp[i][0]更新f数组
于是我们就得到了f[i],g[i]同理
然后..就没有然后了,其实代码没多长
emmmmmmmmm我似乎讲的不够简洁
不管了
乐滋滋好强orz,把边反向转化成1,n到这些街道,然后最长不升子秒啊
code:
#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
#define inf 1e9
using namespace std;
inline void down(int &x,const int &y){if(x>y)x=y;}
const int maxn = 210000;
int n,m,p,K;
int Li[maxn],Ri[maxn];
struct segment
{
int flag,mn;
segment(){mn=-1;}
}seg[maxn<<2];
void pushdown(const int x)
{
int lc=x<<1,rc=lc|1;
if(seg[x].flag)
{
int fl=seg[x].flag; seg[x].flag=0;
if(seg[lc].mn!=-1) seg[lc].mn+=fl;
seg[lc].flag+=fl;
if(seg[rc].mn!=-1) seg[rc].mn+=fl;
seg[rc].flag+=fl;
}
if(seg[x].mn!=-1)
{
int mn=seg[x].mn; seg[x].mn=-1;
if(seg[lc].mn!=-1) down(seg[lc].mn,mn);
else seg[lc].mn=mn;
if(seg[rc].mn!=-1) down(seg[rc].mn,mn);
else seg[rc].mn=mn;
}
}
int lx,rx;
void add(const int x,const int l,const int r)
{
if(rx<l||r<lx) return;
if(lx<=l&&r<=rx)
{
seg[x].flag++;
if(seg[x].mn!=-1) seg[x].mn++;
return;
}
pushdown(x);
int mid=l+r>>1,lc=x<<1,rc=lc|1;
add(lc,l,mid); add(rc,mid+1,r);
}
int Mn;
void upd(const int x,const int l,const int r)
{
if(rx<l||r<lx) return;
if(lx<=l&&r<=rx)
{
if(seg[x].mn==-1) seg[x].mn=Mn;
else down(seg[x].mn,Mn);
return;
}
pushdown(x);
int mid=l+r>>1,lc=x<<1,rc=lc|1;
upd(lc,l,mid); upd(rc,mid+1,r);
}
int loc;
int query(const int x,const int l,const int r)
{
if(l==r) return min(seg[x].flag,seg[x].mn);
pushdown(x);
int mid=l+r>>1,lc=x<<1,rc=lc|1;
if(loc<=mid) return query(lc,l,mid);
else return query(rc,mid+1,r);
}
vector<int>V0[maxn],V1[maxn];
int main()
{
scanf("%d%d%d%d",&n,&m,&p,&K);
for(int i=1;i<=p;i++)
{
int x,y,d; scanf("%d%d%d",&x,&y,&d);
if(d) V0[x+1].push_back(y);
else V1[x].push_back(y);
}
for(int i=1;i<=n;i++)
sort(V0[i].begin(),V0[i].end()),
sort(V1[i].begin(),V1[i].end());
int nowk=0; seg[1].mn=0;
for(int i=2;i<=n;i++)
{
int las=-1;
for(int j=0;j<V0[i].size();j++)
{
int nj=V0[i][j];
if(las+1<nj)
{
lx=las+1,rx=nj-1; add(1,0,m);
loc=nj; Mn=query(1,0,m);
upd(1,0,m);
}
las=nj;
}
if(las+1<=m) lx=las+1,rx=m,add(1,0,m);
loc=0; int nk=query(1,0,m);
if(nk!=nowk)
while(nowk<nk) Li[nowk++]=i-1;
}
while(nowk<K) Li[nowk++]=n;
nowk=0; seg[1].flag=0; seg[1].mn=0;
for(int i=n-1;i>=0;i--)
{
int las=-1;
for(int j=0;j<V1[i].size();j++)
{
int nj=V1[i][j];
if(las+1<nj)
{
lx=las+1,rx=nj-1; add(1,0,m);
loc=nj; Mn=query(1,0,m);
upd(1,0,m);
}
las=nj;
}
if(las+1<=m) lx=las+1,rx=m,add(1,0,m);
loc=0; int nk=query(1,0,m);
if(nk!=nowk)
while(nowk<nk) Ri[nowk++]=i+1;
}
while(nowk<K) Ri[nowk++]=1;
int ans=0;
for(int i=0;i<=K;i++) if(Li[i]>=Ri[K-i])
ans=max(ans,Li[i]-Ri[K-i]+1);
int st=Li[0]>=Ri[0]?Li[0]-Ri[0]+1:0;
printf("%d\n",ans-st);
return 0;
}