20221012
20221011
理想的正方形
单调队列
显然可以对一个\(n\times n\)的区间的最值进行维护,但直接维护最值无法及时让不在范围内的值弹出,并消去它的影响,而对于这种需要将不在范围内的弹出的同时维护最值的,我们自然而然就想到了单调队列。但是单调队列貌似只能维护一维的数值,而这里却需要维护二维的数值,那该怎么办呢?既然单调队列只能维护一维的数值,那我们先维护横行的一维,再维护竖列的一维不就能维护两维了吗?两个单调队列分别维护最大和最小值,再用四个数组存下横行的最值信息和竖列的最值信息,最后统计答案即可。
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define fo(i,x,y) for(int i=x;i<=y;++i)
using namespace std;
template<typename T>inline void in(T &x){
x=0;int f=0;char c=getchar();
for(;!isdigit(c);c=getchar())f|=(c=='-');
for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
x=f?-x:x;
}
template<typename T>inline void out(T x){
if(x<0)x=~x+1,putchar('-');
if(x>9)out(x/10);
putchar(x%10^48);
}
const int N=1005,NN=1e6+5;
int a,b,n;
int val[N][N];
int ans;
int X[N][N],x[N][N],Y[N][N],y[N][N];
//X维护行最大值,x维护行最小值,Y维护列最大值,y维护列最小值
int Q[N],q[N],Head,head,Tail,tail;
//Q维护最大值的单调队列,q维护最小值的单调队列
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 main(){
in(a),in(b),in(n);
fo(i,1,a)fo(j,1,b)in(val[i][j]);
fo(i,1,a){
head=Head=0;
tail=Tail=Q[1]=q[1]=1;
fo(j,2,b){
while(val[i][j]>=val[i][Q[Tail]]&&Tail>Head)--Tail;
while(val[i][j]<=val[i][q[tail]]&&tail>head)--tail;
Q[++Tail]=j,q[++tail]=j;
while(j-Q[Head+1]>=n)++Head;
while(j-q[head+1]>=n)++head;
if(j>=n)X[i][j-n+1]=val[i][Q[Head+1]];
if(j>=n)x[i][j-n+1]=val[i][q[head+1]];
}
}
fo(i,1,b-n+1){
head=Head=0;
tail=Tail=Q[1]=q[1]=1;
fo(j,2,a){
while(X[j][i]>=X[Q[Tail]][i]&&Tail>Head)--Tail;
while(x[j][i]<=x[q[tail]][i]&&tail>head)--tail;
Q[++Tail]=j,q[++tail]=j;
while(j-Q[Head+1]>=n)++Head;
while(j-q[head+1]>=n)++head;
if(j>=n)Y[j-n+1][i]=X[Q[Head+1]][i];
if(j>=n)y[j-n+1][i]=x[q[head+1]][i];
}
}
ans=0x3f3f3f3f;
fo(i,1,a-n+1)
fo(j,1,b-n+1)
ans=min(ans,Y[i][j]-y[i][j]);
out(ans);
return 0;
}
凸多边形的划分
区间dp
区间\(dp\)版题,\(dp[i][j]\)表示从顶点\(i\)到顶点\(j\)的顶点权值乘积和的最小值。\(w[i]\)表示顶点\(i\)的权值。
\[dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+w[i]\times w[j]\times w[k])(i\le k\le j)
\]
注意爆\(longlong\)。
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define fo(i,x,y) for(int i=x;i<=y;++i)
using namespace std;
template<typename T>inline void in(T &x){
x=0;int f=0;char c=getchar();
for(;!isdigit(c);c=getchar())f|=(c=='-');
for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
x=f?-x:x;
}
template<typename T>inline void out(T x){
if(x<0)x=~x+1,putchar('-');
if(x>9)out(x/10);
putchar(x%10^48);
}
const int N=505;
int n;
__int128 w[N];
__int128 dp[N][N],ans;
inline __int128 miN(__int128 a,__int128 b){
return a<b?a:b;
}
int main(){
in(n);
fo(i,1,n){
in(w[i]);
w[i+n]=w[i];
}
n<<=1;
fo(i,1,n){
fo(j,1,n){
dp[i][j]=(__int128)1e30;
}
}
fo(i,1,n)dp[i][i+1]=0;
n>>=1;
ans=(__int128)1e30;
fo(len,2,n-1){
fo(j,1,n){
fo(k,j+1,j+len-1){
dp[j][j+len]=miN(dp[j][j+len],dp[j][k]+dp[k][j+len]+w[j]*w[j+len]*w[k]);
}
}
}
fo(i,1,n)ans=miN(ans,dp[i][i+n-1]);
out(ans);
return 0;
}
电路维修
题目分析
对于每一条线路,只有两种存在方式,要么\
要么/
,转化一次状态对答案的贡献就为\(1\),而我们实际上要求的就是从左上角走到右下角的最小状态改变次数。所以我们只需要建边后跑一遍最短路即可。那么怎么建边呢?如果一条线路为\
那么就建立一条从左上的点到右下的点的一条边权为\(0\)的边,再建立一条从右上到左下的边权为\(1\)的边,/
反之。
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define ll long long
#define fo(i,x,y) for(int i=x;i<=y;++i)
using namespace std;
template<typename T>inline void in(T &x){
x=0;int f=0;char c=getchar();
for(;!isdigit(c);c=getchar())f|=(c=='-');
for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
x=f?-x:x;
}
template<typename T>inline void out(T x){
if(x<0)x=~x+1,putchar('-');
if(x>9)out(x/10);
putchar(x%10^48);
}
const int N=505;
int n,m;
char map[N][N];
struct edge{
int v,w,nex;
}e[1000005];
int cnt;
int head[1000005],tot,d[1000005];
bool vis[1000005];
int en;
inline void init(){
fo(i,1,en){
vis[i]=head[i]=0;
d[i]=0x3f3f3f3f;
}
tot=cnt=0;
}
inline void add(int u,int v,int w){
e[++tot].v=v,e[tot].w=w,e[tot].nex=head[u],head[u]=tot;
e[++tot].v=u,e[tot].w=w,e[tot].nex=head[v],head[v]=tot;
}
priority_queue < pair<int,int> >q;
inline void dj(){
d[1]=0;
q.push(make_pair(0,1));
while(!q.empty()){
int x=q.top().second;q.pop();
if(vis[x])continue;
vis[x]=1;
for(int i=head[x];i;i=e[i].nex){
int v=e[i].v,w=e[i].w;
if(d[v]>d[x]+w){
d[v]=d[x]+w;
q.push(make_pair(-d[v],v));
}
}
}
}
int main(){
int t;
in(t);
while(t--){
in(n),in(m);
en=(m+1)*(n+1);
init();
fo(i,1,n){
fo(j,1,m){
map[i][j]=getchar();
++cnt;
add(cnt,cnt+m+2,map[i][j]=='/'?1:0);
add(cnt+1,cnt+m+1,map[i][j]=='/'?0:1);
}
++cnt;
getchar();
}
dj();
if(!vis[en]){
puts("NO SOLUTION");
continue;
}
out(d[en]),putchar('\n');
}
return 0;
}
换教室
题目分析
因为每节课是按顺序上的,又有换或不换两种状态,最多又只能选\(m\)节课换,很容易想到\(dp\)。定义\(dp[i][j][0/1]\)为到第\(i\)个教室,选择换了\(j\)节课,当前这节课换或不换的耗费体力值的最小期望值。当然,每次选择换课还有换成功和失败两种状态,要各自乘上他们对应的期望。然后还需要预处理出从\(i\)教室到\(j\)教室的最短距离\(dis[i][j]\),这里可以用\(floyd\)来完成。
\[dp[i][j][0]=min(dp[i-1][j][1]+dis[c[i]][d[i-1]]*k[i-1]+dis[c[i]][c[i-1]]*(1-k[i-1]),dp[i-1][j][0]+dis[c[i]][c[i-1]]);
\]
\[dp[i][j][1]=min(dp[i-1][j-1][1]+dis[d[i-1]][d[i]]*k[i]*k[i-1]+dis[d[i-1]][c[i]]*k[i-1]*(1-k[i])+dis[c[i-1]][d[i]]*(1-k[i-1])*k[i]+dis[c[i-1]][c[i]]*(1-k[i-1])*(1-k[i]),dp[i-1][j-1][0]+dis[c[i-1]][d[i]]*k[i]+dis[c[i-1]][c[i]]*(1-k[i]));
\]
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define fo(i,x,y) for(int i=x;i<=y;++i)
using namespace std;
template<typename T>inline void in(T &x){
x=0;int f=0;char c=getchar();
for(;!isdigit(c);c=getchar())f|=(c=='-');
for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
x=f?-x:x;
}
template<typename T>inline void out(T x){
if(x<0)x=~x+1,putchar('-');
if(x>9)out(x/10);
putchar(x%10^48);
}
const int N=2005;
int n,m,v,e;
int c[N],d[N];
double k[N];
int dis[N][N];
double dp[N][N][2];
int main(){
in(n),in(m),in(v),in(e);
fo(i,1,n)in(c[i]);
fo(i,1,n)in(d[i]);
fo(i,1,n)cin>>k[i];
int a,b,w;
memset(dis,0x3f,sizeof(dis));
fo(i,1,e){
in(a),in(b),in(w);
dis[a][b]=dis[b][a]=min(dis[a][b],w);
}
fo(k,1,v)
fo(i,1,v)
fo(j,1,i-1)
dis[j][i]=dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
fo(i,1,v)dis[i][i]=0;
fo(i,0,n){
fo(j,0,m){
dp[i][j][0]=dp[i][j][1]=1e9;
}
}
dp[1][0][0]=dp[1][1][1]=0;
fo(i,2,n){
fo(j,0,min(i,m)){
dp[i][j][0]=min(dp[i-1][j][1]+dis[c[i]][d[i-1]]*k[i-1]+dis[c[i]][c[i-1]]*(1-k[i-1]),dp[i-1][j][0]+dis[c[i]][c[i-1]]);
if(j!=0)dp[i][j][1]=min(dp[i-1][j-1][1]+dis[d[i-1]][d[i]]*k[i]*k[i-1]+dis[d[i-1]][c[i]]*k[i-1]*(1-k[i])+dis[c[i-1]][d[i]]*(1-k[i-1])*k[i]+dis[c[i-1]][c[i]]*(1-k[i-1])*(1-k[i]),dp[i-1][j-1][0]+dis[c[i-1]][d[i]]*k[i]+dis[c[i-1]][c[i]]*(1-k[i]));
}
}
double ans=1e9;
fo(i,0,m)
ans=min(ans,min(dp[n][i][0],dp[n][i][1]));
printf("%.2lf",ans);
return 0;
}