20211013
作者:互联网
AM DP大赏
先把我不打算说的题列上来
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此题,算是介于暴力和优化之间的特殊形式
斜率优化
一般来说有两种推导派系:
- 将式子化成\(y=kx+b\)形式然后推截距最值
- 假设从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
做法
- 因为r和c不具有二分性,只能枚举r和c进行验证
- 在枚举的时候可以简单利用\(sum \mod rc=0\)进行初步剪枝
- 接下来考虑如何验证
- 最粗略想法是按r*c的大小一次次减,但这不现实
- 一个优化就是按矩形左上角的值一次减完(这是可以过的)
- 失败条件如下
- 枚举到一个数为负
- 枚举到一个数为正且以它为左上角无法进行消除
接下来讨论优化
因为暴力减是非常耗时的,所以考虑对这一部分进行优化
手玩样例,可以考虑对数组的每一行进行差分,那么操作次数与差分数组该位保持一致
如果我对\((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
做法
首先上结论:枢纽一定建立在直径上
证明非常简单(其实是我懒得写,如果不求甚解可以直接按贪心处理)
那么我们继续贪,选定一个枢纽左端点,右端点一定越远越好
接下来所求就是在以下两者中取最大
- 直径端点到枢纽两个端点距离的最大值
- 非直径上的点到枢纽距离的最大值(不经过直径的边)
前面那个可以提前处理出直径上的点求出,后面那个点可以通过前面处理出的直径上的点每个一遍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
做法
区间操作,显而易见是树剖+线段树
但是因为求的是连续颜色的段数,直接区间和不可行,所以可以考虑维护左右端点的颜色便于合并
那么问题主要出在询问上,因为询问时树剖拆出的若干区间不会直接相连,合并时如何将左右端点判定清楚就是大问题
上图
我的想法是设两个点分别为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