其他分享
首页 > 其他分享> > 20211013

20211013

作者:互联网

AM DP大赏

先把我不打算说的题列上来

[AHOI2001]质数和分解

[NOI1997]最优乘车

[SCOI2005]互不侵犯

没有上司的舞会

A 移动服务

题目描述

​ 一个公司有三个移动服务员,最初分别在位置1,2,3处。

​ 如果某个位置(用一个整数表示)有一个请求,那么公司必须指派某名员工赶到那个地方去。

​ 某一时刻只有一个员工能移动,且不允许在同样的位置出现两个员工。

​ 从 p 到 q 移动一个员工,需要花费 c(p,q)。

​ 这个函数不一定对称,但保证 c(p,p)=0。

​ 给出N个请求,请求发生的位置分别为 \(p_1\to p_N\)。

​ 公司必须按顺序依次满足所有请求,目标是最小化公司花费,请你帮忙计算这个最小花费。

样例输入

5 9
0 1 1 1 1
1 0 2 3 2
1 1 0 4 1
2 1 5 0 1
4 2 3 4 0
4 2 4 1 5 4 3 2 1

样例输出

5

做法

很容易想到的DP表示为\(f_{t,i,j,k}\)表示\(t\)时刻,三个人在\(i,j,k\)位置时的最小花费

最初想法\(O(TN^3)\)转移,显然会T

考虑压掉一维枚举,显然枚举两个人,另外一个人一定在\(p_{t-1}\)

现在的问题就是空间不足,参考各大题解以及蓝书的做法,需要压掉一维,当然是选择压掉\(p_{t-1}\)那一维,转移就是枚举\(i,j,p_{t-1}\)谁能转移到\(p_t\)

\[\begin{aligned} f_{i+1,x,y}&=\min(f_{i,x,y}+c(p_{t-1}\to p_t),f_{i+1,x,y})\\ f_{i+1,p_i,y}&=\min(f_{i,x,y}+c(x\to p_t),f_{i+1,p_i,y})\\ f_{i+1,x,p_i}&=\min(f_{i,x,y}+c(y\to p_t),f_{i+1,x,p_i}) \end{aligned} \]

B 特别行动队

题目描述

​ 你有一支由n名士兵组成的部队,士兵从1到n编号,要将他们拆分成若干个特别行动队调入战场。

​ 出于默契的考虑,同一支行动队的队员的编号应该连续。

​ 编号为i的士兵的初始战斗力为\(x_i\)

​ 一支行动队的初始战斗力为队内所有队员初始战斗力之和。

​ 通过长期观察,你总结出一支特别行动队的初始战斗力x将按如下公式修正为x’:

​ \(x′=ax^2+bx+c\)

​ 其中,a,b,c是已知的系数(a<0)。

​ 作为部队统帅,你要为这支部队进行编队,使得所有特别行动队修正后的战斗力之和最大。

​ 试求出这个最大和。

样例输入

4 
-1 10 -20 
2 2 3 4 

样例输出

9

做法:斜率优化

先来个\(O(n^2)\)的DP

\[f_i=\displaystyle \max_{j=0}^{i-1}(f_j+a(sum_i-sum_j)^2+b(sum_i-sum_j)+c) \]

半优化版本

方法:每次的转移点从上次的最优决策点开始

原理其实来自斜率优化,但是可能蒙出来而且不需要计算,比较方便且能A此题,算是介于暴力和优化之间的特殊形式

斜率优化

一般来说有两种推导派系:

  1. 将式子化成\(y=kx+b\)形式然后推截距最值
  2. 假设从a转移比b更优,然后推式子

个人认为第二种更好理解,那我们开始吧!

假设从\(j\)转移比\(k\)更优且j>k,那么有

\[f_j+a(sum_i-sum_j)^2+b(sum_i-sum_j)+c>f_k+a(sum_i-sum_k)^2+b(sum_i-sum_k)+c \]

消掉相同的项得到

\[f_j+asum_j^2-2asum_isum_j-bsum_j>f_k+asum_k^2-2asum_isum_k-bsum_k \]

把所有与i有关的项移到一边得到

\[f_j+asum_j^2-bsum_j-(f_k+asum_k^2-bsum_k)>2asum_i(sum_j-sum_k) \]

因为\(sum_j-sum_k\)非负,我们再把它移过去,此时右侧只与i有关

\[\frac{(f_j+asum_j^2-bsum_j)-(f_k+asum_k^2-bsum_k)}{sum_j-sum_k}>2asum_i \]

如果令\(Y(x)=f_x+asum_x^2-bsum_x\),\(X(x)=sum_x\),那么该式子就可以改写成

\[\frac{Y(j)-Y(k)}{X(j)-X(k)}>2asum_i \]

这个式子很好的体现了斜率优化的名字的含义,有了这个式子,我们可以维护一个单调队列,每次在\(q[head]\)与\(q[head+1]\)连线构成斜率大于\(2asum_i\)时不断将队首出队,入队时维护一个上凸包即可

\(\cal code:\)

const int N=1e6+2;
typedef long long ll;
ll s[N],f[N],a,b,c,n;
int q[N],l=1,r=1;
ll Y(int x){
	return f[x]+a*s[x]*s[x]-b*s[x];
}
ll X(int x){
	return s[x];
}
double slope(int x,int y){
	return 1.0*(Y(x)-Y(y))/(X(x)-X(y));
}
bool upd(int a,int b,int c){
	return slope(a,b)<=slope(a,c);
}
int main(){
	scanf("%lld%lld%lld%lld",&n,&a,&b,&c);
	for(int i=1;i<=n;i++){
		ll x;
		scanf("%lld",&x);
		s[i]=s[i-1]+x;
	}
	for(int i=1;i<=n;i++){
		while(l<r&&slope(q[l],q[l+1])>2*a*s[i])l++;
		f[i]=f[q[l]]+a*(s[i]-s[q[l]])*(s[i]-s[q[l]])+b*(s[i]-s[q[l]])+c;
		while(l<r&&upd(q[r-1],q[r],i))r--;
		q[++r]=i;
	}
	printf("%lld",f[n]);
	return 0;
}

PM SDOI2011

预期 实际 \(\Delta\)
A 100 100 0
B 30 0 -30
C 100 100 0
总计 230 200 -30

A 打地鼠

题目描述

​ 打地鼠是这样的一个游戏:地面上有一些地鼠洞,地鼠们会不时从洞里探出头来很短时间后又缩回洞中。玩家的目标是在地鼠伸出头时,用锤子砸其头部,砸到的地鼠越多分数也就越高。

​ 游戏中的锤子每次只能打一只地鼠,如果多只地鼠同时探出头,玩家只能通过多次挥舞锤子的方式打掉所有的地鼠。你认为这锤子太没用了,所以你改装了锤子,增加了锤子与地面的接触面积,使其每次可以击打一片区域。如果我们把地面看做 m×n 的方阵,其每个元素都代表一个地鼠洞,那么锤子可以覆盖 r×c 区域内的所有地鼠洞。但是改装后的锤子有一个缺点:每次挥舞锤子时,对于这的区域中的所有地洞,锤子会打掉恰好一只地鼠。也就是说锤子覆盖的区域中,每个地洞必须至少有 1 只地鼠,且如果某个地洞中地鼠的个数大于 1,那么这个地洞只会有 1只地鼠被打掉,因此每次挥舞锤子时,恰好有r×c 只地鼠被打掉。由于锤子的内部结构过于精密,因此在游戏过程中你不能旋转锤子(即不能互换 r 和 c)。

​ 你可以任意更改锤子的规格(即你可以任意规定 r和 c的大小),但是改装锤子的工作只能在打地鼠前进行(即你不可以打掉一部分地鼠后,再改变锤子的规格)。你的任务是求出要想打掉所有的地鼠,至少需要挥舞锤子的次数。

​ Hint:由于你可以把锤子的大小设置为 1×1,因此本题总是有解的。

样例输入

3 3
1 2 1
2 4 2
1 2 1

样例输出

4

做法

  1. 枚举到一个数为负
  2. 枚举到一个数为正且以它为左上角无法进行消除

接下来讨论优化

因为暴力减是非常耗时的,所以考虑对这一部分进行优化

手玩样例,可以考虑对数组的每一行进行差分,那么操作次数与差分数组该位保持一致

如果我对\((x,y)\)进行一次消除,可以造成如下影响

\(\forall x\leq a\leq x+r-1\),\(w(x,y)+=1\),\(w(x,y+c)-=1\)

这样操作可以大大减小时间复杂度,失败条件同上

\(\cal code:\)

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=102;
int a[N][N],b[N][N],c[N][N],n,m,ans=0x3f3f3f3f,tot;
int solve(int x,int y){
    int res=0;
    memcpy(c,b,sizeof(c));
    for(int i=1;i<=n;++i){
        for(int j=1;j<=m+1;++j){
            if(c[i][j]==0) continue;
            if(c[i][j]<0||i+x-1>n||j+y-1>m) return 0x3f3f3f3f;
            int t=c[i][j];
            for(int k=0;k<x;++k){
                c[i+k][j]-=t;c[i+k][j+y]+=t;
            }
            res+=t;
        }
    }
    return res;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i){
        for(int j=1;j<=m;++j){
            scanf("%d",&a[i][j]);
            tot+=a[i][j];
        }
    }
    for(int i=1;i<=n;++i){
        for(int j=1;j<=m+1;++j){
            b[i][j]=a[i][j]-a[i][j-1];
        }
    }
    for(int i=n;i;--i){
        if(tot%i) continue;
        for(int j=m;j;--j){
            if(tot%(i*j)) continue;
            int t=solve(i,j);
            if(t!=0x3f3f3f3f){
                printf("%d",t);
                return 0;
            }
        }
    }
    return 0;
}

B 消防

题目描述

​ 某个国家有 n个城市,这 n 个城市中任意两个都连通且有唯一一条路径,每条连通两个城市的道路的长度为 \(z_i\)。

​ 这个国家的人对火焰有超越宇宙的热情,所以这个国家最兴旺的行业是消防业。由于政府对国民的热情忍无可忍(大量的消防经费开销)可是却又无可奈何(总统竞选的国民支持率),所以只能想尽方法提高消防能力。

​ 现在这个国家的经费足以在一条边长度和不超过s的路径(两端都是城市)上建立消防枢纽,为了尽量提高枢纽的利用率,要求其他所有城市到这条路径的距离的最大值最小。

​ 你受命监管这个项目,你当然需要知道应该把枢纽建立在什么位置上。

样例输入

5 2
1 2 5
2 3 2
2 4 4
2 5 3

样例输出

5

做法

首先上结论:枢纽一定建立在直径上

证明非常简单(其实是我懒得写,如果不求甚解可以直接按贪心处理)

那么我们继续贪,选定一个枢纽左端点,右端点一定越远越好

接下来所求就是在以下两者中取最大

  1. 直径端点到枢纽两个端点距离的最大值
  2. 非直径上的点到枢纽距离的最大值(不经过直径的边)

前面那个可以提前处理出直径上的点求出,后面那个点可以通过前面处理出的直径上的点每个一遍DFS求出

总的来说是思路想明白就水的一批的题

C 染色

题目描述

​ 给定一棵 n 个节点的无根树,共有 m 个操作,操作分为两种:

​ 1. 将节点 a 到节点 b 的路径上的所有点(包括 a 和 b)都染成颜色 c。

​ 2. 询问节点 a 到节点 b 的路径上的颜色段数量。

​ 颜色段的定义是极长的连续相同颜色被认为是一段。例如 112221 由三段组成:11、222、1。

样例输入

6 5
2 2 1 2 1 1
1 2
1 3
2 4
2 5
2 6
Q 3 5
C 2 1 1
Q 3 5
C 5 1 2
Q 3 5

样例输出

3
1
2

做法

区间操作,显而易见是树剖+线段树

但是因为求的是连续颜色的段数,直接区间和不可行,所以可以考虑维护左右端点的颜色便于合并

那么问题主要出在询问上,因为询问时树剖拆出的若干区间不会直接相连,合并时如何将左右端点判定清楚就是大问题

上图
image

我的想法是设两个点分别为x和y,x的父亲区间合并在右侧,Y的父亲合并在左侧,最后将x放在左边进行合并得到答案,大概图上像这样

那么还是以图上的x,y为例,分析L,R的选取

求出来的东西肯定是L \(\to\) R 的,然而x的合并是相反的,所以每次合并需要交换左右端点(就是它,改了一个多小时!)

\(\cal code:\)

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1e5+5;
int n,m,col[N],zc[N];//col[id[x]]
#define ls p<<1
#define rs p<<1|1
#define l(p) t[p].l
#define r(p) t[p].r
#define lc(p) t[p].lc
#define rc(p) t[p].rc
#define cnt(p) t[p].cnt 
#define lazy(p) t[p].lazy
struct seg{
    int l,r,lc,rc,cnt;
    int lazy;
}t[N<<2];
void pushup(int p){
    lc(p)=lc(ls),rc(p)=rc(rs);
    cnt(p)=cnt(ls)+cnt(rs)-(rc(ls)==lc(rs));
}
void pushdown(int p){
    if(!lazy(p)) return ;
    int c=lazy(p);
    lazy(ls)=lazy(rs)=c;
    lc(ls)=rc(ls)=lc(rs)=rc(rs)=c;
    cnt(ls)=cnt(rs)=1;
    lazy(p)=0;
}
void build(int p,int l,int r){
    l(p)=l,r(p)=r,lazy(p)=0;
    if(l==r){
        lc(p)=rc(p)=col[l];
        cnt(p)=1;
        return ;
    }
    int mid=(l+r)>>1;
    build(ls,l,mid);
    build(rs,mid+1,r);
    pushup(p);
}
void change(int p,int l,int r,int c){
    if(l(p)>=l&&r(p)<=r){
        lazy(p)=c;
        lc(p)=rc(p)=c;
        cnt(p)=1;
        return ;
    }
    pushdown(p);
    int mid=(l(p)+r(p))>>1;
    if(l<=mid) change(ls,l,r,c);
    if(r>mid) change(rs,l,r,c);
    pushup(p);
}
seg ask(int p,int l,int r){
    if(l(p)>=l&&r(p)<=r) return t[p];
    pushdown(p);
    int mid=(l(p)+r(p))>>1;
    if(l>mid) return ask(rs,l,r);
    if(r<=mid) return ask(ls,l,r);
    seg L=ask(ls,l,r),R=ask(rs,l,r);
    seg res=(seg){0,0,L.lc,R.rc,L.cnt+R.cnt-(rc(ls)==lc(rs)),0};
    return res;
}
struct edge{
    int nxt,to;
}e[N<<1];
int head[N],cnte;
void add(int u,int v){
    e[++cnte]=(edge){head[u],v};
    head[u]=cnte;
}
int dfn[N],fa[N],top[N],id[N],dep[N],cnt,tm,sz[N],son[N];
void dfs1(int x,int fat){
    fa[x]=fat;
    sz[x]=1;
    dfn[x]=++tm;
    dep[x]=dep[fat]+1;
    for(int i=head[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(!dfn[y]){
            dfs1(y,x);
            if((!son[x])||sz[y]>sz[son[x]]) son[x]=y;
            sz[x]+=sz[y];
        }
    }
}
void dfs2(int x,int tp){
    top[x]=tp;
    id[x]=++cnt;
    if(son[x]) dfs2(son[x],tp);
    for(int i=head[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(y==fa[x]||y==son[x]) continue;
        dfs2(y,y);
    }
}
char s[5];
void color(int x,int y,int v){
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        change(1,id[top[x]],id[x],v);
        x=fa[top[x]]; 
    }
    if(dep[x]<dep[y]) swap(x,y);
    change(1,id[y],id[x],v); 
}
seg merge(seg a,seg b){
    if(a.cnt==0) return b;
    //if(a==(seg){0,0,0,0,0,0}) return b;
    seg ret=(seg){0,0,a.lc,b.rc,a.cnt+b.cnt-(a.rc==b.lc),0};
    return ret;
}
int tree_ask(int x,int y){
    seg anx=(seg){0,0,0,0,0,0},any=(seg){0,0,0,0,0,0};
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]){
            any=merge(ask(1,id[top[y]],id[y]),any);
            y=fa[top[y]];
        }
        else{
            seg tmp=ask(1,id[top[x]],id[x]);
            swap(tmp.lc,tmp.rc);
            anx=merge(anx,tmp);
            x=fa[top[x]];
        }
    }
    if(dep[x]<dep[y])
        any=merge(ask(1,id[x],id[y]),any);
    else {
        seg tmp=ask(1,id[y],id[x]);
        swap(tmp.lc,tmp.rc);
        anx=merge(anx,tmp);
    }
    seg res=merge(anx,any);
    return res.cnt;
} 
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
        scanf("%d",&zc[i]);
    for(int i=1;i<n;++i){
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    dfs1(1,0);
    dfs2(1,1);
    for(int i=1;i<=n;++i)
        col[id[i]]=zc[i];
    build(1,1,n); 
    for(int i=1;i<=m;++i){
        int L,R;
        scanf("%s%d%d",s,&L,&R);
        if(s[0]=='C'){
            int c;
            scanf("%d",&c);
            color(L,R,c);
        }
        else
            printf("%d\n",tree_ask(L,R));
    }
    return 0;
}

标签:20211013,int,sum,样例,地鼠,bsum,锤子
来源: https://www.cnblogs.com/JA2012/p/15412541.html