日记

日记

周日 12月 08 2024
1226 字 · 8 分钟

上午

切了一题

CF2027D1 The Endspeaker (Easy Version)

还是那句话:既然出现在dp专题里,那肯定就是dp咯
我们设 dpk,i,jdp_{k,i,j} 表示在 ak=bi,ja_k = b_{i,j} 时能否获胜。我们发现,正着推好像不大行,那么,正难则反
我们考虑从 l1,(i,j)(1,1)l \rightarrow 1,(i,j) \rightarrow (1,1) ,反着推。那么有明显的转移:dpk,i,j=[(x=i+1ny=j+1mdpk+1,x,y)=0]dp_{k,i,j} = \left[\left(\sum\limits_{x = i + 1}^{n}\sum\limits_{y = j + 1}^{m}dp_{k + 1,x,y}\right) = 0\right] 。但是,枚举状态就有 O(knm)\mathcal{O}(knm) 的复杂度了,转移还有 O(nm)\mathcal{O}(nm) 。合起来就是 O(kn2m2)\mathcal{O}(kn^2m^2) 。这不给你T飞起来。
我们考虑怎么 O(1)\mathcal{O}(1) 转移。我们发现,(x=i+1ny=j+1mdpk+1,x,y)\left(\sum\limits_{x = i + 1}^{n}\sum\limits_{y = j + 1}^{m}dp_{k + 1,x,y}\right) 可以用二维后缀和维护。那么最外面的循环枚举 kk (本该如此),然后里面是 O(nm)\mathcal{O}(nm) 的转移,等它转移完了,我们再 O(nm)\mathcal{O}(nm) 维护一下二维后缀和,这样就达到的 O(1)\mathcal{O}(1) 转移的效果,总复杂度 O(knm)\mathcal{O}(knm) ,就可以了。

中午 & 下午

打了一下午游戏,在打游戏的时候尝试切一道线段树,但是失败了。

晚上

今天晚上又有 arc 又有 codeforces 的,本来都不想来,但是还是来学校打了。顺便把之前没写完的日记补了。

CF2027D The Endspeaker solution

我们先来看 easy version 。这一看就很有dp的感觉啊,于是我们就设 dpi,jdp_{i,j} 表示当 k=ik = i ,剩的第一个元素下标为 jj 的时候的最小代价。初始化时 dp1,1=0dp_{1,1} = 0 。然后我们就可以考虑如何转移:

  • 第一种操作时,我们从 jj 开始找到第一个位置 cc 使得 x=jc>bi\sum\limits_{x = j}^{c} > b_i ,然后就可以转移:dpi,c=min(dpi,c,dpi,j+mi)dp_{i,c} = \min(dp_{i,c},dp_{i,j} + m - i)
  • 第二种操作时,有显然的转移:dpi+1,j=min(dpi+1,j,dpi,j)dp_{i + 1,j} = \min(dp_{i + 1,j},dp_{i,j})

然后怎么统计答案呢?我们想:要把 aa 数组去完,那么答案肯定是某个 dpx,n+1dp_{x,n + 1}xx 我们还不知道),然后,我们又发现,其实这个 xx 是多少无关紧要,那么我们就有答案:ans=mini=1mdpi,n+1ans = \min\limits_{i = 1}^{m}dp_{i,n + 1}

这是 easy version 啊,现在我们来看 easy versionhard versionyzp 名言)
我们看 hard versionhard 在哪里。我们看:它不仅让我们求最小的代价是多少,他还让我们求有多少种方案能够达到最小的代价。我们发现,对于 p(j+1,c]\forall p \in (j + 1,c] ,都可以从 dpi,jdp_{i,j} 转移:dpi,p=min(dpi,p,dpi,j+mi)dp_{i,p} = \min(dp_{i,p},dp_{i,j} + m - i) 。在简单版中不这么做是因为答案不会更优还浪费时间复杂度,而现在要统计方案数了,那么就要考虑一下了。而我们看要更新 (j+1,c](j + 1,c] 这个区间,那么我们就想到线段树。
因为实现实在是太美所以就放一下代码:

#include<bits/extc++.h>
#define int long long
#define inf 0x7f7f7f7f7f7f7f7f
#define pii pair<int,int>
#define mkp make_pair
using namespace std;
const int maxn = 3e5 + 5;
const int mod = 1e9 + 7;
void operator+=(pii &x,pii &y)//这里就类似统计答案
{
if(x.first != y.first)//如果方案更优就更新方案
{
if(x.first > y.first)
x = y;
}//这里一定一定要打大括号
else//如果方案相同就更新方案数
x.second = (x.second + y.second) % mod;
}
int n,m;
int a[maxn],b[maxn];
int pre[maxn];
struct Rukkhadevata//又好用码量又大的树:大慈树
{
int l,r;
pii val;
}tree[maxn << 2];
void push_down(int rt)
{
if (tree[rt].val.first == inf)
return;
tree[rt << 1].val += tree[rt].val;
tree[rt << 1 | 1].val += tree[rt].val;
tree[rt].val = mkp(inf,0);
}//lazy下放
//为何直接拿val当lazy:因为不用维护区间查询,所以对于非叶子节点的val没有意义,就拿来当lazy
void build(int l,int r,int rt)
{
tree[rt].l = l;
tree[rt].r = r;
tree[rt].val = mkp(inf,0);
if (l == r)
{
if (l == 1)
tree[rt].val = mkp(0,1);
return;
}
int mid = l + r >> 1;
build(l,mid,rt << 1);
build(mid + 1,r,rt << 1 | 1);
}
void upd(int ql,int qr,pii x,int rt)
{
int l = tree[rt].l;
int r = tree[rt].r;
if (ql <= l && r <= qr)
{
tree[rt].val += x;
return;
}
push_down(rt);
int mid = l + r >> 1;
if (ql <= mid)
upd(ql,qr,x,rt << 1);
if (qr > mid)
upd(ql,qr,x,rt << 1 | 1);
}
pii que(int x,int rt)
{
int l = tree[rt].l;
int r = tree[rt].r;
if (l == r)
return tree[rt].val;
push_down(rt);
int mid = l + r >> 1;
if (x <= mid)
return que(x,rt << 1);
else
return que(x,rt << 1 | 1);
}
void solve()
{
scanf("%lld%lld",&n,&m);
for (int i = 1; i <= n; i++)
{
scanf("%lld",a + i);
pre[i] = pre[i - 1] + a[i];
}
for (int j = 1; j <= m; j++)
scanf("%lld",b + j);
build(1,n,1);
pii ans = mkp(inf,0);
for (int j = 1; j <= m; j++)
{
pii tmp1 = mkp(inf,0);
for (int i = 1; i <= n; i++)
{
int pos = upper_bound(pre + i,pre + n + 1,b[j] + pre[i - 1]) - pre;//找到c
if (i < pos)
{
pii tmp2 = que(i,1);
tmp2.first += m - j;
if (pos == n + 1)
{
tmp1 += tmp2;
pos --;
}
if (i < pos)
upd(i + 1,pos,tmp2,1);//转移
}
}
ans += tmp1;//记录答案
}
if (ans.first != inf)
printf("%lld %lld\n",ans.first,ans.second);
else
puts("-1");
}
signed main()
{
int t;
scanf("%lld",&t);
while (t--)
solve();
return 0;
}

Thanks for reading!

日记

周日 12月 08 2024
1226 字 · 8 分钟