JOI 2017 Final
链接
B. Semiexpress
显然,按照高速电车站划成若干段,每段中一定是贪心找到第一个无法到达的车站,在其中建设准高速电车站。
同样可以证明,对于每个新建车站的备选位置,每次最优策略一定是选增量最大的。容易证明选完之后新的备选位置一定不大于当前位置。
用堆维护,复杂度 \(O(nm\log n)\)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define N 100010
using namespace std;
int a,b,c,n,m,k,s[N];
typedef long long ll;
priority_queue<pair<int,int>>q;
ll T,t[N],p[N];
int calc(int i){return t[i]>T?0:min((T-t[i])/a+1,s[i+1]-p[i]);}
int main()
{
scanf("%d%d%d%d%d%d%lld",&n,&m,&k,&a,&b,&c,&T);
for(int i=1;i<=m;i++) scanf("%d",&s[i]);
s[m+1]=n+1,k-=m;
ll res=-1;
for(int i=1;i<=m;i++) t[i]=1ll*b*(s[i]-1),p[i]=s[i];
for(int i=1;i<=m;i++)
{
int x=calc(i);if(x<=0) continue;
res+=x,p[i]+=x,t[i]+=1ll*x*c;
q.emplace(calc(i),i);
}
while(k --> 0 && !q.empty())
{
int u=q.top().second,x=q.top().first;q.pop();
if(x<=0) break;
res+=x,p[u]+=x,t[u]+=1ll*x*c;
q.emplace(calc(u),u);
}
printf("%lld\n",res);
return 0;
}
C. The Kingdom of JOIOI
不妨假设最后的曲线是从右上角到左下角,并且左上角包含最小值,右下角包含最大值。通过翻转一定可以让最优解满足该条件。
二分答案 \(x\),贪心取每行不超过上一行且最大值 \(\leq \min+x\) 的前缀,然后检验另一边是否合法。
复杂度 \(O(n^2\log V)\)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 2010
using namespace std;
int a[N][N],n,m,mn=1e9,mx;
bool check(int x)
{
int pre=m;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=pre;j++) if(a[i][j]-x>mn){pre=j-1;break;}
for(int j=pre+1;j<=m;j++) if(a[i][j]+x<mx) return false;
}
return true;
}
int solve()
{
int l=0,r=mx-mn,res=0;
while(l<=r){int mid=(l+r)>>1;if(check(mid)) r=mid-1,res=mid;else l=mid+1;}
return res;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) scanf("%d",&a[i][j]),mx=max(mx,a[i][j]),mn=min(mn,a[i][j]);
int ans=1e9;
for(int _=0;_<=1;_++)
{
ans=min(ans,solve());
for(int i=1;i<=n;i++)
for(int j=1;j<=m/2;j++) swap(a[i][j],a[i][m-j+1]);
ans=min(ans,solve());
for(int i=1;i<=n/2;i++)
for(int j=1;j<=m;j++) swap(a[i][j],a[n-i+1][j]);
}
printf("%d\n",ans);
return 0;
}
D. Soccer
因为移动代价与控球相同,容易证明,一个球员至多只会控球踢球一次,否则直接控球一定更优。同样可以证明即使我们认为每个有球员的位置都有无数个“分身”,答案不会改变。
这样我们可以对建三张图,第一张代表控球,第二张代表准备在东西方向踢球,第三张代表准备在南北方向踢球。第一张到二三张连 \(B\) 边表示蓄力,第二三张合法边连 \(C\) 表示踢球。第二三张向第一张连该点到最近球员的距离乘 \(C\),表示需要有一个球员跑过来控球。第一张合法边连 \(A\) 表示运球。
跑 dijkstra 即可。复杂度 \(O(nm\log n)\)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N=510,M=100010,K=N*N*3;
typedef long long ll;
int A,B,C,n,m,k;
int f[N][N];
const int X[]={1,0,-1,0},Y[]={0,1,0,-1};
int id(int x,int y,int z){return x*(m+1)*3+y*3+z;}
ll dis[K];bool vis[K];
int nxt[K*4],to[K*4],head[K],cnt;ll w[K*4];
void add(int u,int v,ll w0){nxt[++cnt]=head[u];to[cnt]=v;w[cnt]=w0;head[u]=cnt;}
void dij(int s)
{
memset(dis,0x3f,sizeof(dis));
priority_queue<pair<ll,int>>q;
dis[s]=0,q.emplace(0,s);
while(!q.empty())
{
int u=q.top().second;q.pop();
if(vis[u]) continue;vis[u]=true;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(dis[v]>dis[u]+w[i]) dis[v]=dis[u]+w[i],q.emplace(-dis[v],v);
}
}
}
int main()
{
scanf("%d%d%d%d%d%d",&n,&m,&A,&B,&C,&k);
memset(f,-1,sizeof(f));
queue<pair<int,int>>q;
auto push=[&](int x,int y,int z){if(x>=0 && y>=0 && x<=n && y<=m && f[x][y]==-1) f[x][y]=z,q.push({x,y});};
int sx,sy,tx,ty;
for(int i=1,x,y;i<=k;i++)
{
scanf("%d%d",&x,&y);
if(i==1) sx=x,sy=y;
if(i==k) tx=x,ty=y;
push(x,y,0);
}
while(!q.empty())
{
auto p=q.front();q.pop();
for(int i=0;i<4;i++) push(p.first+X[i],p.second+Y[i],f[p.first][p.second]+1);
}
// for(int i=0;i<=n;i++,puts(""))
// for(int j=0;j<=m;j++) printf("%d ",f[i][j]);
for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++)
{
add(id(i,j,1),id(i,j,2),B),add(id(i,j,1),id(i,j,3),B);
add(id(i,j,2),id(i,j,1),1ll*f[i][j]*C),add(id(i,j,3),id(i,j,1),1ll*f[i][j]*C);
if(i<n) add(id(i,j,1),id(i+1,j,1),C),add(id(i,j,2),id(i+1,j,2),A);
if(i>0) add(id(i,j,1),id(i-1,j,1),C),add(id(i,j,2),id(i-1,j,2),A);
if(j<m) add(id(i,j,1),id(i,j+1,1),C),add(id(i,j,3),id(i,j+1,3),A);
if(j>0) add(id(i,j,1),id(i,j-1,1),C),add(id(i,j,3),id(i,j-1,3),A);
}
dij(id(sx,sy,1));
printf("%lld\n",dis[id(tx,ty,1)]);
return 0;
}
E. Rope
稍微手模一下可以发现,一根绳子可以被折成长度为 \(2\) 的绳子,当且仅当除了开头和结尾任意一段相同颜色连续段都是偶数。证明考虑反向构造:任意一次不在开头和结尾的复制都会导致原先边界的连续段翻倍。
这样可以推出的是:可以将整个序列划为若干长度为 \(2\) 的块,块内颜色相同。
枚举第一个块的奇偶性,可以得知其他块的信息。对于没有当前颜色的块,只需要取数量最多的那种颜色即可。
用一个桶存储。复杂度 \(O(n)\)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define N 1000010
using namespace std;
bool vis[N];
int mx,a[N],b[N],c[N],n;
vector<int>g[N];
void add(int x){if(x<1 || x>n || vis[x]) return;vis[x]=true;x=a[x],b[c[x]]--,c[x]++,b[c[x]]++;mx=max(mx,c[x]);}
void dec(int x){if(x<1 || x>n || !vis[x]) return;vis[x]=false;x=a[x],b[c[x]]--,c[x]--,b[c[x]]++;if(!b[mx]) mx--;}
int main()
{
int m;scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),g[a[i]].push_back(i),add(i);
for(int i=1;i<=m;i++)
{
for(int v:g[i]) dec(v),dec(v^1);
int res=mx;
for(int v:g[i]) add(v),add(v^1);
for(int v:g[i]) dec(v),dec(((v-1)^1)+1);
res=max(res,mx);
for(int v:g[i]) add(v),add(((v-1)^1)+1);
printf("%d\n",n-(int)g[i].size()-res);
}
return 0;
}
本文来自博客园,作者:Flying2018,转载请注明原文链接:https://www.cnblogs.com/Flying2018/p/JOI2017Final.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)