[APIO2016]

2016的题貌似是韩国棒子出的,好丧啊.... 看了题解还想了好久......

-------------------------------------------------

A.Boat

有n个数,每个数字可取[li,ri]内的任意整数si,但是要求对于任意i<j,都有si<sj,求方案数  n<=500,l,r<=10^9

题解:首先离散,然后不同区间的方案数很好转移,我们考虑相同区间的方案数,发现是一个差分了多次的数列,如果区间长度是l,选m个这样的区间,那么方案数是11111...差分m次后的第l项。然后我们可以发现这个其实是一个组合数,等于C(m,m+l)。我们用f[i][j]表示第i个选第j个区间的方案数,然后我们把f数组前缀和之后推出公式,

f[i][j]=∑C(i-i‘-1,i-i‘+l-1) * f[i‘-1][j-1]

复杂度n^3

#include<iostream>
#include<cstdio>
#include<algorithm>
#define mod 1000000007
#define MAXN 1000
#define int 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 tot=-1,n,L[2*MAXN+5];
int l2[MAXN*2+5],l[MAXN+5],r[MAXN+5];
int f[MAXN+5][MAXN*2+5];
int inv[MAXN+5],p[MAXN+5];

main()
{
    p[0]=inv[0]=p[1]=inv[1]=1;
    for(int i=2;i<=MAXN;i++)
    {
        p[i]=1LL*p[i-1]*i%mod;
        inv[i]=1LL*(mod-mod/i)*inv[mod%i]%mod;
    }
    //for(int i=2;i<=MAXN;i++) inv[i]=1LL*inv[i]*inv[i-1]%mod;
    n=read();
    for(int i=1;i<=n;i++)
    {
        l[i]=l2[i*2-1]=read();
        r[i]=l2[i<<1]=read();l2[i<<1]+=1;
    }
    sort(l2+1,l2+n*2+1);
    for(int j=1;j<=n*2;j++)if(l2[j]!=l2[j-1])
        l2[++tot]=l2[j];  tot++;
    for(int i=1;i<tot;i++) L[i]=l2[i]-l2[i-1];
    for(int i=1;i<=n;i++)
    {
        l[i]=upper_bound(l2,l2+tot,l[i])-l2;
        r[i]=upper_bound(l2,l2+tot,r[i])-l2;
    //  cout<<l[i]<<" "<<r[i]<<endl;
    }
    for(int i=0;i<tot;i++) f[0][i]=1;
    for(int i=1;i<=n;i++)
    {
        f[i][0]=1;
        for(int j=l[i];j<=r[i];j++)
        {
            f[i][j]=(long long)L[j]*f[i-1][j-1]%mod;//cout<<f[i][j]<<endl;
            int now=1;long long c=L[j]-1;
            for(int k=i-1;k;--k)
                if(l[k]<=j&&j<=r[k])
                {
                    now++;
                    c=c*(long long)(L[j]+now-2)%mod*inv[now]%mod;
                    if(!c)break;
                    f[i][j]=(f[i][j]+(long long)f[k-1][j-1]*c)%mod;
                }
        }
        for(int j=1;j<tot;j++)
            f[i][j]=((long long)f[i][j]+f[i-1][j]+f[i][j-1]-f[i-1][j-1]+mod)%mod;
    //  for(int j=1;j<tot;j++)
        //  cout<<i<<" "<<j<<" "<<f[i][j]<<endl;
    }
    cout<<(long long)(f[n][tot-1]-1+mod)%mod;
    return 0;
}

B.给定一棵n个非叶节点,m个叶节点的树,有边权,定义修改边权的费用为前后边权的差的绝对值,你要让所有叶节点到根节点的距离相同,但又不能把边权改成负数,求最小费用。

n,m<=300000

题解:我们用f[i][j]表示第i个点,子树中的叶节点到它的距离都是j的最小费用,那么f[i][0]=∑Wjk   (jk都在子树i中)。

很显然,f函数是一个下凸的函数,并且存在一些拐点,拐点前后斜率变化是1,但是拐点可以重在某一个点上。只有一个叶节点时,拐点有两个,且都为于0,凸壳形状像一个绝对值函数。

所以我们只要知道所有拐点,就可以知道这个函数啦。

我们考虑向一个子树添加边时候的影响,由于w可以无限增大,在一定大小后只修改这一条边一定最优,斜率肯定都是1,所以对于斜率大于1的部分我们都可以舍去,即弹掉所有原来斜率大等0的拐点。

这样一次合并我们实际上只把它向右平移了一下。需要删除和合并操作,所以写一个可并堆就好啦。

复杂度(n+m)log(n+m)

#include<iostream>
#include<cstdio>
#define MN 600000
#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,fa[MN+5],in[MN+5];
ll w[MN+5],ans=0;

struct Heap{
    Heap *l,*r;
    ll p;int d;
    Heap(ll _p):p(_p),l(0),r(0),d(1){};
    inline friend int dis(Heap*x){return x?x->d:0;}
    friend Heap* Merge(Heap*x,Heap*y)
    {
        if(!x) return y;if(!y) return x;
        if(x->p<y->p) swap(x,y);
        x->r=Merge(x->r,y);
        if(dis(x->r)>dis(x->l)) swap(x->l,x->r);
        x->d=dis(x->r)+1;

        return x;
    }
}*s[MN+5],*a,*b;

int main()
{
    n=read();m=read();n+=m;
    for(int i=2;i<=n;i++)
        {++in[fa[i]=read()];ans+=(w[i]=read());}
    for(int i=n;i>1;i--)
    {
        if(!s[i]) s[i]=Merge(new Heap(0),new Heap(0));
        for(int j=1;j<in[i];j++)
            s[i]=Merge(s[i]->l,s[i]->r);
        a=new Heap(s[i]->p+w[i]);s[i]=Merge(s[i]->l,s[i]->r);
        b=new Heap(s[i]->p+w[i]);s[i]=Merge(s[i]->l,s[i]->r);
        s[fa[i]]=Merge(s[fa[i]],Merge(s[i],Merge(a,b)));
    }
    int top=0;
    while(s[1])w[++top]=s[1]->p,s[1]=Merge(s[1]->l,s[1]->r);w[top+1]=0;
    for(int i=top;m;m--,i--) ans-=(w[i]-w[i+1])*m;
    printf("%lld\n",ans);
    return 0;
}

C.Gap

给定一个长度为n的严格递增数列,你每次可以询问一个数字区间的最大值,最小值,求最大差分。n<=100000

题解:对于subtask1,询问次数不超过(n+1)/2,我们枚举左右节点查,然后缩短这个区间就好啦。

对于subtask2,询问的区间含有k个数时费用是k+1,要让费用不超过3n。我们先求最大值x和最小值y,显然答案不会低于(y-x)/(n-1),所以我们把数字分块,每块内不存在答案,都询问一次就行了。

#include "gap.h"
#include<algorithm>
#include<iostream>
using namespace std;
#define ll long long
#define INF 1000000000000000000LL
ll s[200005];
ll ans=0;
int cnt=0;

ll solve(int x)
{
    ll l,r;
    MinMax(0,INF,s+1,s+x);cnt=2;int i=2,j=x-1;
    for(l=s[1]+1,r=s[x]-1;i<=j;l=s[i++]+1,r=s[j--]-1)
    {
        MinMax(l,r,s+i,s+j);
    }
    for(int i=2;i<=x;i++)
        ans=max(ans,s[i]-s[i-1]);
    return ans;
}

ll findGap(int T, int N)
{
    if(T==1) return solve(N);
    if(N==1) return 0;
    MinMax(0,INF,s+1,s+2);cnt=2;
    ll p=(s[2]-s[1]-1)/(N-1)+1;
    for(ll i=s[1]+1;i<=s[2]-1;i+=p)
    {
        MinMax(i,min(i+p-1,s[2]-1),s+cnt+1,s+cnt+2);
        if(s[cnt+1]>0)cnt+=2;
    }
    sort(s+1,s+cnt+1);
    for(int i=2;i<=cnt;i++)
        ans=max(ans,s[i]-s[i-1]);
    return ans;
}
时间: 2024-10-15 18:41:30

[APIO2016]的相关文章

CTSC2016&amp;&amp;APIO2016爆零记

CTSC2016&&APIO2016爆零记 前言:人生第一次写(骗)爆(访)零(问)记(量),心里还是有点小激动呢.不过由于本文作者语文水平低下,如果发现用词不当或只会记流水账,请谅解. CTSC Day0: CCF大发慈悲让我们入住了5星级酒店昆(百)泰酒店. 不过也有一点不兹磁的地方,就是酒店离学校大概要步行半个小时,对于我这种懒人大概是天大的灾难吧(雾).于是这次比赛就强行变成了CTSC&&APIO远足营. 第一次看见70块钱一瓶零下八度不结冰的矿泉水,然而家穷喝不起

【APIO2016】Gap

[APIO2016]Gap 交互题 对于30%的数据,直接从s=0,t=1e18开始,每次让s=mn+1,t=mx-1,每一次可以得到两个,可以在(n+1)/2的时间内得到 对于其他数据,我们先求出最大最小值,求出len=(mx-mn)/(n-1),这相当与是一个"平均数",如果我将区间分为若干段长度为len的区间,只记录每个区间内的最大值和最小值,这样被漏算的那些点,他们最多有len-1的贡献,而这个还达不到平均数,所以肯定不会影响答案,所以就可以在3*n以内求出序列有用的部分了.

计数难题5:[APIO2016]划艇

计数难题5:[APIO2016]划艇 标签(空格分隔): 计数难题题选 题目大意: 给定\(n\)个区间\([l_i,r_i]\). 你可以从第\(i\)个区间中选出一个整数元素\(a_i\in [l_i,r_i]\),也可以不选. 要求选出的元素按标号顺序排列后构成一个严格单调递增序列. 求至少选出一个元素的合法方案数. 答案对\(10^9+7\)取模. 数据范围:\(n\leq 500\) , \(l_i\leq r_i \leq 10^9\) . 题解 可以想到把区间离散化. 设\(f_{

BZOJ 4584 【APIO2016】 赛艇

题目链接:赛艇 讲道理好好的Boat为啥要翻译成赛艇呢--题面中不也是划艇么-- 这道题考虑一下dp.由于划艇数量过于庞大,所以肯定不能直接记录到dp状态中.所以一个想法就是把数量离散化,然后把每个学校的数量在哪一段内记录下来.也就是说\(f_{i,j,k}\)表示前\(i\)个学校,第\(i\)所学校派出的划艇数量在区间\(j\)内,并且区间\(j\)内共有\(k\)个学校的方案数.然后分类讨论一下转移: 当\(k\ne 1\)时,有: \begin{aligned}f_{i,j,k} &=\

BZOJ 4584 [Apio2016]赛艇 ——动态规划

Subtask 1 直接$N^2$ $DP$,就可以了 Subtask 2 用$f[i][j]$表示当前位置为$i$,结束元素为$j$的方案数. Subtask 3 看下面 Subtask 4 首先可以枚举一段序列选择同一个重叠的区间,然后一些可以不选,如果选的话要求上升. 然后很容易得到方程,离散化之后就可以$N^4$直接做了. 然后并不会优化,看了题解. 想了想写不出来. 抄代码啦! #include <map> #include <ctime> #include <cm

[BZOJ4584][Apio2016]赛艇

试题描述 在首尔城中,汉江横贯东西.在汉江的北岸,从西向东星星点点地分布着个划艇学校,编号依次为到.每个学校都拥有若干艘划艇.同一所学校的所有划艇颜色相同,不同的学校的划艇颜色互不相同.颜色相同的划艇被认为是一样的.每个学校可以选择派出一些划艇参加节日的庆典,也可以选择不派出任何划艇参加.如果编号为的学校选择派出划艇参加庆典,那么,派出的划艇数量可以在Ai至Bi之间任意选择(Ai<=Bi).值得注意的是,编号为i的学校如果选择派出划艇参加庆典,那么它派出的划艇数量必须大于任意一所编号小于它的学校

CTSC2016&amp;&amp;APIO2016滚粗记&amp;&amp;酱油记&amp;&amp;游记(持续更新)

挖一波坑 #include <cstdio> using namespace std; int main(){ puts("转载请注明出处:http://www.cnblogs.com/wangyurzee7/"); puts("谢谢您的配合"); puts("by wangyurzee7"); return 0; } Day-1 昨天莫名其妙就感冒了... 在家养生了一天,看了一上午的球,下午鏼了几题51nod,拿钱基本无望了,收拾

UOJ #206. 【APIO2016】Gap

有 NN 个严格递增的非负整数 a1,a2,…,aNa1,a2,…,aN(0≤a1<a2<?<aN≤10180≤a1<a2<?<aN≤1018).你需要找出 ai+1−aiai+1−ai(0≤i≤N−10≤i≤N−1)里的最大的值. 你的程序不能直接读入这个整数序列,但是你可以通过给定的函数来查询该序列的信息.关于查询函数的细节,请根据你所使用的语言,参考下面的实现细节部分. 你需要实现一个函数,该函数返回 ai+1−aiai+1−ai(0≤i≤N−10≤i≤N−1)中

CTSC2016&amp;&amp;APIO2016游记

4.30 下午衡中放假,我们因为比赛的缘故提前到中午12:00放假 然后我爸爸说要来接我,直到下午两点多他才到,然后衡中宿舍的楼管阿姨死活不给我开门 莫名其妙的等到了三点多快四点的时候我才跟实验班的一起走 奔波了很久在火车站附近找到了一家宾馆入住 背着爸爸偷偷买了根冰棍,感觉非常的兹磁啊 晚上一直在写 榴莲 这道题的标程,忙了很久才把数据做好 快到十点的时候才把题目传到cojs上,然后草草冲了个澡就去睡觉了 5.1 早上早早的就上了火车,车上一直在跟小伙伴们宣传我在cojs上出的题目 到了北京以