2022-7-7 & 8 #8 AGC049E & AGC049D
铁难成钢 无妨 我本就是这样
而真实的愿望 只能在 废纸篓埋葬
太摆了太摆了啥时候能调整过来啊。😥
024 AGC049E Increment Decrement
首先考虑对于一个数组 \(a\),怎么求出其代价。
(第一步做反了,想成通过操作二最小化结果的绝对值之和了,难受啊)
操作一是更容易刻画的,在有两种操作的情况下,应该把更难刻画的操作作为结果(作为结果可以放低对操作的要求),将更容易刻画的操作作为过程。(感觉说的很混乱啊)
于是我们需要确定一个序列 \(b\),最小化:
可以直接列出 dp,令 \(f_{i,j}\) 表示考虑前 \(i\) 个位置,\(b_i=j\) 的最小代价。
通过归纳证明 \(f_{i,j}\) 为凸,具体我们考虑 \(f_{i-1}\) 转移到 \(f_i\) 时的变化,其加上了一个 \(g_i(x)=|a_{i-1}-x|\),然后和 \(h(x)=C\max(x,0)\) 进行了一次 \((\min,+)\) 卷积。
采用 slope trick 维护 \(0\) 处点值 \(v\) 以及其差分的转折点集合 \(S\),一开始集合为 \(C\) 个 \(0\)。加上 \(g_i\) 会加入两个转折点 \(a_{i-1}\),而与 \(h\) 进行 \((\min,+)\) 卷积会将斜率为 \(-1\) 或 \(C+1\) 的直线掰下来,答案减去 \(S\) 中最小值,然后弹掉 \(S\) 中最小值、最大值各一个即可。
然后考虑计数,对于每个值计算其作为最小值被删掉的次数之和,改变一下就是大于等于某个值的数被删掉的次数之和。我们可以将 \(<x\) 的数视为 \(0\),\(\geqslant x\) 的数视为 \(1\)(因为我们只关心 \(x\)),令 \(f_{i,j}\) 表示前 \(i\) 次操作,\(j\) 个 \(1\) 的方案数,\(g_{i,j}\) 表示对应的删掉 \(1\) 总个数。
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
const int maxn=55,mod=1000000007;
int n,c,k,sum,ans,nums;
int a[maxn][maxn],b[maxn*maxn],f[maxn][maxn],g[maxn][maxn];
int calc(int d){
memset(f,0,sizeof(f)),memset(g,0,sizeof(g));
f[0][0]=1;
for(int i=1;i<=n;i++){
int t1=0;
for(int j=1;j<=k;j++)
t1+=a[i][j]>=d;
for(int j=0;j<=c;j++){
f[i][max(j-1,0)]=(f[i][max(j-1,0)]+1ll*f[i-1][j]*(k-t1))%mod;
f[i][min(j+1,c)]=(f[i][min(j+1,c)]+1ll*f[i-1][j]*t1)%mod;
g[i][max(j-1,0)]=(g[i][max(j-1,0)]+1ll*g[i-1][j]*(k-t1))%mod;
g[i][min(j+1,c)]=(g[i][min(j+1,c)]+1ll*g[i-1][j]*t1)%mod;
}
g[i][c]=(g[i][c]+1ll*f[i-1][c]*t1)%mod;
}
int res=0;
for(int i=0;i<=c;i++)
res=(res+g[n][i])%mod;
return res;
}
int main(){
scanf("%d%d%d",&n,&c,&k);
for(int i=1;i<=n;i++)
for(int j=1;j<=k;j++)
scanf("%d",&a[i][j]),sum=(sum+a[i][j])%mod,b[++nums]=a[i][j];
for(int i=1;i<n;i++)
sum=1ll*sum*k%mod;
sort(b+1,b+1+nums);
for(int i=1;i<=nums;i++)
ans=(ans+1ll*(b[i]-b[i-1])*calc(b[i]))%mod;
printf("%d\n",(sum-ans+mod)%mod);
return 0;
}
025 AGC049D Convex Sequence
明明没搞什么文化,思维还是很迟钝,真是碰了👻了。原来是看错题了那没事了。
显然中间有一段最小值,然后向左向右都是凸且递增的。
枚举一下最小值最靠左的位置(即差分数组为 \(0\) 的位置),发现其实就是左边能选一个前缀差分数组减一(最小值左边强制操作一次),右边能选一个后缀差分数组加一。由于是 \(+1,+2,\cdots,+k\) 形式,有效的操作只有根号种,做一个完全背包就好了。
每次都要提取所有 \(m-kn\) 位置的值,放一个 \(n\) 进完全背包即可。
复杂度 \(O(n\sqrt n)\)。
#include<stdio.h>
const int maxn=100005,mod=1000000007;
int n,m,ans;
int f[maxn];
void add(int x){
for(int i=x;i<=m;i++){
f[i]+=f[i-x];
if(f[i]>=mod)
f[i]-=mod;
}
}
void del(int x){
for(int i=m;i>=x;i--){
f[i]-=f[i-x];
if(f[i]<0)
f[i]+=mod;
}
}
int main(){
scanf("%d%d",&n,&m);
f[0]=1,add(n);
for(int i=1;i<n&&i*(i+1)/2<=m;i++)
add(i*(i+1)/2);
for(int i=1;i<=n;i++){
if(1ll*i*(i-1)/2<=m){
ans+=f[m-i*(i-1)/2];
if(ans>=mod)
ans-=mod;
}
if(1ll*(n-i)*(n-i+1)/2<=m)
del((n-i)*(n-i+1)/2);
if(1ll*i*(i+1)/2<=m)
add(i*(i+1)/2);
}
printf("%d\n",ans);
return 0;
}

浙公网安备 33010602011771号