P4890 Never·island(dp)
求门开的最小时间,其实也就是求门关的最大时间。
坐标这么大....显然坐标要离散化
离散化排序后,我们发现x轴被这些点划分成若干条线段$(l,r)$,并且有4种情况
我们用$v[i]$数组表示给队$i$钥匙的贡献
1.左端点为$i$队的起点,右端点为$i$队的终点:显然队$i$钥匙的贡献包括这一段,$v[i]+=r-l$
2.左端点为$i$队的终点,右端点为$j$队的起点:无论如何分配钥匙,这一段都可以关门,于是答案可直接算上$r-l$
3.左端点为$i$队的终点,右端点为$j$队的终点:如果我们给$j$队钥匙,$i$队就可以关门,$v[j]+=r-l$
4.左端点为$i$队的起点,右端点为$j$队的终点:我们只有给$i,j$各一把钥匙,才能算上这一段的贡献
于是我们就在$i,j$之间先连边,边权为$r-l$
蓝后我们又发现,这些点由一些互不相干的链组成。
于是我们按$x轴$从左到右dfs一遍求出dp的优先顺序
最后我们用$f[i][j][0/1]$表示前$i$个点,已给$j$个点钥匙,是否选择了第$i$个(当前)点的最优解
这就是一个二维dp辣
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<map> #define rint register int using namespace std; inline int Max(int a,int b){return a>b?a:b;} inline int Min(int a,int b){return a<b?a:b;} void read(int &x){ char c=getchar();x=0; while(c<'0'||c>'9') c=getchar(); while('0'<=c&&c<='9') x=x*10+(c^48),c=getchar(); } #define N 4005 int n,m,pos[N],tp,h[N],nxt[N],v[N],sv[N]; int f[N][N][2],tot; bool vis[N]; map <int,int> mp; void dfs(int x){vis[h[++tp]=x]=1; if(nxt[x])dfs(nxt[x]);} int main(){ read(n);read(m); int l,r,L,R,tn=n<<1; for(rint i=1;i<=n;++i){ read(l), read(r); pos[i]=l, pos[i+n]=r; mp[l]=i<<1, mp[r]=i<<1|1; }sort(pos+1,pos+tn+1);//离散化 for(rint i=1;i<tn;++i){ l=pos[i], r=pos[i+1]; L=mp[l], R=mp[r]; if((L&1)&&(R&1)) v[R>>1]+=r-l; if((L&1)&&!(R&1)) tot+=r-l; if(!(L&1)&&!(R&1)) v[L>>1]+=r-l; if(!(L&1)&&(R&1)){ if((L>>1)==(R>>1)) v[L>>1]+=r-l; else nxt[R>>1]=L>>1,sv[R>>1]=r-l; } } for(rint i=1;i<=tn;++i){ int p=mp[pos[i]]; if(!(p&1)&&!vis[p>>1]) dfs(p>>1); }//dfs确定dp顺序 memset(f,0xbf,sizeof(f));//-inf f[n+1][0][0]=0; for(rint i=n;i;--i){ f[i][0][0]=0; for(rint j=Min(n-i+1,m);j;--j){ f[i][j][0]=Max(f[i+1][j][0],f[i+1][j][1]); f[i][j][1]=Max(f[i+1][j-1][0],f[i+1][j-1][1]+sv[h[i]])+v[h[i]]; } }printf("%d",pos[tn]-pos[1]-tot-Max(f[1][m][1],f[1][m][0])); return 0; }