暑期集训加餐
rank...你知道我垫底就行了。mark 20
T1:(蛋糕)区间DP;T2:(游戏)贪心+dij最短路优化坐标的转移:对性质的判断和算法优化的可行性
T1:A和B分n块排成环的蛋糕,A先从里面任意拿一块,B每次都会拿最大的,A和B轮流拿,每次只能拿旁边至少一块蛋糕已经被拿走的蛋糕,每个蛋糕有一个价值,求A可以获得的最大价值。(n<=2000)
(1)dfs+记忆化:因为A的选择决定了B的选择,所以只枚举A选择哪块蛋糕就行,2^(n/2),看起来很悬,但是你加一个dp[l][r]:表示当前正在选(l,r)区间范围的蛋糕,A是要选的那个人,每次选完记录一下dp[l][r],下次到这就不搜了。就可以过了。
特别离谱的贪心就别搞了,安心打个暴力没准能A
#define int ll
inline int Max(int a,int b){return (a>b)?(a):b;}
inline int Min(int a,int b){return (a<b)?(a):b;}
int n;
int a[6010];
int dp[7000][7000];
int cnt=0;
inline int dfs(int l,int r)
{
//++cnt;if(cnt>100)exit(0);
//chu("l:%d r:%d\n",l,r);
if(dp[l][r]!=-1)return dp[l][r];
if(r-l+1>n){
dp[l][r]=0;return 0;
}
if(r-l+1==n)//如果已经最后唯一选择
{
dp[l][r]=min(a[l],a[r]);return dp[l][r];
}
//相当于是有2种选择,我从里面选出一种最优的作为dp[l][r]的值并且返回
//如果是A选l
if(a[l-1]>a[r])
{
dp[l][r]=a[l-1]+dfs(l-2,r);
}
else dp[l][r]=a[r]+dfs(l-1,r+1);
if(a[l]>a[r+1])
{
dp[l][r]=Min(dp[l][r],a[l]+dfs(l-1,r+1));
}
else dp[l][r]=Min(dp[l][r],a[r+1]+dfs(l,r+2));
return dp[l][r];
}
signed main()
{
// freopen("","r",stdin);
// freopen("","w",stdout);
n=re();int tot=0;
_f(i,1,n)
{
a[i]=re();a[i+n]=a[i+2*n]=a[i];
tot+=a[i];
}//3倍链
//dfs(int l,int r,int sum)当前准备选择的左右边界,B获得的价值,return 到结束为止的最小sum
//记忆化?就是剪?如果sum>dp[i][j]直接return dp[i][j]
//dp[i][j]:当前状态i,j,A正准备选择的最小价值
_f(i,0,n*3+10)
_f(j,0,n*3+10)
dp[i][j]=-1;
int ans=1e17;
_f(i,n+1,(n<<1))
{
if(a[i-1]>a[i+1])ans=min(ans,dfs(i-2,i+1)+a[i-1]);
else ans=min(ans,dfs(i-1,i+2)+a[i+1]);
}
chu("%lld",tot-ans);
return 0;
}
/*
5
2 8 1 10 9
12
816 469 799 183 660 250 298 118 925 91 52 970
3743
离谱贪心:
枚举起点,贪心让酱小
好像挺对的,呵呵
*/
(2)DP:dp[i][j]代表当前选完了[l,r]区间的A的最大价值
dp[i][j]=max(dp[i][j],val[i]+dp[i+2][j]/dp[i+1][j-1])(depend on val of val[i+1],val[j])
dp[i][j]=max(dp[i][j],val[j]+dp[i][j-2]/dp[i+1][j-1])(depend on val of val[i],val[j-1])
#include <bits/stdc++.h>
#define fre(x) freopen( #x ".in", "r", stdin ), freopen( #x ".out", "w", stdout )
using namespace std; typedef long long ll; typedef unsigned long long ull; typedef double db;
const int N = 4e3 + 10;
mt19937 mt( (ull)(&N) );
int Rand ( int l, int r ) { return uniform_int_distribution<>(l,r)(mt); }
#define int ll
int n, len, arr[N], dp[N][N];
void Solve ()
{
cin >> n, len = 2*n;
for( int i = 1; i <= n; ++i ) cin >> arr[i], arr[i+n] = arr[i];
for( int i = 1; i <= len; ++i ) dp[i][i] = arr[i];
for( int l = 2; l <= n; ++l ) for( int i = 1, j = i+l-1; j <= len; ++i, ++j )
dp[i][j] = max( dp[i][j], arr[i] + ( arr[i+1] > arr[j] ? dp[i+2][j] : dp[i+1][j-1] ) ),
dp[i][j] = max( dp[i][j], arr[j] + ( arr[i] > arr[j-1] ? dp[i+1][j-1] : dp[i][j-2] ) );
int res = 0;
for( int i = 1; i <= n; ++i ) res = max( res, dp[i][i+n-1] );
cout << res << "\n";
}
signed main ()
{
//fre(x);
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); Solve(); return 0;
}
T2:给你平面坐标系上的n个点,要求你从(x1,y1)出发,到(xn,yn),你有2种移动方式(1)让n个点中的某个花费每1的距离C费用移动到你的位置,并且带上你以同样代价移动(2)让某点把你横着或者竖着抛出去,假设抛出距离是dis,cost=A*dis+B。求最小花费(n<=1e5)
20tps:暴力,n=2,可以证明如果要单向移动一段距离,那么要么让点带着你,要么抛你,不存在2种方式一起用,不然一定更不优【ax+b+cx<2xa+b && ax+b+cx<2xc,你会发现a<c,a>c矛盾,所以一定更不优】。所以只有3种方式:横走+竖走;横走+竖抛;横抛+竖走。(from Catherine_leah)
100tps正解:Dij大法好,转化成图论。
其实不用建边,只需要在每个点枚举可以到达的位置,看lu[][][]有没有更新就行。
const int N=1e5+100;
int X,Y,A,B,C,n,stx,sty,edx,edy;
ll dis[510][510],lu[510][510][3];
int dx[5]={0,1,0,-1},dy[5]={1,0,-1,0};
struct node
{
int x,y,id;ll ds;
node(){}
node(int xx,int yy,int idid,ll dsds){x=xx,y=yy,id=idid,ds=dsds;}
bool operator<(const node&R)const
{
return ds>R.ds;
}
}e[250000+100];
priority_queue<node>q;
deque<node>qq;
int main()
{
//freopen("","r",stdin);
//freopen("","w",stdout);
X=re()+1,Y=re()+1,A=re(),B=re(),C=re();
n=re();
_f(i,1,X+1)
_f(j,1,Y+1)
{
dis[i][j]=1e17;
_f(k,0,2)lu[i][j][k]=1e17;
}
stx=re()+1,sty=re()+1;dis[stx][sty]=0;
qq.push_front(node(stx,sty,0,0));
_f(i,2,n-1)
{
int xi=re()+1,yi=re()+1;dis[xi][yi]=0;
qq.push_front(node(xi,yi,0,0));
}
edx=re()+1,edy=re()+1;dis[edx][edy]=0;
qq.push_front(node(edx,edy,0,0));
while(!qq.empty())
{
node u=qq.front();qq.pop_front();
_f(i,0,3)
{
int xn=u.x+dx[i],yn=u.y+dy[i];
if(dis[xn][yn]==1e17&&xn<=X&&xn>=1&&yn<=Y&&yn>=1)
{
dis[xn][yn]=u.ds+1*C;qq.push_back(node(xn,yn,0,dis[xn][yn]));
}
}
}
// _f(i,1,X)
// _f(j,1,Y)
// chu("dis[%d][%d]:%lld\n",i,j,dis[i][j]);
q.push(node(stx,sty,2,0));
while(!q.empty())
{
node u=q.top();q.pop();
if(lu[u.x][u.y][u.id]!=1e17)continue;
lu[u.x][u.y][u.id]=u.ds;
// chu("lu[%d][%d][%d]:%lld\n",u.x,u.y,u.id,u.ds);
if(u.x==edx&&u.y==edy)break;
if(u.id==2)//如果是被带着移动
{
//可以被抛出去
if(lu[u.x][u.y][0]==1e17)q.push(node(u.x,u.y,0,u.ds+B));
if(lu[u.x][u.y][1]==1e17)q.push(node(u.x,u.y,1,u.ds+B));
_f(i,0,3)//可以继续被带着走 优化(上dis)
{
int xn=u.x+dx[i],yn=u.y+dy[i];
if(xn<=X&&xn>=1&&yn<=Y&&yn>=1&&lu[xn][yn][2]==1e17)
q.push(node(xn,yn,u.id,u.ds+C));
//如果xn,yn已经在这里被带过一回了,那再从其他地方带着过去一定不是最优
}
}
else//如果飞着呢
//那就0代表y的加减,1代表x的加减
{
for(rint i=u.id;i<4;i+=2)
{
int xn=u.x+dx[i],yn=u.y+dy[i];
if(xn>=1&&xn<=X&&yn<=Y&&yn>=1&&lu[xn][yn][u.id]==1e17)
{
q.push(node(xn,yn,u.id,u.ds+A));
}
}
if(lu[u.x][u.y][2]==1e17)
q.push(node(u.x,u.y,2,u.ds+dis[u.x][u.y]));
//现在是放进去,但是不进行更新
}
}
ll ans=min(lu[edx][edy][0],min(lu[edx][edy][1],lu[edx][edy][2]));
chu("%lld",ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】