抢匪的财宝
(treasure.pas/c/cpp)
【问题描述】
古威市长和韩丁纳市长都争着要找到抢匪野蛮老危的地图宝藏,放在自己城市的展览馆里。汪汪队帮助古威市长抢先一步找到了藏宝藏的地方。
宝藏埋在洞穴中长为n, 宽为m的矩形地面下。汪汪队从洞穴入口A(图中左上角)进入,从洞穴出口B(图中右下角)离开,每步只能向下走或向右走,当走到[x,y]方格时,可取出[x,y]方格及与[x,y]相邻的上下左右四个方向方格中的全部财宝。每个方格中的数表示财宝数量,如下图所示:
汪汪队从图中A[1,1]走到B[5,5]格时:
通过的路径是[1,1]→[2,1]→[3,1]→[4,1]→[4,2]→[4,3]→[4,4]→[4,5]→[5,5]
最多能取到财宝的数量为:0+1+2+1+0+3+0+0+8+5+0+5+0+6+7+0+8+8+6=60
【输入文件】
输入文件treasure.in中有n+1行。
第1行包含2个用空格分开的正整数n、m,分别表示洞穴中埋藏财宝地面的长和宽;接下来的n行,每行m个用空格分隔的正整数,表示各个格子中的财宝数量。
【输出文件】
输出文件treasure.out中有1个正整数,是汪汪队最多能取到的财宝数量。
【输入输出样例】
treasure.in |
treasure.out |
5 5 0 1 2 0 5 2 1 3 0 0 0 3 6 8 6 0 0 5 5 7 8 0 0 0 8 |
60
|
【数据规模】
100%的数据:1≤n,m≤500。
分析
一看就知道是一道动归
但是要取旁边的,怎么知道取过没有呢
然后就冥思苦想
看见动归题总想着,定义f[N][N],然后想转移方程,
然后发现怎么转都转移不过来
现在筋转过来了
增加状态呀
n=500,吝啬什么,要用什么加什么
其实增加一个从哪个方向转移过来就好了
f[i][j][0]=
max(f[i-1][j][0]+a[i+1][j]+a[i][j+1]+a[i][j-1],f[i-1][j][1]+a[i+1][j]+a[i][j+1]);
f[i][j][1]=
max(f[i][j-1][1]+a[i+1][j]+a[i][j+1]+a[i-1][j],f[i][j-1][0]+a[i+1][j]+a[i][j+1]);
两行的转移方程…………
聚会
给你 n 个点,任意两点之间有唯一路径可到达
每个点有一个点权 Ci
每条边有个边权 Wi
问:找出一个点作为聚会点,使得所有其他点到该点代价总和最小,代
价=点权*路径。如:i 作为集合点,点 x 的代价为 Cx*Len(x,i) ,Len(x,i)
表示 x 的代价为 Cx*Len(x,i) ,Len(x,i)表示 x 到 i 的距离。
【输入样例】
51 1 0 0 21 3 12 3 23 4 34 5 3【输出样例】
15
【数据规模】
30%数据:N<=30
100%数据:N<=100000,0<=Ci,W<=1000
分析
看出来是一棵树,打死没想到树形dp(换根)
然后写了个暴力
其实很简单,暴力就是枚举每个集合点,然后dfs
但很多都是重复的
我们换一个根,子树很多东西可以直接用
void Dfs(LL cur,LL fa)
{
for(LL i=first[cur];i;i=next[i]){
LL t=to[i];
if(t==fa) continue;
f[t]=f[cur]+(sum-num[t])*w[i]-num[t]*w[i];
ans=min(ans,f[t]);
Dfs(t,cur);
}
}
容斥原理真是个好东西
发现树形dp好多都有一个总子树和
减一下,就是剩下的和。
一定要学会这种思想,实现很容易
数字游戏
alice 和 bob 又在玩一个游戏。他们从一个数字 X0>=3,开始,期望到很大的数字。游戏是这样的:
alice 先走,然后轮流。在第 i 个回合中,轮到的玩家找一个小于当前数字的素数, 然后选择大于当前数字且是找的素数的倍数。即选择的素数 P<Xi-1,Xi>=Xi-1,Xi 是 P 的倍数,注意如果 P 是 Xi-1 的约数,那么数字不会变。
L 知道了他们两轮后的状态,现在给你一个 X2 表示两轮后选择的数字,请你确定最小的起始数字 X0。特别提醒,玩家不一定每一步选择是最聪明的,你应该考虑所有可能的情况。
【输入】
输入一个整数 X2,保证 X2 是合数
【输出】
输出一个整数 X0
【输入样例 1】14
【输出样例 1】6
【样例解释】
X0=6,
第一轮:alice 选择素数 5,并决定这轮数字X1=10
第一轮:BOB 选择素数 7,并决定这轮数字 X2=14
分析
推一下,我们发现,需要求每个数的最大z质约数
void pre(int n){
for(int i=2;i<=n;i++){
if(isp[i]==0){
prim[++cnt]=i;//质数
p[i]=i; //最大质因子
}
for(int j=1;j<=cnt&&prim[j]*i<=n;j++){//筛 i*比i小的质数 ->i是最大因子
isp[prim[j] * i]=1;
p[prim[j] * i]=p[i];//最大质因子,每个数是由最大因子筛选出
/*一个数的最大质因子是它的最大因子的最大质因子
这个break保证了合数只被最小质约数访问到。
比如40=2*20=4*10=5*8,只有i=20时,才会在prim[j]=2的时候被访问到。
当i=10时,在prim[j]=2时就已经被break了;同样的,i=8时,在prim[j]也已经break
*/
if(i%prim[j]==0)break;
}
}
}
线性筛一定要会,处理质数的利器。
序列切割
给定一个长度为 n 的序列 ai,和一个数字 c。你需要将这个序列切成若干段,对于每一个长度为 k 的数字段,这段中最小的 k/C 个数字(向下取整)都会自动删除,问如何切割使得最后剩下的数字和最小,最小是多少?
如序列[3,1,6,5,2]当 C=2,这是 3+6+5=14
【输入】
第一行是 2 个整数 N,C
第二行是 N 个整数 Ai
【输出】
一个整数,最小值
【样例输入】12 10
1 1 10 10 10 10 10 10 9 10 10 10
【样例输出】92
【样例解释】
其中一个最佳分区分别是[1,1],[10,10,10,10,10,10,10,10,10,10],其值分别为 2 和 90。
100%数据 n,c<=100000,1<=ai<=10^9
分析
dp
方程基本推对了,但考虑复杂了
标程是st+dp,很短
#include<bits/stdc++.h>
#define N 100005
#define LL long long
using namespace std;
LL n,c,num,a[N],st[N][30],f[N],Log[N];
LL read(){
LL cnt=0,f=1;char ch=0;
while(!isdigit(ch)){ch=getchar();if(ch=='-') f=-1;}
while(isdigit(ch)) cnt=cnt*10+(ch-'0'),ch=getchar();
return f*cnt;
}
LL rmq(LL l,LL r){
LL x=Log[r-l+1];
return min(st[l][x],st[r-(1<<x)+1][x]);
}
int main()
{
n=read(),c=read();
for(LL i=1;i<=n;i++){
a[i]=read(),num+=a[i],st[i][0]=a[i];
if(i==1) continue;
Log[i]=Log[i/2]+1;
}
if(n<c){cout<<num;return 0;}
for(LL j=1;(1<<j)<=n;j++)
for(LL i=1;i<=n;i++)
st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
for(LL i=c;i<=n;i++)
f[i]=max(f[i-1],f[i-c]+rmq(i-c+1,i));
cout<<num-f[n];
return 0;
}
现在有个毛病,遇到dp就紧张,总觉得转移方程是什么高大尚的东西
其实可以这么想
假设我要求一个东西,之前所有东西都有,我想要什么要什么
比如这道,结合样例
f[12]要么更f[11]一样,要么自己再加一段,即f[7]+min(a[i])(8<=i<=12)
而且f[i]一定是i的最优解,不要想多
多练练手啊
多米诺骨牌
有 n 个多米诺骨牌,从左到右排列,每一个骨牌都有一个高度 Li,向右推倒,它会直接向右倒下,如下图,倒下后该骨牌的顶端落在 Xi+Li 的位置,(Xi 是它位于的坐标,即倒下时该骨牌不会发生移动)
在倒下过程中,骨牌会碰到其他骨牌,碰到的骨牌会向右倒,如下图,最左边的骨牌倒下会碰倒 A,B,C,A,B,C 会倒下,但是不会直接碰到 D,但是 D 会因为 C 的倒下而碰倒。
在给你 N 个骨牌的坐标 Xi,和每个骨牌的高度 Li。则一个骨牌能碰倒另一个骨牌当切仅当 xi+li≥xj。同时有 Q 个询问 [L,R],问向右推到第 L 个骨牌,最少需要多少代价让 R 倒下。你可以临时增加某个骨牌的高度,增加 1 个高度的代价是 1.
6
1 5
3 3
4 4
9 2
10 1
12 1
4
1 2
2 4
2 5
2 6
输出样例
0
1
1
2
20%数据:N,Q<=1000,Xi<=10000
40%数据:N,Q<=10000,Xi<=100000
100%数据:2<=N<=100000,1<=Q<=2000000,Xi<=10^9
分析
栈+并查集
特别巧妙
将询问的左端点存在vector
然后倒着处理
能合并就合并
顺便处理前缀和
O1询问
#include<bits/stdc++.h>
/*
这题等于是求一段区间内有多少长度没被覆盖
把多米诺骨牌看成区间,按照倒序处理每个区间,看成是每次这个区间与后面的一些区间并成连通块,
处理x为当前区间的查询,这需要知道y属于哪个连通块,用栈+并查集维护,然后再维护一个未覆盖长度的后缀和,
就可以O1来回答询问,并成连通块的时候顺便更新这个后缀和
*/
using namespace std;
#define ll long long
#define N 200010
#define in read()
int read(){
int x=0;char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x;
}
struct Node{
int l,r;
}a[N],b[N];
vector<int>g[N];
int n,m;
stack<int> S;
int fa[N],l[N],r[N];
ll sum[N],ans[N];
int find(int x){
if(x!=fa[x])fa[x]=find(fa[x]);return fa[x];
}
inline void solve(int x,int id){
int tmp=b[id].r;
ans[id]=sum[x]-sum[find(tmp)];
}
int main(){
n=in;
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=n;i++){
a[i].l=in;
a[i].r=a[i].l+in;
//i骨牌的区间
}
m=in;
for(int i=1;i<=m;i++){
b[i].l=in;b[i].r=in;
g[b[i].l].push_back(i);
}
for(int i=n;i>=1;i--){
l[i]=a[i].l;r[i]=a[i].r;
while(!S.empty() && l[S.top()] <=r[i]){
r[i]=max(r[i],r[S.top()]);
fa[find(S.top())]=i;
S.pop();
}//与栈顶合并一个联通块
if(!S.empty()) sum[i]=sum[S.top()] + l[S.top()]-r[i];//如果栈有元素,说明后面有联通快,则统计后缀和
else sum[i]=0;//后缀和记录是当前点到最后的不联通数量
S.push(i);
int len= g[i].size();
for(int j=0;j<len;j++)//统计当前点做为左端点的询问答案
solve(i,g[i][j]);
}
for(int i=1;i<=m;i++)
printf("%lld\n",ans[i]);
return 0;
}