P5574 [CmdOI2019] 任务分配问题
<h3 id="题目描述">题目描述</h3><p>经典的分 <span class="math inline">\(k\)</span> 段问题,要求求出分 <span class="math inline">\(k\)</span> 段后使每段顺序对数量之和最小,求这个最小的值。</p>
<h3 id="思路">思路</h3>
<p>首先,我们很好得出这种分段问题的状态转移方程即 $$dp_{i,j}=\min{dp_{k,j-1}+w(k+1,i)}$$ 其中 <span class="math inline">\(dp_{i,j}\)</span> 表示选到前 <span class="math inline">\(i\)</span> 个数,分了 <span class="math inline">\(j\)</span> 段的最小费用,我们可以用 <span class="math inline">\(O(n^2k)\)</span> 的时间复杂度来实现,显然超时,得分<strong>20pts</strong>。</p>
<p>接着考虑优化,不难发现,<span class="math inline">\(w(i+1,j)+w(i,j+1) \ge w(i,j)+w(i+1,j+1)\)</span>,所以,该式子满足四边形不等式,即可使用决策单调性来优化该状态转移方程。</p>
<p>P4767邮局这道题,与该题状态转移方程相同,所以一上手想到使用四边形不等式中 <span class="math inline">\(opt(i,j-1) \ge opt(i,j) \ge opt(i+1,j)\)</span> 的性质优化该题优化该题,然而,这种算法时间复杂度为 <span class="math inline">\(O(n(n+m))\)</span>,并不能通过该题,结果超时,得分<strong>40pts</strong>,邮局一题中 <span class="math inline">\(n\)</span> 与 <span class="math inline">\(m\)</span> 上界相同,而该题 <span class="math inline">\(m\)</span> 远小于 <span class="math inline">\(n\)</span> 我们需要使用分治优化将时间复杂度降到 <span class="math inline">\(O(k \times n \log n)\)</span> 级别才能通过。</p>
<p>我们解决了DP阶段的问题,接着需要解决的就是预处理 <span class="math inline">\(w\)</span> 数组的问题了,由于我们无法接受 <span class="math inline">\(O(n^2)\)</span> 的时间复杂度,所以我们要对其进行优化,因为数组大小的限制,预处理出 <span class="math inline">\(w\)</span> 数组这条思路已经行不通了,我们考虑在DP过程中求一段的花费,注意到,在分治求解的过程中,当决策点每向右移动一位,我们的费用由 <span class="math inline">\(w(i,j)\)</span> 变为 <span class="math inline">\(w(i,j+1)\)</span> 过程中,我们增加的顺序对费用即为 <span class="math inline">\(i\)</span> 到 <span class="math inline">\(j\)</span> 中小于 <span class="math inline">\(a_{j+1}\)</span> 的数的个数,而这个我们很容易实现 <span class="math inline">\(log\)</span> 级别的时间复杂度,所以,记录 <span class="math inline">\(tl\)</span> 和 <span class="math inline">\(tr\)</span> 分别表示上个状态转以后已经处理完的左右端点,如果两状态位于分治中同一区间,则每次转移需要 <span class="math inline">\(O(\log n)\)</span> 的时间复杂度,如果改变了区间,需要先跳到所求区间,设两区间分别为 <span class="math inline">\(\)</span> 和 <span class="math inline">\(\)</span> 所需要跳的步数即为 <span class="math inline">\(R_j-L_i\)</span>,放在分治中即可粗略计算为每层跳 <span class="math inline">\(n\)</span> 次,每次跳跃转移点同样需要 <span class="math inline">\(O(\log n)\)</span>,综上所述,时间复杂度为 <span class="math inline">\(O(k \times n \log^2 n)\)</span> 可以通过该题。</p>
<p>求顺序对时若使用线段树实现,时间超限,得分<strong>60pts</strong>,使用常数更小的树状数组实现,成功通过该题。</p>
<p>另外,观察状态转移方程,每次转移只和 <span class="math inline">\(j-1\)</span> 有关,所以我们可以压缩掉一维,只记录当前与上一行状态。</p>
<h3 id="代码">代码</h3>
<pre><code>#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int M=25010;
int n,m;
int a,w;
int dp,tl=1,tr=0,sum=0;
int lowbit(int x)
{
return x&-x;
}
void add(int x,int w)
{
for(;x<=n;x+=lowbit(x))
a+=w;
}
int query(int x)
{
int ans=0;
for(;x;x-=lowbit(x))
ans+=a;
return ans;
}
void ask(int le,int re)
{
while(tr<re)
{
++tr;
sum+=query(w);
add(w,1);
}
while(tl>le)
{
--tl;
sum+=tr-tl-query(w);
add(w,1);
}
while(tr>re)
{
add(w,-1);
sum-=query(w);
tr--;
}
while(tl<le)
{
add(w,-1);
sum-=tr-tl-query(w);
tl++;
}
}
void solve(int le,int re,int lt,int rt)
{
int mid=(le+re)>>1;
int k=mid;
for(int i=lt;i<=min(rt,mid-1);i++)
{
ask(i+1,mid);
if(dp+sum<=dp)
{
dp=dp+sum;
k=i;
}
}
if(mid-1>=le) solve(le,mid-1,lt,k);
if(mid+1<=re) solve(mid+1,re,k,rt);
}
int main()
{
memset(dp,0x3f,sizeof(dp));
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&w);
}
dp=dp=0;
for(int i=1;i<=m;i++)
{
solve(1,n,0,n-1);
for(int j=1;j<=n;j++)
{
dp=dp;
}
}
printf("%d\n",dp);
return 0;
}
</code></pre><br><br>
来源:https://www.cnblogs.com/Naoxiaoyu/p/19049730
頁:
[1]