2014 ACM-ICPC World Finals

链接

A.Baggage

开幕明哥题。

观察样例,大胆猜测最小步数是恰好 n 步。

考虑规定只使用 2 个额外格子,容易手模出 nn4 的递归:

0: __BABAsBABA
1: ABBABAsB__A
2: ABBA__sBBAA
   ...
2: ABBAs__BBAA
3: A__AsBBBBAA
4: AAAAsBBBB__

对于 n7 的情况,直接 dfs 出一组需要只需要 2 个空格子的方案即可。特别的 n=3 时需要的空格子为 3,直接手模一下解就好了。

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define N 210
using namespace std;
vector<pair<int,int>>res;
int a[N];
bool dfs(int l,int r,int n,int st)
{
    if(!st)
    {
        for(int i=0;i<n;i++) if(a[l+i]!=1) return false;
        for(int i=0;i<n;i++) if(a[l+n+i]!=2) return false;
        return true;
    }
    for(int j=l;j<r;j++) if(!a[j] && !a[j+1])
        for(int i=r-1;i>=l;i--) if(a[i] && a[i+1])
        {
            res.emplace_back(i,j);
            swap(a[i],a[j]),swap(a[i+1],a[j+1]);
            if(dfs(l,r,n,st-1)) return true;
            swap(a[i],a[j]),swap(a[i+1],a[j+1]);
            res.pop_back();
        }
    return false;
}
void make(int x,int y){swap(a[x],a[y]),swap(a[x+1],a[y+1]),res.emplace_back(x,y);}
void solve(int l,int r)
{
    int n=(r-l+1-2)/2;
    if(n<=7)
    {
        if(!dfs(l,r,n,n)) throw;
        return;
    }
    make(r-2,l),make(l+4,r-2),solve(l+4,r-4);
    make(l+1,r-5),make(r-1,l+1);
}
int main()
{
    int n;scanf("%d",&n);
    for(int i=2;i<=2*n+1;i++) a[i]=(i-1)%2+1;
    if(n==3) res.assign({{3,0},{6,3},{4,-2}});
    else solve(0,2*n+1);
    // for(int i=0;i<=2*n+2;i++) printf("%d ",a[i]);puts("");
    for(auto p:res) printf("%d to %d\n",p.first-1,p.second-1);
    return 0;
}

B. Buffed Buffet

考虑把离散的和连续的背包分开做。

对于离散的部分,直接用背包可以做到 O(w3) 然而不太能过。考虑将所有重量为 x 的物品放一起,根据贪心同种重量的方案中只有前 nx 大是有用的,所以这部分复杂度 O(m2logn),而且跑不太满。

考虑连续部分。首先枚举还有多少的重量剩下了。假设我们能每 ϵ 地枚举,那么我们一定贪心地选最大的。所以最后一定存在一个值 x,所有函数都取了比 x 大的那段。直接二分 x,这部分复杂度 O(nmlogϵ1)

总复杂度 O(m2n+nmlogϵ1)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define N 10010
#define ll long long
#define fi first
#define se second
#define db double
#define inf 1e18
using namespace std;
char opt[3];
ll f[N];int a[N],b[N],n;
vector<pair<int,int>>g[N];
bool check(db x,db y)
{
    db ans=0;
    for(int i=1;i<=n;i++) if(a[i]>x){if(!b[i]) return true;ans+=(a[i]-x)/b[i];}
    return ans>=y;
}
db calc(int t,db x){return x*a[t]-x*x*b[t]/2;}
int main()
{
    int m;scanf("%d%d",&n,&m);
    int tt=0;
    for(int i=1;i<=n;i++)
    {
        scanf("%s",&opt);
        int x,t,d;
        if(opt[0]=='D') scanf("%d%d%d",&x,&t,&d),g[x].emplace_back(t,d);
        else ++tt,scanf("%d%d",&a[tt],&b[tt]);
    }
    n=tt;
    memset(f,0xcf,sizeof(f));
    f[0]=0;
    for(int i=1;i<=m;i++) if(!g[i].empty())
    {
        int x=m/i;
        vector<int>tmp;
        for(auto v:g[i])
            for(int j=0;j<x;j++) tmp.push_back(v.fi-v.se*j);
        sort(tmp.begin(),tmp.end(),greater<int>());
        tmp.resize(x);
        for(int v:tmp)
            for(int j=m;j>=i;j--) f[j]=max(f[j],f[j-i]+v);
    }
    // for(int i=0;i<=m;i++) printf("%lld ",f[i]);puts("");
    db ans=-1e18;
    if(!n && f[m]<=-1e17){puts("impossible");return 0;}
    for(int i=0;i<=m;i++) if(f[i]>-1e17)
    {
        db l=-1e8,r=1e8;int v=m-i;
        for(int _=0;_<=60;_++)
        {
            db mid=(l+r)/2;
            if(check(mid,v)) l=mid;
            else r=mid;
        }
        db res=f[i]+v*l;
        for(int i=1;i<=n;i++) if(a[i]>l && b[i])
        {
            db k=(a[i]-l)/b[i];
            res+=calc(i,k)-l*k;
        }
        ans=max(ans,res);
    }
    printf("%.7lf",ans);
    return 0;
}

E. Maze Reduction

一种很显然的思路是,用一种哈希函数将每个点出发的可能情况表示出来。具体来说,用 ft,i,j 表示 t 步内 i 到达状况的哈希函数,其中到达 i 的边是第 j 条,容易证明任意可以区分的两个点可以在 n 步内区分。

一种可行的哈希函数是从 j 开始绕一圈对应点哈希的序列哈希值,乘上一个特殊 base 再加上当前点度数。

考虑最后怎么区分两个没有初始边的点:容易发现只要两点出发的哈希序列只要循环同构即可。一种可行方式就是取一个点所有循环同构序列中哈希值最小的那个,直接以此为分辨依据即可。

复杂度 O(n4)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<map>
#include<algorithm>
#define N 110
using namespace std;
vector<int>g[N];int d[N];
struct hs{
    int x,y;
    hs(int x=0):x(x),y(x){}
    hs(int x,int y):x(x),y(y){}
    bool operator <(const hs a)const{return x==a.x?y<a.y:x<a.x;}
};
const int mo1=998244353,mo2=1000000007;
hs operator +(hs a,hs b){return hs((a.x+b.x)%mo1,(a.y+b.y)%mo2);}
hs operator *(hs a,hs b){return hs(1ll*a.x*b.x%mo1,1ll*a.y*b.y%mo2);}
hs operator -(hs a,hs b){return hs((a.x-b.x+mo1)%mo1,(a.y-b.y+mo2)%mo2);}
const hs b(233),b2(2333);
hs f[N][N],p[N][N];
int id[N][N];
map<hs,vector<int>>mp;
vector<vector<int>>ans;
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&d[i]);g[i].resize(d[i]);
        for(int j=0;j<d[i];j++) scanf("%d",&g[i][j]),id[g[i][j]][i]=j;
    }
    for(int i=1;i<=n;i++)
        for(int j=0;j<d[i];j++) f[i][j]=d[i];
    for(int _=1;_<=n;_++)
    {
        for(int i=1;i<=n;i++)
            for(int j=0;j<d[i];j++) p[i][j]=f[i][j],f[i][j]=0;
        for(int i=1;i<=n;i++)
            for(int j=0;j<d[i];j++)
            {
                for(int k=0;k<d[i];k++)
                {
                    int v=g[i][(j+k)%d[i]];
                    f[i][j]=f[i][j]*b+p[v][id[i][v]];
                }
                f[i][j]=f[i][j]*b2+d[i];
            }
    }
    for(int i=1;i<=n;i++)
    {
        vector<hs>res;
        for(int j=0;j<d[i];j++)
        {
            int v=g[i][j];
            res.push_back(f[v][id[i][v]]);
        }
        hs w(mo2);
        for(int j=0;j<d[i];j++)
        {
            hs u;
            for(int k=0;k<d[i];k++) u=u*b+res[(j+k)%d[i]];
            u=u*b2+d[i];
            w=min(w,u);
        }
        mp[w].push_back(i);
    }
    for(auto x:mp) if(x.second.size()!=1) ans.push_back(x.second);
    if(ans.empty()){puts("none");return 0;}
    sort(ans.begin(),ans.end());
    for(auto x:ans){for(int v:x) printf("%d ",v);puts("");}
    return 0;
}

G. Metal Processing Plant

考虑枚举 A 集合最大值 wA,二分 B 集合最大值 wB。如果一条边 w>wA,那么它两端的点不能同时在 A 集合,w>wB 同理。可以发现这是一个 2-sat 模型,单次处理 O(n2)

直接处理总复杂度 O(n4logn)。当然用双指针可以优化到 O(n5logn),有点卡常。

考虑优化。不妨钦定 wAwB,那么将 >wA 的边取出,如果这些边不能构成一个二分图,那么 wA 更小的边一定无解。在合法条件下,如果 wA+1wA 过程中加入的所有边都在同一个连通块中,那么不会影响原有答案,直接跳过即可。

这样每次执行必然链接一个连通块,复杂度 O(n3logn)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define N 410
#define fi first
#define se second
using namespace std;
int r[N][N],w[N*N],n;
int dfn[N],low[N],idx;
int ton[N],scc[N],tp,sc;
vector<int>g[N];
bool in[N];
void dfs(int u)
{
    dfn[u]=low[u]=++idx;
    in[u]=true;ton[++tp]=u;
    for(int v:g[u])
    if(!dfn[v]) dfs(v),low[u]=min(low[u],low[v]);
    else if(in[v]) low[u]=min(low[u],dfn[v]);
    if(dfn[u]==low[u])
    {
        ++sc;int v=0;
        while(v!=u) v=ton[tp--],in[v]=false,scc[v]=sc;
    }
}
void add1(int x,int y){g[x].push_back(y+n),g[y].push_back(x+n);}
void add2(int x,int y){g[x+n].push_back(y),g[y+n].push_back(x);}
void pre_work(int wa,int wb)
{
    for(int i=1;i<=2*n;i++) g[i].clear(),ton[i]=scc[i]=dfn[i]=low[i]=0;
    idx=tp=sc=0;
    for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++)
        {
            if(r[i][j]>wa) add1(i,j);
            if(r[i][j]>wb) add2(i,j);
        }
}
bool check(int wa,int wb)
{
    pre_work(wa,wb);
    for(int i=1;i<=2*n;i++) if(!dfn[i]) dfs(i);
    for(int i=1;i<=n;i++) if(scc[i]==scc[i+n]) return false;
    return true;
}
int ans=1e9;
void solve(int x)
{
    int l=0,r=x,res=-1;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(check(x,mid)) r=mid-1,res=mid;
        else l=mid+1;
    }
    if(res!=-1) ans=min(ans,w[x]+w[res]);
}
int f[N];
vector<pair<int,int>>rd[N*N];
int find(int x){return x==f[x]?f[x]:(f[x]=find(f[x]));}
bool chk(int x,int y)
{
    if(find(x)==find(y) || find(x+n)==find(y+n)){solve(r[x][y]);printf("%d\n",ans);exit(0);}
    if(find(x)==find(y+n)) return false;
    f[find(x)]=find(y+n);f[find(x+n)]=find(y);
    return true;
}
int main()
{
    int m=0;scanf("%d",&n);
    for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++) scanf("%d",&r[i][j]),w[++m]=r[i][j];
    for(int i=1;i<=n*2;i++) f[i]=i;
    sort(w+1,w+m+1);
    m=unique(w+1,w+m+1)-w-1;
    for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++) r[i][j]=lower_bound(w+1,w+m+1,r[i][j])-w,rd[r[i][j]].emplace_back(i,j);
    // for(int i=1;i<=n;i++,puts(""))
    //     for(int j=i+1;j<=n;j++) printf("%d ",r[i][j]);
    for(int i=m;i;i--)
    {
        bool can=false;
        for(auto p:rd[i]) if(chk(p.fi,p.se)) can=true;
        if(can) solve(i);
    }
    solve(0);
    printf("%d\n",ans);
    return 0;
}

I. Sensor Network

经典老题。

考虑答案点集中距离最远的两个点 x,y,距离为 l,分别以 x,y 为圆心作半径为 l 的圆,因为其他点距离 x,y 都不超过 l,所以一定在两个圆相交区域内。

将圆的相交区域按 x,y 的连线划分为两块,那么同一块内的任意两点距离不超过 l。所以将区域内所有 d 的点对连线,最后构成一个二分图。而要求的就是二分图的独立子集,即求最大匹配。

直接跑匈牙利,复杂度 O(n5),显然根本跑不满。

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define N 110
#define ll long long
using namespace std;
int link[N],vis[N],T;
vector<int>g[N];
bool dfs(int u)
{
    vis[u]=T;
    for(int v:g[u]) if(!link[v] || (vis[link[v]]!=T && dfs(link[v]))){link[v]=u;return true;}
    return false;
}
struct node{
    int x,y;
    node(int x=0,int y=0):x(x),y(y){}
}p[N];
node operator -(node a,node b){return node(a.x-b.x,a.y-b.y);}
ll operator *(node a,node b){return 1ll*a.x*b.y-1ll*a.y*b.x;}
ll cross(node a,node o,node b){return (a-o)*(b-o);}
ll dis(node a,node b){return 1ll*(a.x-b.x)*(a.x-b.x)+1ll*(a.y-b.y)*(a.y-b.y);}
int n,d,ans=1;
int col[N],alink[N];vector<int>ag[N],aw={1};
void make(int x,int y)
{
    ll l=dis(p[x],p[y]);
    for(int i=1;i<=n;i++) link[i]=vis[i]=col[i]=0,g[i].clear();
    T=0;
    int res=2;
    for(int i=1;i<=n;i++) if(i!=x && i!=y && dis(p[i],p[x])<=l && dis(p[i],p[y])<=l)
    {
        if(cross(p[x],p[i],p[y])>=0) col[i]=1;
        else col[i]=2;
        ++res;
    }
    for(int i=1;i<=n;i++) if(col[i]==1)
        for(int j=1;j<=n;j++) if(col[j]==2 && dis(p[i],p[j])>l) g[i].push_back(j);
    for(int i=1;i<=n;i++) if(col[i]==1) ++T,res-=dfs(i);
    if(ans<res)
    {
        ans=res;
        aw={x,y};
        for(int i=1;i<=n;i++) if(col[i]) aw.push_back(i);
        for(int i=1;i<=n;i++) alink[i]=link[i],ag[i]=g[i];
    }
}
bool cut[N];
int main()
{
    scanf("%d%d",&n,&d);
    for(int i=1;i<=n;i++) scanf("%d%d",&p[i].x,&p[i].y);
    for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++) if(dis(p[i],p[j])<=1ll*d*d) make(i,j);
    printf("%d\n",ans);
    for(int i=1;i<=n;i++) g[i]=ag[i],vis[i]=0;
    T=0;
    for(int i=1;i<=n;i++) if(alink[i])
    {
        for(int j=1;j<=n;j++) link[j]=alink[j];
        ++T;
        if(dfs(link[i])) cut[alink[i]]=true;
        else cut[i]=true;
    }
    for(int i:aw) if(!cut[i]) printf("%d ",i);
    return 0;
}

K. Surveillance

首先把所有被完全包含的区间删去。假如题意是一条链,那么有一个很显然的贪心就是每次选当前被覆盖的区间中最靠后的区间,一种可行的实现方式就是从后往前,对于每个区间求出最后一个与当前区间有交的区间。这个可以双指针实现。

对于原题,首先将序列复制一份展开。我们钦定跨过 n 这个点后必须结束,对每个区间为开始都求出最小方案,那么最后在所有覆盖长度超过 n 的方案中选最小的即可。

复杂度 O(n+k)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 2000010
using namespace std;
int x[N],f[N],g[N];
int main()
{
    int n,k;scanf("%d%d",&n,&k);
    for(int i=1;i<=k;i++)
    {
        int l,r;scanf("%d%d",&l,&r);if(l>r) r+=n;
        ++r;
        if(x[l]<r) x[l]=r,x[l+n]=r+n;
    }
    int mx=0;
    for(int i=1;i<=n;i++) mx=max(mx,x[i]);
    for(int i=n+1;i<=n*2;i++) if(x[i]<=mx) x[i-n]=x[i]=0;else mx=x[i];
    for(int i=n+1;i<=n*2;i++) f[i]=i;
    int y=2*n;
    for(int i=n;i;i--) if(x[i])
    {
        for(y=min(y,x[i]);!x[y];y--);
        if(y==i){puts("impossible");return 0;}
        f[i]=f[y],g[i]=g[y]+1;
    }
    int ans=1e9;
    for(int i=1;i<=n;i++) if(f[i]-i>=n) ans=min(ans,g[i]);
    printf("%d\n",ans);
    return 0;
}
posted @   Flying2018  阅读(192)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示