APIO2015简要题解

  最近老师把apio的题目拿出来了,然后由于我实在是菜,分数还没三位数......

 ----------------我是分割线

1.巴厘岛的雕塑

N个数,分成连续的A-B个组,让每个组的和或起来最小,求最小值。

对于Task1 n<=100

  由于涉及到位运算,所以很容易想到按二进制位来做。要让答案最小,显然要从二进制高位到低位判断,能取0就取0。

  所以我们考虑一个n^3 dp  用 f[i][j] 表示在当前的值之下,前i个分j组能否符合条件,用x表示当前判断的数字。

  f[i][j]|=f[k][j-1] (k=[0,i-1] , s[k+1..i] or x=x)

  很显然如果满足第k+1到第i个数的和或上x等于x,那么这个数字和没有一个二进制位比x大。

  那么就先把所有位设成1,从高位开始,把它设成0,进行dp。如果在f[n][A..B]中有一个true,那么就可以分,不然就把这一位改回1。这样一直做下去就可以得到答案。

复杂度n^3logAi

对于Task 2 n<=2000, A=1

  n的范围扩大了,但是A=1很关键,这意味着我不考虑分组是否大等A了,只要<=B即可(显然这样我们就要让分组最小)。

  那么我们改良一下dp,用f[i]表示前i个数,满足条件的情况下,至少分几组。

  f[i]=min(f[j])+1;(j=[0,i-1],s[j+1..i] or x=x)

  最后的答案和B比较一下即可,这样就dp变成了n^2了,其他过程不变。

复杂度n^2logAi。

附很丑的代码

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘) f=-1;ch=getchar();}
    while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-‘0‘; ch=getchar();}
    return x*f;
}

bool f[105][105];
int f2[2005];
int n;
ll ans;
ll s[2005];
int a,b;

bool check(ll x)
{
    memset(f,0,sizeof(f));
    f[0][0]=1;
    for(int i=1;i<=n;i++)
        for(int k=1;k<=i;k++)
            for(int j=0;j<i;j++)
            {
                if(((s[i]-s[j])|x)==x)
                    f[i][k]|=f[j][k-1];
            }
    for(int i=a;i<=b;i++) if(f[n][i]) return true;
    return false;
}

bool check2(ll x)
{
    f2[0]=0;ll nown=0;
    for(int i=1;i<=n;i++)
    {
        f2[i]=n+1;
        for(int j=0;j<i;j++)
        {
            if(((s[i]-s[j])|x)==x) f2[i]=min(f2[i],f2[j]+1);
        }
    }
    if(f2[n]<=b) return true;
    return false;
}

int main()
{
    freopen("sculpture.in","r",stdin);
    freopen("sculpture.out","w",stdout);
    n=read();a=read();b=read();
    for(int i=1;i<=n;i++)
        s[i]=read();
    for(int i=1;i<=n;i++) s[i]+=s[i-1];
    if(n<=100)
    {
        ans=(ll)1<<50;--ans;
        for(int i=49;i>=0;--i)
        {
            ans-=(ll)1<<i;
            if(!check(ans)) ans+=(ll)1<<i;
        }
    }
    else
    {
        ans=(ll)1<<50;--ans;
        for(int i=49;i>=0;--i)
        {
            ans-=(ll)1<<i;
            if(!check2(ans)) ans+=(ll)1<<i;
        }
    }
    printf("%I64d\n",ans);
    return 0;
}

2.雅加达的摩天楼

有n个点m只doge(0-m-1),每只doge给定初始位置和跳跃力,每次跳跃,位置为b,跳跃力为p的doge可以跳到b+p或者b-p。

现在doge0有信息要传到doge1 求最少的跳跃数。n,m<=30000

  网上有其他的最短路的做法,但是我们仔细分析分析,是可以暴力的.....

  设一个状态(b,p)表示现在信息传到了在b点跳跃力为p的doge上。从出题人卡代码的角度上,显然要状态数最多,只要加上了判重,那么状态数就最多是

  30000+2*15000+3*10000+....

  也就是说每个跳跃力状态数最多只有n种,但是对于跳跃力p,每个能增加的状态数辆只有n/p....

  那么这样数量最多的时候, doge跳跃力从1,2,2,3,3,3,一直到k,  k*(k-1)/2=m,状态数最多n*k,大约七百多万....

  所以直接bfs,到达一个没到过的点时候把那里的doge全部入队,加上哈希判重即可。(map跑太慢了,听了一个大佬的建议手写了一个在这道题理论上o(1)的哈希表...)

附上好丑好丑的代码

#include<iostream>
#include<cstdio>
#include<queue>
#define ll long long
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘) f=-1;ch=getchar();}
    while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-‘0‘; ch=getchar();}
    return x*f;
}

int n,m,cnt=0,cnt2=0;
int head[30005];
bool has[30005];

int front[9875322];
int hx[7300001];
int next[7300001];

struct edge{
    int m,next;
}e[30005];
int q[3][7300001];
int top,tail;

inline void push(int b,int p,int f)
{
    q[0][++top]=b;
    q[1][top]=p;
    q[2][top]=f;
}

inline void ins(int x,int y)
{
    e[++cnt].next=head[x];
    head[x]=cnt;
    e[cnt].m=y;
}

void rel(int num,int f)//释放所有doge
{
    has[num]=1;
    for(int i=head[num];i;i=e[i].next)
        push(num,e[i].m,f);
}

inline void insw(int h,int b)
{
    next[++cnt2]=front[h];
    front[h]=cnt2;
    hx[cnt2]=b;
}

bool check(int hash,int b)
{
    for(int i=front[hash];i;i=next[i])
        if(hx[i]==b) return false;
    insw(hash,b);
    return true;
}

inline void get(int&b,int&p,int&f)
{
    b=q[0][++tail];
    p=q[1][tail];
    f=q[2][tail];
}

inline void add(int b,int p,int f)
{
    int hash=(b<<15)+p;hash%=9875321;
    if(check(hash,b))
    {
        push(b,p,f);
        if(!has[b]) rel(b,f);
    }
}

int main()
{
    freopen("skyscraper.in","r",stdin);
    freopen("skyscraper.out","w",stdout);
    n=read();m=read();int from,to;
    for(register int i=1;i<=m;++i)
    {
        q[0][i]=read();q[1][i]=read();
        ins(q[0][i],q[1][i]);
    }from=q[0][1];to=q[0][2];
    rel(from,0);
    if(from==to) return 0*puts("0");
    register int b,p,f;
    while(top!=tail)
    {
        get(b,p,f);
        if(b+p==to||b-p==to) {printf("%d\n",f+1);return 0;}
        if(b>=p)
            add(b-p,p,f+1);
        if(b+p<n)
            add(b+p,p,f+1);
    }
    puts("-1");
    return 0;
}

3.巴邻旁之桥

从前有条河,河旁住着人....

有n个人,每个人有要从一个坐标到另一个坐标,这些位置可能在河的两边。现在你要在河的某些位置修建垂直于河的k座桥,让所有人的路径长之和最短。

对于Task 1  k=1,n<=100000

  不过河的人直接统计答案。

剩下的人,很显然,只修一座桥的情况下,一定要修在坐标的中位数那里。(设桥修在x,每个坐标产生的距离为|pos-x|,脑补一下就知道了)。

对于Task 2 k=2 n<=100000

  

  这下预算高了一点 要修两座桥了,对于每一个人(x,y),肯定走离x,y的中点(x+y)/2更近的桥。

  那么一定会存在一个分割点,使得左边的人走左边的桥,右边的人走右边的桥,距离和最短。这样的话,两边的桥就按照Task1修在中位数就可以了。

  那么就要考虑枚举分割点。从一段中删掉或者插入一个点,我们发现由于每次都选在中位数,实际上产生影响的只有被操作的那个点。所以我们用两个权值线段树(或者平衡树),维护左右两段的中位数即可啦。

  复杂度nlogn

  附上最丑的代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘) f=-1;ch=getchar();}
    while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-‘0‘; ch=getchar();}
    return x*f;
}

int T1[800005],T2[800005];

inline ll myabs(ll x)
{
    return x<0?-x:x;
}

struct gg{
    int l,r,mid,idl,idr;
}home[200005];

int n,cnt=0;
char c1,c2;
ll s[200005];
ll ext=0,ans=0,ans2=0,minn,num;

int solve1()
{
    int a,b;
    for(int i=1;i<=n;i++)
    {
        scanf("%c",&c1);a=read();
        scanf("%c",&c2);b=read();
        if(c1==c2)
            ext+=myabs(a-b);
        else
        {
            s[++cnt]=a;
            s[++cnt]=b;
            ++ext;
        }
    }
    sort(s+1,s+cnt+1);
    ll mid=s[(cnt+1)/2];
    for(int i=1;i<=cnt;i++)
        ans+=myabs(mid-s[i]);
    printf("%lld",ans+ext);
    return 0;
}

ll query(int*T,int x,int l,int r,int rk)
{
    if(l==r) return s[l];
    if(T[x<<1]>=rk) return query(T,x<<1,l,(l+r)>>1,rk);
    else return query(T,x*2+1,(l+r)/2+1,r,rk-T[x<<1]);
}

void ins(int*T,int x,int p,int l,int r,int ad)
{
    if(l!=r)
    {
        int mid=(l+r)>>1;
        if(x<=mid) ins(T,x,p<<1,l,mid,ad);
        else ins(T,x,(p<<1)+1,mid+1,r,ad);
    }
    T[p]+=ad;
}

bool cmp(gg x,gg y){return x.mid<y.mid;}

int main()
{
    freopen("bridge.in","r",stdin);
    freopen("bridge.out","w",stdout);
    int type=read();n=read();
    if(type==1) return 0*solve1();
    int a,b;
    for(int i=1;i<=n;i++)
    {
        scanf("%c",&c1);a=read();
        scanf("%c",&c2);b=read();
        //cout<<c1<<" "<<c2<<endl;
        if(c1==c2)
            ext+=myabs(a-b);
        else
        {
            home[++cnt].l=min(a,b);
            home[cnt].r=max(a,b);
            home[cnt].mid=(a+b)/2;
            s[++num]=a;
            s[++num]=b;
            ++ext;
        }
    }
    sort(s+1,s+num+1);
    for(int i=1;i<=cnt;i++)
    {
        home[i].idl=lower_bound(s+1,s+num+1,home[i].l)-s;
        home[i].idr=lower_bound(s+1,s+num+1,home[i].r)-s;
    }
    sort(home+1,home+cnt+1,cmp);
    if(cnt<=2)
    {
        ans=0;
        for(int i=1;i<=cnt;i++) ans+=home[i].r-home[i].l;
        printf("%lld\n",ans+ext);return 0;
    }
    for(int i=1;i<=cnt;i++)
          ins(T2,home[i].idl,1,1,num,1),ins(T2,home[i].idr,1,1,num,1);
    ans=ans2=0;
    ll pos=query(T2,1,1,num,cnt+1);
    for(int i=1;i<=cnt;i++)
        ans2+=myabs((ll)home[i].r-pos)+myabs((ll)home[i].l-pos);
    minn=ans+ans2;
    ll pos1,pos2=pos;
    for(int i=1;i<=cnt;i++)
    {
        ans2-=myabs((ll)home[i].l-pos2)+myabs((ll)home[i].r-pos2);
        ins(T1,home[i].idl,1,1,num,1);
        ins(T2,home[i].idl,1,1,num,-1);
        ins(T1,home[i].idr,1,1,num,1);
        ins(T2,home[i].idr,1,1,num,-1);
        pos1=query(T1,1,1,num,i);
        pos2=query(T2,1,1,num,cnt-i+1);
        ans+=myabs((ll)home[i].l-pos1)+myabs((ll)home[i].r-pos1);
        if(ans+ans2<minn) minn=ans+ans2;
    }
    printf("%I64d\n",minn+ext);
    return 0;
}

好的就是这样,理解了题目做法后都就不难写了.....

有什么错误请您指出 谢谢咯 ??

时间: 2024-12-22 17:03:19

APIO2015简要题解的相关文章

AGC025简要题解

AGC025简要题解 B RGB Coloring 一道简单题,枚举即可. C Interval Game 考虑可以进行的操作只有两种,即左拉和右拉,连续进行两次相同的操作是没有用的. 左拉时肯定会选择右端点尽量小的,右拉选择左端点尽量大的,所以排序之后贪心即可. D Choosing Points 首先证明对于所有\(d\),假设让两个不能同时选的点之间连一条边,那么结果是一张二分图. \(d\)是奇数可以黑白染色,\(d\)是偶数的时候,显然连边的两点在同一个颜色内.那么我们可以只考虑这个颜

月考简要题解

模拟赛简要题解 一下题目均可在loj上找到 10178. 「一本通 5.5 例 4」旅行问题 简单题,将n扩大到2 * n,单调队列即可,注意正反向. #include<iostream> #include<cstring> #include<cmath> #include<cstdio> #include<algorithm> using namespace std; typedef long long ll; const int N=2000

JXOI2018简要题解

JXOI2018简要题解 T1 排序问题 题意 九条可怜是一个热爱思考的女孩子. 九条可怜最近正在研究各种排序的性质,她发现了一种很有趣的排序方法: Gobo sort ! Gobo sort 的算法描述大致如下: 假设我们要对一个大小为 \(n\) 的数列 \(a\) 排序. 等概率随机生成一个大小为 \(n\) 的排列 \(p\) . 构造一个大小为 \(n\) 的数列 \(b\) 满足 \(b_i=a_{p_i}\) ,检查 \(b\) 是否有序,如果 \(b\) 已经有序了就结束算法,并

BJOI2018简要题解

BJOI2018简要题解 D1T1 二进制 题意 pupil 发现对于一个十进制数,无论怎么将其的数字重新排列,均不影响其是不是 \(3\) 的倍数.他想研究对于二进制,是否也有类似的性质. 于是他生成了一个长为 \(n\) 的二进制串,希望你对于这个二进制串的一个子区间,能求出其有多少位置不同的连续子串,满足在重新排列后(可包含前导 \(0\))是一个 \(3\) 的倍数.两个位置不同的子区间指开始位置不同或结束位置不同. 由于他想尝试尽量多的情况,他有时会修改串中的一个位置,并且会进行多次询

杂题记录及简要题解(三)

以下是大概 5 月初开始做的一些题.以前的简要题解都是骗人的.这次真的是简要题解了(大雾 相对之前改良了一下题目名称的格式. 2017 计蒜之道 初赛 - 腾讯狼人杀 二分答案 \(x\) 后原问题变为检验是否存在选取方案 \((V, E)(|V| = k)\) 使得 \(\sum_\limits{e \in E} w_e - xk \cdot (2n- k)\).式子可以写成 \(\sum_\limits{e \in E} w_e + \frac{k(k - 1)}{2} \cdot 2x -

【简要题解】Hihocoder 重复旋律1-8简要题解

[简要题解]Hihocoder 重复旋律1-8简要题解 编号 名称标签 难度 1403 后缀数组一·重复旋律 Lv.4 1407 后缀数组二·重复旋律2 Lv.4 1415 后缀数组三·重复旋律3 Lv.4 1419 后缀数组四·重复旋律4 Lv.4 1445 后缀自动机二·重复旋律5 Lv.4 1449 后缀自动机三·重复旋律6 Lv.4 1457 后缀自动机四·重复旋律7 Lv.1 1465 后缀自动机五·重复旋律8 Lv.1 1466 后缀自动机六·重复旋律9 Lv.1 后缀数组 思路简单

《信奥一本通》提高版—简要题解

<信奥一本通>提高版-简要题解 贪心 活动安排: 按右端点排序,因为越早结束越好. 然后从1扫到n,每次比较当前位置线段的左端点是否大于上一个选的线段的右端点.如果大于,那么ans++,将上一个选的线段的右端点更新为当前线段的右端点:如果小于,那什么都不用做.因为选上一条一定比选当前这一条更优(结束时间更早). 种树 按右端点排序,对于每个区间的需求,从右端往左端扫,只要没种到树的就种,ans++. 因为要使前面的需求尽量与后面的需求重叠,从而使树的数量最少 喷水装置 观察+画图发现对于一个圆

【题解】CF616(Div 2)简要题解

[题解]CF616(Div 2)简要题解 A 分类讨论 若数码和是奇数 若最后一个数是奇数:找到从前往后第一个奇数数位删掉 若最后一个数是偶数:不断删除最后一个数直到这个剩下的数是奇数,由于之前删掉的数都是偶数所以对数码和\(\mod 2\)不会有影响.再做一遍第一个算法即可. 若数码和是偶数 若最后一个数是奇数:符合条件 若最后一个数是偶数:不断删除最后一个数直到奇数.由于之前删掉的数都是偶数所以对数码和\(\mod 2\)不会有影响,直接输出即可. 最后要判断一下前导零. B 可以发现若有合

Codeforces Round #483 (Div. 1) 简要题解

来自FallDream的博客,未经允许,请勿转载,谢谢. 为了证明一下我又来更新了,写一篇简要的题解吧. 这场比赛好像有点神奇,E题莫名是道原题,导致有很多选手直接过掉了(Claris 表演24s过题).然而D题比E题要难一些,分还少. A. Finite or not? 先把\(\frac{p}{q}\)约成最简分数,然后就是要判断是否\(q\)的所有质因数都是\(b\)的质因数. 每次取\(g=gcd(b,q)\),并尽可能的让\(q\)除\(g\),最后判断\(q\)是否是1即可. 还有一