11-1模拟赛 By cellur925
期望得分:70+100+60
实际得分:70+20+60 \(qwq\)。
T1:有一个 \(n\) × \(n\) 的 \(01\) 方格, 图图要从中选出一个面积最大的矩形区域, 要求这个矩形区域不能有超过 \(k\) 个 \(1\)。
开始只会\(O(n^4)\)算法,即枚举左上角和右下角,然后去写了T2&T3,回来想了一个多小时大概,还是没想出...但是造了大数据验证了一下暴力应该是没问题的。
70分暴力:
#include<cstdio>
#include<algorithm>
using namespace std;
int n,k,ans;
int mapp[600][600];
int main()
{
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&mapp[i][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
mapp[i][j]+=mapp[i-1][j]+mapp[i][j-1]-mapp[i-1][j-1];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int l=1;l<=n&&i-l+1>=1;l++)
for(int r=1;r<=n&&j-r+1>=1;r++)
{
int ii=i-l+1;
int jj=j-r+1;
int qwq=mapp[i][j]+mapp[ii-1][jj-1]-mapp[ii-1][j]-mapp[i][jj-1];
if(qwq>k) continue;
ans=max(ans,l*r);
}
printf("%d",ans);
return 0;
}
正解:很巧妙的想法:我们不需枚举那么多,有些是重复的。因为矩阵的二维前缀和是有单调性的。什么意思?我们枚举矩阵的上下界,然后只枚举一个右边界,左边界从1开始向右推进,显然在开始时刻包含的1最多,在不断向右推进的过程中包含的1越来越少,当找到一个满足\(k\)的时刻我们便停止向右推进,这时一定保证最优。因为右面的情况会越来越少。
#include<cstdio>
#include<algorithm>
using namespace std;
int n,k,ans;
int f[1000][1000];
int ask(int a,int b,int c,int d)
{
return f[c][d]+f[a-1][b-1]-f[a-1][d]-f[c][b-1];
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&f[i][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
f[i][j]+=f[i-1][j]+f[i][j-1]-f[i-1][j-1];
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
{
int l=1;
for(int r=1;r<=n;r++)
{
while(ask(i,l,j,r)>k) l++;
ans=max(ans,(r-l+1)*(j-i+1));
}
}
printf("%d\n",ans);
return 0;
}
主要是这个单调性,它就是很显然的东西,但是如果不注意它的话就真找不到正解==。
T2:他正在玩一款策略游戏, 地图由 \(n\) 座城市组成, 并由 \(n - 1\) 条无向带权边连接成树形结构。 为了解决物资补给, 图图需要在这 \(n\) 座城市选出若干座城市建立机场, 其中在第 \(i\) 座城市建立机场的代价是 \(cost[i]\)。
建立机场之后, 每座城市得到补给的代价为该城市到最近机场的距离。 而他总共花费的代价即为建立机场的代价与每座城市得到补给的代价之和, 当然他想让这个代价最小。
考场上读完题后感觉是自己做过的原题然后就惨惨了。事后感觉一点也不一样啊,只是看起来很像⑧了(摔)。事实是考场上几乎不会出原题的,如果感觉像一定要一再尝试否认自己。不能盲目自信==。
这种最优化&在树上背景,大多是树形dp。我竟然从没向这想过(。首先我们考虑一条链的情况,\(f[i]\)表示前\(i\)个城市的代价均已计算,最后一个机场设在第\(i\)个城市的最小花费。转移:
https://cdn.luogu.org/upload/pic/41397.png
如果状态设计出来,这个转移应该还是不难理解的。考虑如何扩展到树的情况,一般我们设计树形dp的状态时,有诸如\(dp[i]\)为以\(i\)为根的子树\(balabala\)...的表述。
我们考虑设计这样一个状态:\(f[i][j]\)表示以\(i\)为根的子树,当前距离\(i\)最近的机场设在了\(j\),子树内所有城市花费之和的最小值,另设一个\(g[i]\)保存\(min{f[i][j]}\)。那么转移:有两部分我们是一定要花费的,\(i\)到\(j\)的距离和在\(j\)点建造的花费。其他:枚举\(i\)子树内的所有儿子\(v\),在\(g[v]\)和\(f[v][j]\)-\(val[j]\)中寻求最小。(减去\(val[j]\)是为了防止算多。)那么转移即为这样:
for(int j=1;j<=n;j++)
{
f[u][j]=val[j]+dis[u][j];
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v!=fa) f[u][j]+=min(g[v],f[v][j]-val[j]);
}
}
可以看出我们还需要计算一下两点间的距离:因为是在一棵树上,因为复杂度被控制在不\(n^2\)内,那么我们可以从每个点出发进行一遍\(dfs\)求出这个距离。(感觉\(std\)的这个想法好巧妙啊x)
for(int i=1;i<=n;i++) dfs(i,0,i);
void dfs(int u,int fa,int s)
{
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa) continue;
dis[s][v]=dis[s][u]+edge[i].val;
dfs(v,u,s);
}
}
完整代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 2600
using namespace std;
int n,tot;
int head[maxn],val[maxn],g[maxn],dis[maxn][maxn],f[maxn][maxn];
struct node{
int to,next,val;
}edge[maxn<<1];
void add(int x,int y,int z)
{
edge[++tot].to=y;
edge[tot].next=head[x];
head[x]=tot;
edge[tot].val=z;
}
void dfs(int u,int fa,int s)
{
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa) continue;
dis[s][v]=dis[s][u]+edge[i].val;
dfs(v,u,s);
}
}
void TreeDP(int u,int fa)
{
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v!=fa) TreeDP(v,u);
}
for(int j=1;j<=n;j++)
{
f[u][j]=val[j]+dis[u][j];
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v!=fa) f[u][j]+=min(g[v],f[v][j]-val[j]);
}
}
g[u]=f[u][1];
for(int i=1;i<=n;i++) g[u]=min(g[u],f[u][i]);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&val[i]);
for(int i=1;i<=n-1;i++)
{
int x=0,y=0,z=0;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z),add(y,x,z);
}
for(int i=1;i<=n;i++) dfs(i,0,i);
TreeDP(1,0);
printf("%d\n",g[1]);
return 0;
}
T3:他计划去 \(Bzeroth\) 的精灵王国去旅游, 精灵王国由 \(n\) 座城市组成, 第 \(i\) 座城市有 \(3\) 个属性 \(x[i]\), \(w[i]\), \(t[i]\)。
在精灵王国的城市之间穿行只能依靠传送阵, 第 \(i\) 座城市的传送阵可以将他从城市 \(i\) 传送到距离城市 \(i\) 不超过 \(w[i]\)的任意一个城市, 并需要 \(t[i]\)的时间完成传送。 现在他知道了每个城市的坐标 \(x[i]\), 想知道他从城市 \(s\) 到城市 \(t\) 的最小时间。
唔...读完题后感觉只会打\(60\)分的暴力,建边最坏复杂度\(O(n^2)\),跑\(dijistra\)复杂度\(O(mlogn)\),总复杂度近似为\(O(n^2logn)\)。
\(60\)分暴力:
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#define maxn 153000
using namespace std;
typedef long long ll;
int n,s,t,tot;
int pos[maxn],dist[maxn],w[maxn],head[maxn];
ll dis[maxn];
bool vis[maxn];
struct node{
int to,next,val;
}edge[6300000];
void add(int x,int y,int z)
{
edge[++tot].to=y;
edge[tot].next=head[x];
head[x]=tot;
edge[tot].val=z;
}
void dijkstra()
{
priority_queue<pair<ll,int> >q;
q.push(make_pair(0,s));
for(int i=1;i<=n;i++) dis[i]=2e9;
dis[s]=0;
while(!q.empty())
{
int u=q.top().second;q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(dis[v]>dis[u]+edge[i].val)
{
dis[v]=dis[u]+edge[i].val;
q.push(make_pair(-dis[v],v));
}
}
}
}
int main()
{//60 pts
freopen("trip.in","r",stdin);
freopen("trip.out","w",stdout);
scanf("%d%d%d",&n,&s,&t);
for(int i=1;i<=n;i++) scanf("%d",&pos[i]);
for(int i=1;i<=n;i++) scanf("%d",&dist[i]);
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
for(int i=1;i<=n;i++)
{
int j=i+1;
while(pos[j]-pos[i]<=dist[i]&&j<=n)
{
add(i,j,w[i]);
j++;
}
j=i-1;
while(pos[i]-pos[j]<=dist[i]&&j>=1)
{
add(i,j,w[i]);
j--;
}
}
dijkstra();
printf("%lld\n",dis[t]);
return 0;
}
正解:这个复杂度的瓶颈其实就在建边上,首先很显然能建的边是一段连续的区间,我们可以优化用二分求得。然后我们可以从各点向这些连续区间连边!这就很像线段树的查询区间了,把这些区间在线段树上维护,那么从每个城市出发,会向\(O(logn)\)个区间连边,边数被降到了\(O(nlogn)\)。再跑\(dij\)就能稳过了。代码...写不出来,因为不会动态开点线段树(,先鸽了。