题解:P15093 [UOI 2025 II Stage] Odd Rows
竞选全场第一篇 DP 题解(不会只有我一个人想的 DP 吧)。
首先啊,我们看到这个
然后我们来思考怎么解决这道题啊。首先第一眼是构造,不过作者太菜了,没瞪出来构造方法。所以选择了 DP。
现在我们不考虑怎么构造这个东西,而是思考最多有多少行这个问题。你很容易发现我们可以直接 DP:设 vector 就行。
然后转移就很显然了:我们枚举一个 1 放在偶数行上(也就是有 1 放在奇数行上(也就是有
最后枚举一下最少有多少个偶数行时能够成立,那么剩下的就全是奇数行。
这里重点说一下这个 1 的偶数行个数必然不会超过原本有的偶数行个数;其次是 1;然后是 1 的奇数行的个数不会超过原本有的奇数行个数;最后是
我猜你没看到下限中间的那一个限制完全没用。
这个东西因为包含
现在我们来考虑怎么构造这个矩阵。我们考虑我们从哪个状态转移到的
那么我们构造的时候只需要 dfs 一下,从 1 被放在了偶数行上,剩下的在奇数行上(你把 set 一通处理就行。
构造代码:
void print(int x,int y)
{
if(x==0)
{
return;
}
print(x-1,g[x][y]);
set<int>ss0,ss1;
int num=(a[x]+g[x][y]-y)/2;
int cnt=num;
for(auto i:s0)//原本有的偶数行
{
if(!cnt)
{
break;
}
mp[i][x]=1;
ss0.insert(i);
cnt--;
}
cnt=a[x]-num;
for(auto i:s1)//原本有的奇数行
{
if(!cnt)
{
break;
}
mp[i][x]=1;
ss1.insert(i);
cnt--;
}
for(auto i:ss0)
{
s0.erase(i);
s1.insert(i);
}
for(auto i:ss1)
{
s1.erase(i);
s0.insert(i);
}
}
放一下暴力代码:
f[0][n]=1;
for(int j=1;j<=m;j++)
{
for(int i=0;i<=n;i++)
{
if(f[j-1][i])
{
int l=max({0ll,(i+a[j]-n+1)/2,a[j]-n+i}),r=min(i,a[j]);
for(int k=l;k<=r;k++)
{
f[j][i-k+a[j]-k]=1;
g[j][i-k+a[j]-k]=i;
}
}
}
}
现在我们考虑如何优化这个转移:我们会发现主要问题就在 k 的枚举上,那有没有什么办法把 k 的枚举变成
仔细看转移方程:我们实际上就是把
好吧这个差分有点诡异:因为我们要分奇偶性来考虑,那么在构造差分时,我们只需要在 multiset。
当然因为奇偶性的问题,我们要设两个 multiset(虽然差分只需要一个就行)。然后维护一下就行。
放一下主要代码:
f[0][n]=1;
for(int j=1;j<=m;j++)
{
for(int i=0;i<=n;i++)
{
v[i].clear();
}
for(int i=0;i<=n;i++)
{
if(f[j-1][i])
{
int l=max({0ll,(i+a[j]-n+1)/2,a[j]-n+i}),r=min(i,a[j]);
v[i+a[j]-2*r].push_back(make_pair(i,1));
v[i+a[j]-2*l+2].push_back(make_pair(i,-1));
}
}
multiset<int>ms0,ms1;
for(int i=0;i<=n;i++)
{
if(i&1)
{
for(auto k:v[i])
{
if(k.second==1)
{
ms1.insert(k.first);
}
else
{
ms1.erase(ms1.find(k.first));
}
}
}
else
{
for(auto k:v[i])
{
if(k.second==1)
{
ms0.insert(k.first);
}
else
{
ms0.erase(ms0.find(k.first));
}
}
}
if(i&1)
{
if(ms1.empty())
{
f[j][i]=0;
g[j][i]=0;
}
else
{
f[j][i]=1;
g[j][i]=(*ms1.begin());
}
}
else
{
if(ms0.empty())
{
f[j][i]=0;
g[j][i]=0;
}
else
{
f[j][i]=1;
g[j][i]=(*ms0.begin());
}
}
}
}
完结撒花!