先把晚上打的 CF 题目写上:
考虑看题猜答案,显然是 (abs(a-b).)
考虑移动的步数,令 (t=abs(a-b),min step=left{amod t,t-amod t ight}.)
考虑猜结论,首先一个结论显然是要将车辆平均分,所以先将总人数算出来 (mod n)
观察样例发现,答案恰好是余数乘以 (n-rest.)
于是结论就对了。
看到 (v) 的范围发现选的次数肯定很少,考虑爆搜。
注意 double
的精度问题,要设置 eps=1e-10
来确保不会出现精度损失。
还有,这是求期望,求出概率记得乘以步数。
考虑从 (i) 开始到 (n-1) 每次询问 i xor (i-1)
这样每次密码的影响都会被异或运算消除。
所以一直询问即可。但是会发现如果密码是 (0) 那么这 (n-1) 次询问都不会问到,这时的密码应该等于 (sum_{xor} i.)
于是维护异或和即可。
DP:
考虑递归建树,至于只有一个孩子的节点默认左边就行。设 (f[i][0/1/2]) 表示三种颜色分贝对应的最少 (2) 颜色出现次数(默认绿色是 (2) 了。)
同时令 (g) 表示最大颜色个数。细节上注意取 (min) 的时候不要让 (0) 的 (f) 值参与。
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+10;
const int dyx=(1<<30);
int ls[MAXN],rs[MAXN],node,rt;
int f[MAXN][3],g[MAXN][3];
inline int Min(int x,int y){return x<y?x:y;}
inline int Max(int x,int y){return x>y?x:y;}
void Read(int &x){
x=++node;
char c;
cin>>c;
if(c=='0')return;
if(c=='1'){
Read(ls[x]);
return;
}
Read(ls[x]);
Read(rs[x]);
return;
}
void dfs(int x){
if(ls[x])dfs(ls[x]);
if(rs[x])dfs(rs[x]);
// printf("%d:ls:%d rs:%d
",x,ls[x],rs[x]);
f[x][0]=f[x][1]=0;
g[x][0]=g[x][1]=0;
f[x][2]=g[x][2]=1;
//f->min g->max
if(!ls[x]&&!rs[x])return;
else if(ls[x]&&!rs[x]){
f[x][0]=Min(f[ls[x]][1],f[ls[x]][2]);
f[x][1]=Min(f[ls[x]][0],f[ls[x]][2]);
f[x][2]=Min(f[ls[x]][0],f[ls[x]][1])+1;
g[x][0]=Max(g[ls[x]][1],g[ls[x]][2]);
g[x][1]=Max(g[ls[x]][0],g[ls[x]][2]);
g[x][2]=Max(g[ls[x]][0],g[ls[x]][1])+1;
}
else if(rs[x]&&!ls[x]){
f[x][0]=Min(f[rs[x]][1],f[rs[x]][2]);
f[x][1]=Min(f[rs[x]][0],f[rs[x]][2]);
f[x][2]=Min(f[rs[x]][0],f[rs[x]][1])+1;
g[x][0]=Max(g[rs[x]][1],g[rs[x]][2]);
g[x][1]=Max(g[rs[x]][0],g[rs[x]][2]);
g[x][2]=Max(g[rs[x]][0],g[rs[x]][1])+1;
}
else{
f[x][0]=Min(f[ls[x]][1]+f[rs[x]][2],f[ls[x]][2]+f[rs[x]][1]);
f[x][1]=Min(f[ls[x]][0]+f[rs[x]][2],f[ls[x]][2]+f[rs[x]][0]);
f[x][2]=Min(f[ls[x]][1]+f[rs[x]][0],f[ls[x]][0]+f[rs[x]][1])+1;
g[x][0]=Max(g[ls[x]][1]+g[rs[x]][2],g[ls[x]][2]+g[rs[x]][1]);
g[x][1]=Max(g[ls[x]][0]+g[rs[x]][2],g[ls[x]][2]+g[rs[x]][0]);
g[x][2]=Max(g[ls[x]][1]+g[rs[x]][0],g[ls[x]][0]+g[rs[x]][1])+1;
}
// printf("%d:
",x);
// for(int i=0;i<3;++i)printf("%d ",f[x][i]);
// puts("");
// for(int i=0;i<3;++i)printf("%d ",g[x][i]);
// puts("");
}
int main(){
Read(rt);
f[0][1]=f[0][2]=f[0][0]=dyx;
g[0][0]=g[0][1]=g[0][2]=-dyx;
dfs(rt);
// cout<<f[4][0]<<" "<<f[4][1]<<" "<<f[4][2]<<endl;
// cout<<ls[4]<<" "<<rs[4]<<endl;
// cout<<f[ls[4]][1]+f[rs[4]][2]<<" "<<f[ls[4]][2]+f[rs[4]][1]<<endl;
int ansA=-1,ansB=dyx;
for(int i=0;i<3;++i)ansA=Max(ansA,g[rt][i]),ansB=Min(ansB,f[rt][i]);
printf("%d %d
",ansA,ansB);
return 0;
}
考虑区间 (dp) 处理出每一行的区间 ([l,r]) 刷 (k) 次的最多合法格子数目。
考虑预处理出每一行区间 ([l,r]) 直接扫一遍能扫到的最大合法格子数。于是 (dp) 的时候考虑枚举分界点和扫的次数,有:
复杂度 (O(n^5))
发现最后我们只需要知道 (f[1,m]) 所以可以考虑用线性 (dp) 优化掉一个 (n)
做完之后发现每一行都变成了背包中的物品,分组背包即可。
dwt Orz
#include<bits/stdc++.h>
using namespace std;
const int MAXN=51;
int a[51][51],n,m;
int T,dp[MAXN][MAXN][MAXN];
int maxlen[MAXN][MAXN][MAXN];
int f[MAXN*MAXN],g[MAXN*MAXN];
int sum[MAXN][MAXN],v[MAXN][MAXN];
inline int Max(int x,int y){return x>y?x:y;}
inline int Min(int x,int y){return x<y?x:y;}
int main(){
scanf("%d%d%d",&n,&m,&T);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j){
char ch;
cin>>ch;
a[i][j]=ch-'0';
}
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j){
sum[i][j]=sum[i][j-1]+a[i][j];
}
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
maxlen[i][j][j]=1;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
for(int k=j+1;k<=m;++k)
maxlen[i][j][k]=Max(sum[i][k]-sum[i][j-1],k-j+1-sum[i][k]+sum[i][j-1]);
for(int i=1;i<=n;++i){
v[i][0]=0;
for(int j=1;j<=m;++j)dp[j][j][1]=1;//
for(int len=2;len<=m;++len){
for(int l=1;l<=m-len+1;++l){
int r=l+len-1;
for(int k=l;k<r;++k){
dp[l][r][1]=maxlen[i][l][r];//
for(int t=2;t<=m;++t){
dp[l][r][t]=Max(dp[l][r][t],dp[l][k][t-1]+maxlen[i][k+1][r]);
}
}
}
}
for(int k=1;k<=m;++k) v[i][k]=dp[1][m][k];//
for(int l=0;l<=m;++l)
for(int j=0;j<=m;++j)
for(int k=0;k<=m;++k)
dp[l][j][k]=0;
}
for(int i=1;i<=n;++i){
for(int j=0;j<=T;++j)f[j]=0;//
for(int j=1;j<=m;++j){
for(int k=T;k>=j;--k){
f[k]=Max(f[k],g[k-j]+v[i][j]);
}
}
for(int j=0;j<=T;++j)g[j]=Max(g[j],f[j]);//
}
printf("%d
",g[T]);
return 0;
}
//dwt Orz
代码中细节:循环的值搞反了,以及区间枚举分界点的时候少了一种不分界的情况,还有最外层的背包初始化。
考虑预处理出每次派出兵的数量对城堡 (i) 的影响,发现每次派兵的人数一定是某个玩家驻守的人数的 (2cdot x+1.)
于是可以预处理出每种方案“体积”和“价值”,设计 (dp[i][j]) 表示前 (i) 城堡派出 (j) 兵力的最大收获,就变成一个分组背包了。
傻逼错误:忘记继承了上一次的状态,因为可以考虑对当前城堡不派兵。
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e4+10;
int s,n,m;
int a[200][200];
int dp[101][20001];
int v[200][200];
inline int Min(int x,int y){return x<y?x:y;}
inline int Max(int x,int y){return x>y?x:y;}
int main(){
scanf("%d%d%d",&s,&n,&m);
for(int i=1;i<=s;++i)
for(int j=1;j<=n;++j)
scanf("%d",&a[j][i]);
for(int i=1;i<=n;++i){
for(int j=1;j<=s;++j){
int val=(a[i][j]<<1)+1;
for(int k=1;k<=s;++k){
if(val>2*a[i][k])v[i][j]+=i;
}
}
}
for(int i=1;i<=n;++i){
for(int j=1;j<=s;++j){
for(int k=(a[i][j]<<1)+1;k<=m;++k){
dp[i][k]=Max(dp[i][k],dp[i-1][k-a[i][j]-a[i][j]-1]+v[i][j]);
}
for(int k=0;k<=m;++k) dp[i][k] = Max(dp[i][k], dp[i-1][k]);
//
}
}
int ans=-1;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
ans=Max(ans,dp[i][j]);
cout<<ans<<endl;
return 0;
}
第一问用经典转化,减去下标就变成了最长不降子序列,拿线段树搞搞就好。
关键在于第二问:可以考虑维护每一个 (dp) 状态可以从哪些位置转移过来。
那么,有一个结论:对于更改的新序列,里面的值一定在 (a) 中出现。
所以,对于一个区间 ([l,r]) 需要更改,首先它一定由两个最优决策点构成。
所以我们暴力枚举决策点,再暴力计算这一段区间修改的最小贡献。
考虑枚举区间断点 (k) 其左边覆盖为 (a[l]) 右边覆盖为 (a[r])
如果强行枚举复杂度 (O(n^4)) 无法接受。
所以考虑 (O(len)) 计算区间最小代价:预处理前缀代价和后缀代价再枚举断点即可。
注意代码细节,传入的区间端点一定要合乎计算函数中的实现。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN=2e6+10;
const int dyx=(1LL<<50);
int a[MAXN],b[MAXN],n;
int dp[MAXN],node,rt;
int ls[MAXN],rs[MAXN],maxn[MAXN];
int blen,bcnt,g[MAXN];
int pre[MAXN],suf[MAXN];
vector<int>vec[MAXN];
inline int Min(int x,int y){return x<y?x:y;}
inline int Max(int x,int y){return x>y?x:y;}
inline int Abs(int x){if(x<0)x=-x;return x;}
inline void pushup(int x){maxn[x]=Max(maxn[ls[x]],maxn[rs[x]]);}
inline int getpos(int x){return lower_bound(b+1,b+blen+1,x)-b;}
void change(int &x,int l,int r,int pos,int v){
if(!x)x=++node;
if(l==r){
maxn[x]=Max(maxn[x],v);
return;
}
int mid=(l+r)>>1;
if(pos<=mid)change(ls[x],l,mid,pos,v);
else change(rs[x],mid+1,r,pos,v);
pushup(x);
}
int query(int x,int L,int R,int l,int r){
if(L>=l&&R<=r)return maxn[x];
int mid=(L+R)>>1,res=-1;
if(l<=mid)res=query(ls[x],L,mid,l,r);
if(mid<r)res=Max(res,query(rs[x],mid+1,R,l,r));
return res;
}
int calc(int l,int r){
for(int i=l;i<=r;++i)pre[i]=pre[i-1]+Abs(b[a[i]]-b[a[l]]);
for(int i=r;i>=l;--i)suf[i]=suf[i+1]+Abs(b[a[i]]-b[a[r]]);
int R=dyx;
for(int k=l;k<=r;++k)R=Min(R,pre[k]+suf[k+1]);
for(int i=l;i<=r;++i)suf[i]=pre[i]=0;
return R;
}
signed main(){
scanf("%lld",&n);bcnt=n+1;
for(int i=1;i<=n;++i)scanf("%lld",&a[i]);a[++n]=dyx;
for(int i=1;i<=n;++i)a[i]+=n-i+1;
for(int i=1;i<=n;++i)b[i]=a[i];
for(int i=1;i<=n;++i)b[++bcnt]=a[i]-1;
sort(b+1,b+bcnt+1);blen=unique(b+1,b+bcnt+1)-b-1;
for(int i=1;i<=n;++i)a[i]=getpos(a[i]);
vec[0].push_back(0);
// change(rt,1,n,a[1],1);
for(int i=1;i<=n;++i){
dp[i]=query(rt,1,blen,1,a[i])+1;
change(rt,1,blen,a[i],dp[i]);
vec[dp[i]].push_back(i);
}
int ANS=-1;
for(int i=1;i<=n;++i)ANS=Max(ANS,dp[i]);
cout<<n-ANS<<endl;
// for(int i=1;i<=n;++i)cout<<b[a[i]]<<" ";
// puts("");
ANS=dyx;
calc(1,2);
// cout<<calc(3,4)<<endl;
// cout<<calc(1,4)<<endl;
// cout<<calc(1,4)<<endl;
// puts("??");
// for(int i=0;i<=n;++i){
// for(int j=i;j<=n;++j){
// printf("calc(%lld %lld)=%lld:",i,j,calc(i,j));
// }
// puts("");
// }
for(int i=1;i<=n;++i){
g[i]=dyx;
for(int j=0;j<(int)vec[dp[i]-1].size();++j){
int v=vec[dp[i]-1][j];
if(a[v]>a[i])continue;
int val=calc(v,i);
g[i]=Min(g[i],g[v]+val);
}
}
cout<<g[n]<<endl;
return 0;
}
代码细节:传入的区间端点,以及:离散化的数组有没有排序、询问的时候应该是小于等于 (a[i]) 的数、维护决策点可以用 vector
优化枚举复杂度、每个状态都是可以从 (0) 转移过来、calc
函数的边界以及 pre,suf
的清空。
还要注意:状态设计的是 (b[i]=j) 的最小代价,但是 (b[n]) 不一定等于 (a[n]) 所以可以添加一个最大值到序列末尾来进行转移以保证 (n) 可以都被转移到。
关于加入一个最大值的作用:答案应该是求一个位置 (pos) 使得 (dp[pos]+calc(pos,n)) 最小。
如果我们加入一个 (infty), 那么这个 (calc) 函数就一定会被非 (infty) 的数覆盖。而 (g[n+1]) 又正好求出了这个东西的最优解。所以答案就是 (g[n+1].) 利用了 (g) 的定义:以 (i) 为结尾的修复最小代价,以及利用的 (calc) 函数可以利用最大值恰好计算一个后缀除去最大值的性质,以达到恰好计算到最优解(答案)的目的。
dwt Orz
考虑设 (f_i) 表示凑齐数 (i) 的最小点数,所以 (f_isum f_j[j<i])
所以这玩意是 (log) 级别的(虽然很难卡满)
这题以防万一就开 (f[i][15]) 表示节点 (i) 染色是 (k) 的最小权值。
暴力枚举所有颜色转移即可。(O(nlog^2 n)) 可以记录信息做到 (O(n))
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e4+10;
const int dyx=(1<<30);
int dp[MAXN][15];
int n,head[MAXN],tot;
struct E {
int nxt,to;
} e[MAXN];
inline void add(int x,int y) {
e[++tot]=(E) {
head[x],y
};
head[x]=tot;
}
inline int Min(int x,int y) {
return x<y?x:y;
}
void dfs(int x,int fa) {
for(int i=1; i<=14; ++i)dp[x][i]=i;
for(int i=head[x]; i; i=e[i].nxt) {
int j=e[i].to;
if(j==fa)continue;
dfs(j,x);
}
for(int k=1; k<=14; ++k) {
for(int i=head[x]; i; i=e[i].nxt) {
int j=e[i].to;
if(j==fa)continue;
int M=dyx;
for(int l=1;l<=14;++l){
if(l==k)continue;
M=Min(M,dp[j][l]);
}
dp[x][k]+=M;
}
}
}
int main() {
scanf("%d",&n);
for(int i=1; i<n; ++i) {
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
for(int i=1; i<=n; ++i) {
for(int j=1; j<=14; ++j)
dp[i][j]=dyx;
}
dfs(1,0);
int ans=dyx;
for(int i=1; i<=14; ++i)ans=Min(ans,dp[1][i]);
cout<<ans<<endl;
return 0;
}
看着就是区间 (dp.)
观察到是中序遍历,所以对于一个区间 ([l,r]) 根一定在其中。
所以合并区间可以看成选择一个根并合并两棵子树。注意可以直接看成一棵树。就是根等于区间边界的时候;
否则直接暴力枚举断点转移即可。
至于输出路径,可以开和 (dp) 一样的数组记录一个区间更新最优的根在哪里,递归输出路径即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int dyx=4e9+10;
int n,a[50];
int f[51][51];
int pre[51][51];
inline int Max(int x,int y){return x>y?x:y;}
void print(int l,int r,int p){
printf("%lld ",p);
if(l==r)return;
if(pre[l][p-1])print(l,p-1,pre[l][p-1]);
if(pre[p+1][r])print(p+1,r,pre[p+1][r]);
}
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;++i)scanf("%lld",&a[i]);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
f[i][j]=-dyx;
for(int i=1;i<=n;++i)f[i][i]=a[i],pre[i][i]=i;
for(int len=2;len<=n;++len){
for(int l=1;l<=n-len+1;++l){
int r=l+len-1;
if(f[l][r-1]+a[r]>=f[l][r]){
pre[l][r]=r;
f[l][r]=f[l][r-1]+a[r];
}
if(f[l+1][r]+a[l]>=f[l][r]){
pre[l][r]=l;
f[l][r]=f[l+1][r]+a[l];
}
for(int k=l+1;k<r;++k){
if(f[l][k-1]*f[k+1][r]+a[k]>=f[l][r]){
f[l][r]=f[l][k-1]*f[k+1][r]+a[k];
pre[l][r]=k;
}
}
}
}
cout<<f[1][n]<<endl;
print(1,n,pre[1][n]);
return 0;
}
傻逼问题:输出路径的时候区间端点传错参数了……
[USACO09MAR]Cow Frisbee Team S
同余背包问题。考虑将物品体积 (mod f.)
注意这样的话 (01) 背包的倒叙枚举不成立了——因为同余下的大小关系是不确定的。
所以再开一个数组记录上一次转移的值就好了。
#include<bits/stdc++.h>
using namespace std;
int n,f,r[3000],dp[5000],g[5000];
const int mod=1e8;
inline int add(int x,int y){return (x+y)%mod;}
inline int mul(int x,int y){return 1ll*x*y%mod;}
int main(){
// freopen("111.in","r",stdin);
scanf("%d%d",&n,&f);
for(int i=1;i<=n;++i)scanf("%d",&r[i]);
for(int i=1;i<=n;++i)r[i]%=f;
g[r[1]]=1;g[0]=1;
for(int i=2;i<=n;++i){
for(int j=0;j<f;++j){
dp[j]=add(g[(j-r[i]+f)%f],g[j]);
}
for(int j=0;j<f;++j)g[j]=dp[j],dp[j]=0;
}
cout<<(g[0]+mod-1)%mod<<endl;
return 0;
}
最后别忘了取模,以及减一。去除 (g[0]=1) 的影响。
考虑单纯维护一个 (dp) 值应该怎么做:维护一个栈,里面维护左括号的位置,右括号匹配的时候相当于 (dp[i]=dp[pos]+1.)
所以到树上是类似的,维护一个栈,处理 (dp.) 但是题目给出的定义是从根到某个节点的子串数目,所以还需要做一个前缀和。
注意维护栈的细节:当前点如果匹配到了一个左括号,回去的时候记得将左括号还回去;如果进入了一个左括号,走的时候要出去。
(dp) 和 (ans) 数组的转移是不一样的:一个是 (i) 以结尾匹配的子串数目,另一个是对它的前缀和。
注意开 long long
.
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN=5e5+10;
namespace Hws {
inline int read() {
int s=0,w=1;
char ch=getchar();
while(!isdigit(ch)) {
if(ch=='-')w=-1;
ch=getchar();
}
while(isdigit(ch))s=s*10+ch-48,ch=getchar();
return w*s;
}
int top,pa[MAXN],n,cnt,st[MAXN];
int head[MAXN],tot,val[MAXN];
int ans[MAXN],fg[MAXN],dp[MAXN];
struct E {
int nxt,to;
} e[MAXN];
inline void add(int x,int y) {
e[++tot].to=y;
e[tot].nxt=head[x];
head[x]=tot;
}
void dfs(int x,int fa){
int T=0;
if(val[x]==1)st[++top]=x;
else{
if(top)dp[x]=dp[pa[st[top]]]+1,T=st[top--];
}
ans[x]=ans[fa]+dp[x];
for(int i=head[x];i;i=e[i].nxt){
int j=e[i].to;
dfs(j,x);
}
if(val[x]==1)--top;
else if(T)st[++top]=T;
}
void Init() {
n=read();
for(int i=1;i<=n;++i){
char ch;
cin>>ch;
val[i]=(ch=='(');
}
for(int i=2;i<=n;++i)pa[i]=read(),add(pa[i],i);
dfs(1,0);
int A=ans[1];
for(int i=2;i<=n;++i)A^=(i*ans[i]);
cout<<A<<endl;
}
}
signed main() {
// freopen("111.in","r",stdin);
Hws::Init();
return 0;
}
找区间不重集合让覆盖长度最大……按照右端点排序,将点离散化用线段树维护 (dp) 值,一个线段的 (dp) 值位移到右端点,每次查询一条线段左端点之前的最大 (dp) 值加上当前线段长度即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN=3e5+10;
int n,rt,dp[MAXN];
struct line {
int x,y,len;
bool operator<(const line&B)const{
return y<B.y;
}
} p[MAXN];
const int N=2e6+10;
int ls[N],rs[N],maxn[N],node;
int b[MAXN],bcnt,blen;
inline int getpos(int x){return lower_bound(b+1,b+blen+1,x)-b;}
inline int Max(int x,int y){return x>y?x:y;}
inline void pushup(int x){maxn[x]=Max(maxn[ls[x]],maxn[rs[x]]);}
void change(int &x,int l,int r,int pos,int v){
if(!x)x=++node;
if(l==r){
maxn[x]=Max(maxn[x],v);
return;
}
int mid=(l+r)>>1;
if(pos<=mid)change(ls[x],l,mid,pos,v);
else change(rs[x],mid+1,r,pos,v);
pushup(x);
}
int query(int x,int L,int R,int l,int r){
if(L>=l&&R<=r)return maxn[x];
int mid=(L+R)>>1;
int res=-1;
if(l<=mid)res=query(ls[x],L,mid,l,r);
if(mid<r)res=Max(res,query(rs[x],mid+1,R,l,r));
return res;
}
signed main() {
// freopen("P1868_3.in","r",stdin);
scanf("%lld",&n);
for(int i=1; i<=n; ++i) {
scanf("%lld%lld",&p[i].x,&p[i].y);
p[i].x++;p[i].y++;
p[i].len=p[i].y-p[i].x+1;
b[++bcnt]=p[i].x;
b[++bcnt]=p[i].y;
b[++bcnt]=p[i].x-1;
}
b[++bcnt]=0;
b[++bcnt]=1;
b[++bcnt]=3000010;
sort(b+1,b+bcnt+1);
blen=unique(b+1,b+bcnt+1)-b-1;
int p1=getpos(1);
sort(p+1,p+n+1);
for(int i=1;i<=n;++i){
// cout<<p[i].x<<" "<<p[i].y<<endl;
dp[i]=query(rt,1,blen,p1,getpos(p[i].x-1))+p[i].len;
change(rt,1,blen,getpos(p[i].y),dp[i]);
}
int ans=-1;
for(int i=1;i<=n;++i)ans=Max(ans,dp[i]);
cout<<ans<<endl;
return 0;
}
注意出现 (0.) 以及查询的时候记得 getpos .
CF713C Sonya and Problem Wihtout a Legend
这题看到严格递增先同时减去一个下标,仔细一看这不是跟前两天做的考试题一样嘛……设 (dp[i][j]) 表示 (b[i]=a[j]) 的最小代价,维护一个前缀 (min) 就可以做到 (O(n^2)) 了。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN=5001;
const int dyx=(1LL<<60);
int dp[MAXN][MAXN];
int n,a[MAXN];
int b[MAXN],blen,minn[MAXN];
inline int getpos(int x){return lower_bound(b+1,b+blen+1,x)-b;}
inline int Abs(int x){return x<0?-x:x;}
inline int Min(int x,int y){return x<y?x:y;}
signed main(){
// freopen("CF_in.txt","r",stdin);
scanf("%lld",&n);
for(int i=1;i<=n;++i)scanf("%lld",&a[i]);
for(int i=1;i<=n;++i)a[i]-=i;
for(int i=1;i<=n;++i)b[i]=a[i];
sort(b+1,b+n+1);
blen=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;++i)a[i]=getpos(a[i]);
// for(int i=1;i<=n;++i)cout<<a[i]<<" ";
// puts("");
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
dp[i][j]=dyx;
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
dp[i][a[j]]=minn[a[j]]+Abs(b[a[i]]-b[a[j]]);
}
for(int j=1;j<=n;++j)minn[j]=dyx;
for(int j=1;j<=n;++j)minn[a[j]]=Min(minn[a[j]],dp[i][a[j]]);
for(int j=2;j<=n;++j)minn[j]=Min(minn[j],minn[j-1]);
}
int ans=dyx;
for(int i=1;i<=n;++i)ans=Min(ans,dp[n][i]);
cout<<ans<<endl;
return 0;
}