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;
}
posted @   Flying2018  阅读(90)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示