Test 2022.10.12

今天是关机专场

关于我好不容易写的题解因为关机而无了这件事

T1 理想的正方形

本来写了挺多的,现在不想多说了,简单来说就是维护一个二维的单调队列

一维单调队列

就是对每一行维护从\(i\)开始长度为\(n\)的区间中的最大最小值

二维单调队列

对我们一维单调队列维护出来所有处于同一列的值,纵向维护最大最小值,就可以得到一个矩形内的最大最小值了

Code

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+100;
int maxh[maxn][maxn],minh[maxn][maxn];
int Maxh[maxn][maxn],Minh[maxn][maxn];
int v[maxn][maxn];
int a,b,n,qmax[maxn],qmin[maxn];
int H,T,h,t;
int main()
{
	scanf("%d%d%d",&a,&b,&n);
	for(register int i=1;i<=a;++i)
		for(register int j=1;j<=b;++j)
			scanf("%d",&v[i][j]);
	
	for(register int i=1;i<=a;++i)
	{
		H=T=h=t=qmax[1]=qmin[1]=1;
		for(register int j=2;j<=b;++j)
		{
			while(H<=T&&v[i][j]>=v[i][qmax[T]])T--;	
			while(h<=t&&v[i][j]<=v[i][qmin[t]])t--;
			qmax[++T]=qmin[++t]=j;
			while(j-qmax[H]>=n)H++;
			while(j-qmin[h]>=n)h++;
			if(j>=n)
				maxh[i][j-n+1]=v[i][qmax[H]],
				minh[i][j-n+1]=v[i][qmin[h]];
		}	
	}
	for(register int j=1;j<=b-n+1;++j)
	{
		H=T=h=t=qmax[1]=qmin[1]=1;
		for(register int i=2;i<=a;++i)
		{
			while(H<=T&&maxh[i][j]>=maxh[qmax[T]][j])T--;	
			while(h<=t&&minh[i][j]<=minh[qmin[t]][j])t--;
			qmax[++T]=qmin[++t]=i;
			while(i-qmax[H]>=n)H++;
			while(i-qmin[h]>=n)h++;
			if(i>=n)
				Maxh[i-n+1][j]=maxh[qmax[H]][j],
				Minh[i-n+1][j]=minh[qmin[h]][j];
		}
	}
	int ans=2005120700;
	for(register int i=1;i+n-1<=a;++i)
		for(register int j=1;j+n-1<=b;++j)
			ans=min(ans,Maxh[i][j]-Minh[i][j]);
	cout<<ans;
	return 0;
}

T2 凸多边形的划分

不想多说了,我写的是网络流,对每一个点,他可以和所有不和他相邻的边匹配,然后简简单单建模一下,直接跑\(MCMF\)就行了,但是有问题:会考虑到同一个三角形,即使在图上是完全不同的两组匹配,可能是我太菜了,反正按照我现在的网络流水平,我是无法处理这种情况的。

正解 区间\(dp\)

对于每两个点(记为\(l,r\))的连线,我们考虑从\(l\)\(r\)的子多边形,把它划分成若干个三角形的最小化费,首先值得肯定的是,我们需要把动态规划的问题转化成每一个之前阶段的子问题,也就是说,这个子多边形还要划分成不同的子多边形,那么就是妥妥的区间动态规划问题问题了。

最外层枚举两个端点之间的距离\(len\),然后是左端点\(l\)和中间点\(k\),对于我们枚举的每个\(k\):\(l,r,k\)的连线都会构成一个三角形,产生新的花费,同时把多边形割成两个子多边形加上中间的三角形,最后按着定义来就是区间\(dp\)的板子了

转移方程是这样的

\[dp[l][r]=min(dp[l][r],dp[l][k]+dp[k][r]+val[l]\times val[r]\times val[k]) \]

小小注意

这个数据范围当然\(long long\)也是无法存下来的,最方便的就是用\(int128\)

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#define int __int128
using namespace std;
const int maxn=105,INF=1e30;
int dp[maxn][maxn],a[maxn];
using namespace std;
inline int re() 
{
	int x=0,f=1;
	char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
	for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^48);
	return x*=f;
}
inline void wr(int n)
{
	if(n==0) return;
	if(n<0)putchar('-'),n=-n;
    wr(n/10);
    putchar(n%10+'0');
}
inline int min(int x,int y){return x>y?y:x;}
signed main() 
{
    int n;
	n=re();
    for(register int i=1;i<=n;++i)a[i]=re();
    for(register int len=3;len<=n;++len)
	{
        for(register int l=1;l+len-1<=n;++l) 
		{
            int r=l+len-1;
            dp[l][r]=INF;
            for(register int k=l+1;k<=r-1;++k) 
                dp[l][r]=min(dp[l][r],dp[l][k]+dp[k][r]+a[l]*a[k]*a[r]);
        }
    }
    wr(dp[1][n]);
    return 0;
}

T3 电路维修

可以\(0-1bfs\)但是我不会也可以最短路,既然是最小步数,那么理所应当的想到要么就是\(IDA*\),要么就是最短路了。

建图

对于给出的\('/'\),我们把当前格子拆成四个点,把左下角和右上角建一条边权为\(0\)的双向边,表示不用改动就可以从这两个点互相到达;把左上角和右下角建一条边权为\(1\)的双向边,表示需要改变一次才能使他们互相到达

对于给出的' \ ',我们做相反的操作就行了,应该很好理解

建图的正确性

不用赘述了吧

小小的注意事项

首先稠密图肯定不能用\(spfa\)了,然而这道题普通的\(dijkstra\)的效率是\(O((nm)^2)\)的,所以还需要堆优化,复杂度就可以降到\(O(nm\log nm)\)

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#define int long long
using namespace std;
const int maxm=1e6;
const int maxn=3e5;
struct Edge{int u,v,w,nex;}E[maxm];
int T,n,m,s,t,tote,head[maxm];
void add(int u,int v,int w){E[++tote].u=u,E[tote].v=v,E[tote].w=w,E[tote].nex=head[u],head[u]=tote;}
int dis[maxm],vis[maxm];
char map[550][550];
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);
}
void ADD(int x,int y)
{
	if(map[x][y]=='/')
	{
		add((m+1)*x+y,(m+1)*(x-1)+y+1,0);
		add((m+1)*(x-1)+1+y,(m+1)*x+y,0);
		add((m+1)*(x-1)+y,(m+1)*x+y+1,1);
		add((m+1)*x+y+1,(m+1)*(x-1)+y,1);
	} 
	else 
	{
		add((m+1)*x+y,(m+1)*(x-1)+y+1,1);
		add((m+1)*(x-1)+1+y,(m+1)*x+y,1);
		add((m+1)*(x-1)+y,(m+1)*x+y+1,0);
		add((m+1)*x+y+1,(m+1)*(x-1)+y,0);
	}
}
void reset()
{
	memset(dis,0x3f,sizeof dis);
	memset(vis,0,sizeof vis);
	memset(head,0,sizeof head);
	tote=0;
}
priority_queue < pair<int,int> >q;
signed main()
{
	scanf("%lld",&T);
	while(T--)
	{
		reset();
		in(n),in(m);	
		for(int i=1;i<=n;++i)
		{
			for(int j=1;j<=m;++j)
				map[i][j]=getchar(),ADD(i,j);
			getchar();
		}
		s=1,t=(n+1)*(m+1);
		dis[s]=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(dis[v]>dis[x]+w)
				{
					dis[v]=dis[x]+w;
					q.push(make_pair(-dis[v],v));
				}
			}
		}
		if(!vis[t])printf("NO SOLUTION\n");
		else out(dis[t]),putchar('\n');
	}

	return 0;
}

T4 换教室

一眼\(dp\)

分析

首先题目中给出的点数是小于\(200\)的,所以可以直接用\(Floyd\)预处理出每个点之间的最小距离,然后注意题目中是会给出重边的,甚至会存在自环,所以我们需要特判一下,并且每次输入的边应该和当前的边取最小值才行,然后注意每个点到自己的距离一定是\(0\),因为当前这节课和上一节课上课的教室可能是同一间,所以当前点到当前点的最小距离应该是\(0\)

设计\(dp\)方程

很容易看出来这道题的阶段是前\(i\)间教室,然后状态就是用了\(j\)次换教室的机会,但是这个时候,我们仍然无法涉及到换课与否,所以应该另外引入一维状态\(k:0/1\)表示当前状态是否选择了换课

然后\(dp\)定义出来了转移按照定义就应该很好写了

第一种情况:当前选择不换课\(dp[i][j][0]\)

那么就需要从第\(i-1\)个阶段转移过来,此阶段可能申请换课,也可能不会,于是我们把每种可能乘以他的期望

写出来就是$$ dp[i][j][0]=min(dp[i-1][j][0]+dis[c[i-1]][c[i]],dp[i-1][j][1]+dis[c[i-1]][c[i]]\times (1-k[i-1])+dis[d[i-1]][c[i]]\times k[i-1]) $$

第二种情况:当前选择换课\(dp[i][j][1]\)

同样需要从第\(i-1\)个阶段转移过来,可能换课,也可能不会

写出来是

\[ dp[i][j][1]=min(dp[i-1][j-1][0] +dis[c[i-1]][c[i]]*(1-k[i]) +dis[c[i-1]][d[i]]*k[i], dp[i-1][j-1][1] +dis[c[i-1]][c[i]]*(1-k[i])*(1-k[i-1]) +dis[d[i-1]][c[i]]*(1-k[i])*k[i-1] +dis[c[i-1]][d[i]]*k[i]*(1-k[i-1]) +dis[d[i-1]][d[i]]*k[i]*k[i-1]) \]

初始化

注意\(dp[1][0][0]=dp[1][1][1]=1\)

答案

\(ans=\min{\min_{i=0}^{M}{dp[N][i][1],dp[N][i][0]}}\)

Code

#include<bits/stdc++.h>
#define re register 
using namespace std;
const int maxn=2e3+100;
const double INF=1e18;
int N,M;int n,m,u,v,w;
int c[maxn],d[maxn];int dis[maxn][maxn];
double k[maxn],dp[maxn][maxn][2];
void floyd()
{
	for(re int k=1;k<=n;++k)
		for(re int i=1;i<=n;++i)
			for(re int j=1;j<=n;++j)
				dis[j][i]=dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
	for(re int i=1;i<=n;++i)dis[i][i]=0;			
}
void prework()
{
	scanf("%d%d%d%d",&N,&M,&n,&m);
	memset(dis,63,sizeof dis);
	for(re int i=0;i<=N;++i)for(re int j=0;j<=M;++j)dp[i][j][0]=dp[i][j][1]=INF;
	for(re int i=1;i<=N;++i)scanf("%d",&c[i]);
	for(re int i=1;i<=N;++i)scanf("%d",&d[i]);
	for(re int i=1;i<=N;++i)scanf("%lf",&k[i]);
	for(re int i=1;i<=m;++i)
	{
		scanf("%d%d%d",&u,&v,&w);
		dis[u][v]=dis[v][u]=min(dis[u][v],w);
	}
	floyd();
	dp[1][0][0]=dp[1][1][1]=0;
}
int main()
{
	freopen("classroom.in","r",stdin);
	freopen("classroom.out","w",stdout);
	prework();
	for(re int i=2;i<=N;++i)
	{
		dp[i][0][0]=dp[i-1][0][0]+dis[c[i-1]][c[i]];
		for(re int j=1;j<=min(i,M);++j)
		{
			double tmp1=INF,tmp2;
			tmp1=min(dp[i-1][j][0]
							+dis[c[i-1]][c[i]],
							
							dp[i-1][j][1]
							+dis[c[i-1]][c[i]]*(1-k[i-1])
							+dis[d[i-1]][c[i]]*k[i-1]);
			tmp2=min(dp[i-1][j-1][0]
							+dis[c[i-1]][c[i]]*(1-k[i])
							+dis[c[i-1]][d[i]]*k[i],
			
							dp[i-1][j-1][1]
							+dis[c[i-1]][c[i]]*(1-k[i])*(1-k[i-1])
							+dis[d[i-1]][c[i]]*(1-k[i])*k[i-1]
							+dis[c[i-1]][d[i]]*k[i]*(1-k[i-1])
							+dis[d[i-1]][d[i]]*k[i]*k[i-1]);
			dp[i][j][0]=min(dp[i][j][0],tmp1);
			dp[i][j][1]=min(dp[i][j][1],tmp2);
		}
	}
	double ans=INF;
	for(re int i=0;i<=M;++i)ans=min(ans,min(dp[N][i][1],dp[N][i][0]));
	printf("%.2lf",ans);
	return 0;
}

posted @ 2022-10-12 19:47  Hanggoash  阅读(6)  评论(0编辑  收藏  举报
动态线条
动态线条end