当前位置:首页 > 技术 > 正文内容

JOISC 2018 解题报告

访客 技术 2026年5月27日 3

A. 公路建设

给定 n 个节点,每个节点初始有权值 w_i。进行 n-1 次操作,每次连接一条边 u←v,其中 u 与节点 1 连通,v 与节点 1 不连通。要求计算从 1 到 u 路径上权值的逆序对数量,然后将路径上的权值统一更新为 v 的权值。

使用 Link-Cut Tree (LCT) 解决此问题。每次连接边后执行 access(v) 操作,此时树上每条实链的颜色相同。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+1,W=1e9;
int n,w[MAXN]; ll ans;
struct FenwickTree {
    int tr[MAXN]; ll s;
    vector <int> p;
    inline void add(int x,int v) { for(p.push_back(x);x<=n;x+=x&-x) tr[x]+=v; }
    inline ll qry(int x) { for(s=0;x;x&=x-1) s+=tr[x]; return s; }
    inline void clr() { for(int x:p) for(;x<=n;x+=x&-x) tr[x]=0; p.clear(); }
}    T;
namespace LCT {
int fa[MAXN],ch[MAXN][2],col[MAXN],cov[MAXN],siz[MAXN];
inline void psu(int p) { siz[p]=siz[ch[p][0]]+siz[ch[p][1]]+1; }
inline void adt(int x,int c) { if(x) cov[x]=col[x]=c; }
inline void psd(int x) { if(cov[x]) adt(ch[x][0],cov[x]),adt(ch[x][1],cov[x]),cov[x]=0; }
inline bool nrt(int p) { return ch[fa[p]][0]==p||ch[fa[p]][1]==p; }
inline int son(int x) { return ch[fa[x]][1]==x; }
inline void upd(int p) { if(nrt(p)) upd(fa[p]); psd(p); }
inline void rot(int p) {
    int u=fa[p],v=fa[u],k=son(p);
    if(nrt(u)) ch[v][son(u)]=p;
    fa[ch[u][k]=ch[p][k^1]]=u,fa[fa[ch[p][k^1]=u]=p]=v;
    psu(p),psu(u);
}
inline void splay(int p) {
    for(upd(p);nrt(p);rot(p)) if(nrt(fa[p])) rot(son(p)==son(fa[p])?fa[p]:p);
}
inline int access(int p) {
    int u=0;
    for(ans=0,T.clr();p;u=p,p=fa[p]) {
        splay(p);
        int tmp=1+siz[ch[p][0]];
        ans+=1ll*tmp*T.qry(col[p]-1);
        T.add(col[p],tmp);
        ch[p][1]=u,psu(p);
    }
    return u;
}
}
using namespace LCT;
signed main() {
    scanf("%d",&n);
    vector <int> vals;
    for(int i=1;i<=n;++i) scanf("%d",&w[i]),vals.push_back(w[i]);
    sort(vals.begin(),vals.end()),vals.erase(unique(vals.begin(),vals.end()),vals.end());
    for(int i=1;i<=n;++i) w[i]=lower_bound(vals.begin(),vals.end(),w[i])-vals.begin()+1;
    col[1]=w[1],siz[1]=1;
    for(int i=1;i<n;++i) {
        int u,v;
        scanf("%d%d",&u,&v);
        adt(access(u),w[v]);
        printf("%lld\n",ans);
        fa[v]=u,col[v]=w[v],siz[v]=1;
    }
    return 0;
}

C. 栅栏

在一个 n×m 的网格中选择若干格子进行着色并指定方向(上、下、左、右),要求同行或同列的格子方向相对。求满足条件的方案数。

设 dp[i][j] 表示 i×j 网格上的答案。考虑第 i 行的情况:

  • 第 i 行无黑色格子:从 dp[i-1][j] 转移
  • 第 i 行有一个黑色格子:
    • 若对应列只有一个黑色格子:从 4×j×dp[i-1][j-1] 转移
    • 若对应列有两个黑色格子:从 (i-1)×j×dp[i-2][j] 转移
  • 第 i 行有两个黑色格子:从 (j×(j-1)/2)×dp[i-1][j-2] 转移
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=3001,MOD=1e9+7;
int dp[MAXN][MAXN];
signed main() {
    int n,m;
    scanf("%lld%lld",&n,&m);
    for(int i=0;i<=max(n,m);++i) dp[0][i]=dp[i][0]=1;
    for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) {
        dp[i][j]=(dp[i-1][j]+4*j*dp[i-1][j-1])%MOD;
        if(i>1) dp[i][j]=(dp[i][j]+(i-1)*j%MOD*dp[i-2][j-1]%MOD)%MOD;
        if(j>1) dp[i][j]=(dp[i][j]+j*(j-1)/2%MOD*dp[i-1][j-2]%MOD)%MOD;
    }
    printf("%lld\n",(dp[n][m]+MOD-1)%MOD);
    return 0;
}

D. 苦行僧

给定 n 和 m,求长度为 n 的排列 p_i 中满足 Σ[i=1 to n-1][p_i > p_{i+1}] = m-1 的排列数量。

使用容斥原理,钦定 k 个位置必须满足 p_i > p_{i+1}。将连续的 > 看作一段,整个序列被分为 n-k 段,方案数为 n!/(Πs_i!)。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5,MOD=1e9+7;
ll fac[MAXN],ifac[MAXN];
inline ll ksm(ll a,ll b=MOD-2,ll p=MOD) {
    ll ret=1;
    for(;b;a=a*a%p,b=b>>1) if(b&1) ret=ret*a%p;
    return ret;
}
inline ll binom(int n,int m) { return fac[n]*ifac[m]%MOD*ifac[n-m]%MOD; }
signed main() {
    int n,m;
    scanf("%d%d",&n,&m),--m;
    for(int i=fac[0]=ifac[0]=1;i<=n+1;++i) ifac[i]=ksm(fac[i]=fac[i-1]*i%MOD);
    ll ans=0;
    for(int i=1;i<=n-m;++i) ans=(ans+MOD+((n-m-i)%2?-1:1)*ksm(i,n)*binom(n+1,m+i+1)%MOD)%MOD;
    printf("%lld\n",ans);
    return 0;
}

E. 道路服务

给定一棵 n 个节点的树,增加 k 条无向边,最小化所有点对间最短距离之和。

较优解是将 k 条边连成菊花形状。使用模拟退火算法,每次更改一条边的端点。评估函数采用 Σ[1≤i≤n]dist(rt,i),其中 rt 是附加边的中心点。

F. 最差记者3

数轴上有 n+1 个人,编号 0~n,第 i 个人初始在 -i 位置。每轮第 0 个人向右移动 1,其他人如果与前一个人距离 < d_i,则移动到前一个人左边。q 次询问 t 秒后 [l,r] 区间内的人数。

每个人运动具有周期性,即每隔 k_i 轮移动 k_i 步。二分 t 秒后位置 ≤ r 和 ≤ l-1 的人数即可。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=5e5+1;
int d[MAXN],k[MAXN];
signed main() {
    int n,q;
    scanf("%lld%lld",&n,&q);
    for(int i=1;i<=n;++i) scanf("%lld",&d[i]);
    d[0]=k[0]=1;
    for(int i=1;i<=n;++i) k[i]=(d[i]+k[i-1]-1)/k[i-1]*k[i-1];
    auto query=[&](int t,int x) {
        int l=0,r=n,res=-1;
        while(l<=r) {
            int mid=(l+r)>>1;
            if((t/k[mid])*k[mid]-mid>=x) res=mid,l=mid+1;
            else r=mid-1;
        }
        return res+1;
    };
    while(q--) {
        int t,l,r;
        scanf("%lld%lld%lld",&t,&l,&r);
        printf("%lld\n",query(t,l)-query(t,r+1));
    }
    return 0;
}

G. 航线地图

通信题。A.cpp 输入一张 n 个点的图 G,输出点数不超过 n+12 的图 H。交互器任意打乱 H 中点和边的标号得到 H'。B.cpp 输入 H',还原 G。

使用 12 个额外点表示原图的 n 个点原始标号,采用二进制分组。用 10 个点 v_0~v_9 表示,v_i 连接第 i 个二进制位为 1 的所有点。

H. 比太郎的派对

给定一张 n 个点 m 条边的有向图,q 次询问 x_i 并给定 k_i 个点,求 1~n 中除这些点外到 x_i 的最长距离。

采用根号分治策略,若 k_i ≤ √n,只关心最短的 √n 条路径;k_i > √n 的情况暴力处理。

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5,inf=2e9;
vector <array<int,2>> P[MAXN];
vector <int> G[MAXN];
int dis[MAXN],mark[MAXN],mp[MAXN];
signed main() {
    int n,m,q;
    scanf("%d%d%d",&n,&m,&q);
    int B=sqrt(n);
    for(int i=1;i<=m;++i) {
        int u,v;
        scanf("%d%d",&u,&v);
        G[v].push_back(u);
    }
    fill(mp+1,mp+n+1,-inf);
    for(int u=1;u<=n;++u) {
        P[u].push_back({0,u});
        for(int v:G[u]) for(auto I:P[v]) mp[I[1]]=max(mp[I[1]],I[0]+1);
        for(int v:G[u]) {
            int ed=P[u].size();
            for(auto I:P[v]) if(mp[I[1]]==I[0]+1) {
                P[u].push_back({mp[I[1]],I[1]}),mp[I[1]]=-inf;
            }
            inplace_merge(P[u].begin(),P[u].begin()+ed,P[u].end(),greater<array<int,2>>());
            if((int)P[u].size()>B) P[u].resize(B);
        }
    }
    for(int T=1;T<=q;++T) {
        int t,y;
        scanf("%d%d",&t,&y);
        for(int i=0,j;i<y;++i) scanf("%d",&j),mark[j]=T;
        for(auto p:P[t]) {
            if(mark[p[1]]<T) {
                printf("%d\n",p[0]);
                goto ok;
            }
        }
        if((int)P[t].size()<B) puts("-1");
        else {
            fill(dis+1,dis+t+1,-inf),dis[t]=0;
            for(int u=t;u;--u) {
                for(int v:G[u]) dis[v]=max(dis[v],dis[u]+1);
            }
            int ans=-1;
            for(int i=1;i<=t;++i) if(mark[i]<T) ans=max(ans,dis[i]);
            printf("%d\n",ans<0?-1:ans);    
        }
        ok:;
    }
    return 0;
}

I. 安全门

定义"好"括号序列:能够将一个可空子串的左右括号反转后得到合法括号序列。给定含空白位置的括号串,求填入左右括号使其成为"好"序列的方案数。

令左括号为 1,右括号为 -1。设翻转区间为 (l,r]。定义翻转:整个序列变为 s_n s_{n-1}...s_1 然后反转括号。

J. 糖果

给定长度为 n 的数轴,每个位置有权值。对于 k=1~⌈n/2⌉,求选 k 个不相邻位置的最大权值和。

观察解的形态,用 0/1 表示位置是否被选。序列能划分为 0,1 交替出现的段且两端为 0。每次操作是将某段 01 取反,然后合并两边段。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=2e5+5,INF=1e18;
int pre[MAXN],suf[MAXN],inq[MAXN],c[MAXN];
signed main() {
    int n;
    scanf("%lld",&n);
    for(int i=1;i<=n;++i) scanf("%lld",&c[i]),pre[i]=i-1,suf[i]=i+1,inq[i]=1;
    c[0]=c[n+1]=-INF;
    auto merge=[&](int u) {
        pre[suf[u]]=pre[u];
        suf[pre[u]]=suf[u];
        inq[u]=0;
    };
    priority_queue <array<int,2>> Q;
    for(int i=1;i<=n;++i) Q.push({c[i],i});
    int ans=0;
    for(int i=1;i<=(n+1)/2;++i) {
        while(!inq[Q.top()[1]]) Q.pop();
        int w=Q.top()[0],p=Q.top()[1]; Q.pop();
        printf("%lld\n",ans+=w);
        Q.push({c[p]=c[pre[p]]+c[suf[p]]-w,p});
        merge(pre[p]),merge(suf[p]);
    }
    return 0;
}

K. 图书馆

交互器有一个 1~n 的排列 p,每次可询问 S 里的元素在 p 上构成几个连续段。在 20000 次询问内还原排列。

采用增量法。先用 2n 次询问求出排列的一个端点。然后动态维护已知前缀 P,对于未确定集合 Q,二分成两个集合,询问即可确定前缀的下一个元素。

L. 野猪

给一张 n 个点 m 条边的无向图,定义合法路径为不会连续经过同一条边。动态维护长度为 k 的序列 v_1~v_k,支持 q 次单点修改,每次求顺次经过的最短合法路径。

观察到任何一段 v_i→v_{i+1} 路径的第一条边 s 和最后一条边 t 只有四种可能情况。暴力 Dijkstra 求出边间的全源最短路,预处理四条路径。使用动态规划记录路径类型,修改用矩阵乘法维护。

#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int MAXN=2005,MAXM=4005,MAXL=1e5+1,INF=1e18;
int n,m,q,len,ecnt=0,head[MAXN];
struct Edge {
    int v,w,lst;
}    G[MAXM];
inline void AddEdge(int u,int v,int w) { G[ecnt]={v,w,head[u]},head[u]=ecnt++; }
struct Node {
    int s,t,c;
};
int dist[MAXM][MAXM];
bool vis[MAXM];
inline Node GetPath(int u,int v,int lst,int nxt) {
    Node P={-1,-1,INF};
    for(int s=head[u];s!=-1;s=G[s].lst) {
        for(int t=head[v];t!=-1;t=G[t].lst) {
            if((lst==-1||s!=lst)&&(nxt==-1||(t^1)!=nxt)&&dist[s][t^1]<P.c) {
                P={s,t^1,dist[s][t^1]}; 
            }
        }
    }
    return P;
} 
Node Info[MAXN][MAXN][4];
/* u[0],v[0]: shortest path
 * u[1],v[1]: u[1]!=u[0],v[1]!=v[0]
 * u[2],v[2]: u[2]!=u[0],v[2]!=v[1]
 * u[3],v[3]: u[3]!=u[1],v[3]!=v[0]
 */
struct Matrix {
    int f[4][4];
    Matrix() { memset(f,0x3f,sizeof(f)); }
    inline int* operator [](int i) { return f[i]; }
    inline friend Matrix operator *(Matrix u,Matrix v) {
        Matrix w;
        for(int k:{0,1,2,3}) for(int i:{0,1,2,3}) for(int j:{0,1,2,3}) {
            w[i][j]=min(w[i][j],u[i][k]+v[k][j]);
        }
        return w;
    }
};
int ord[MAXL];
inline Matrix GetMatrix(int idx) {
    Matrix m;
    int a=ord[idx-1],b=ord[idx],c=ord[idx+1];
    for(int u:{0,1,2,3}) for(int v:{0,1,2,3}) {
        if(~Info[a][b][u].t&&~Info[b][c][v].s&&(Info[a][b][u].t^Info[b][c][v].s)!=1) {
            m[u][v]=Info[b][c][v].c;
        }
    }
    return m;
}
Matrix sum[MAXL<<2];
inline void Build(int l=2,int r=len-1,int pos=1) {
    if(l==r) { sum[pos]=GetMatrix(l); return ; }
    int mid=(l+r)>>1;
    Build(l,mid,pos<<1),Build(mid+1,r,pos<<1|1);
    sum[pos]=sum[pos<<1]*sum[pos<<1|1];
}
inline void Modify(int u,int l=2,int r=len-1,int pos=1) {
    if(u<l||u>r) return ;
    if(l==r) { sum[pos]=GetMatrix(l); return ; }
    int mid=(l+r)>>1;
    if(u<=mid) Modify(u,l,mid,pos<<1);
    else Modify(u,mid+1,r,pos<<1|1);
    sum[pos]=sum[pos<<1]*sum[pos<<1|1];
}
signed main() {
    scanf("%lld%lld%lld%lld",&n,&m,&q,&len);
    memset(head,-1,sizeof(head));
    for(int i=1;i<=m;++i) {
        int u,v,w;
        scanf("%lld%lld%lld",&u,&v,&w);
        AddEdge(u,v,w),AddEdge(v,u,w);
    }
    memset(dist,0x3f,sizeof(dist));
    for(int st=0;st<ecnt;++st) {
        priority_queue <array<int,2>,vector<array<int,2>>,greater<array<int,2>>> Q;
        Q.push({dist[st][st]=G[st].w,st});
        memset(vis,false,sizeof(vis));
        while(!Q.empty()) {
            int e=Q.top()[1]; Q.pop();
            if(vis[e]) continue; vis[e]=true;
            for(int f=head[G[e].v];f!=-1;f=G[f].lst) {
                if(dist[st][f]>dist[st][e]+G[f].w&&(e^f)!=1) {
                    Q.push({dist[st][f]=dist[st][e]+G[f].w,f});
                }
            }
        }
    }
    for(int u=1;u<=n;++u) for(int v=1;v<=n;++v) {
        Info[u][v][0]=GetPath(u,v,-1,-1);
        Info[u][v][1]=GetPath(u,v,Info[u][v][0].s,Info[u][v][0].t);
        Info[u][v][2]=GetPath(u,v,Info[u][v][0].s,Info[u][v][1].t);
        Info[u][v][3]=GetPath(u,v,Info[u][v][1].s,Info[u][v][0].t);
    }
    for(int i=1;i<=len;++i) scanf("%lld",&ord[i]);
    if(len==2) {
        while(q--) {
            int x,y;
            scanf("%lld%lld",&x,&y);
            ord[x]=y;
            printf("%lld\n",Info[ord[1]][ord[2]][0].c);
        }
    } else {
        Build();
        while(q--) {
            int x,y;
            scanf("%lld%lld",&x,&y);
            ord[x]=y;
            Modify(x-1),Modify(x),Modify(x+1);
            int ans=INF;
            for(int i:{0,1,2,3}) for(int j:{0,1,2,3}) {
                ans=min(ans,Info[ord[1]][ord[2]][i].c+sum[1][i][j]);
            }
            printf("%lld\n",ans<INF?ans:-1);
        }
    }
    return 0;
}

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。