8.3省选模拟总结

这次爆零了…~~(>_<)~~

第一题 百团大战
题目大意:
给定n个点,速度v(主角最大移动速度,如1可以跑到2~v+1),ai.x和ai.t(表示第ai.x个点在时间ai.t时刻点权为1,其余时候为0,可能同一点有多个时刻有点权),若主角在某一时刻在某一位置停留,那么主角刻获得该点权。求:
1.当他初始时刻在0号点的情况下,所能获得的最大点权和
2.当他初始时刻可以在任一点时,所能获得的最大点权和

此题由于只会打10%的暴力,对拍分块时找不到错误…于是挂了..

这题我不打算打理论复杂度上的正解,只打算记录下考场上的一些决策技巧(正解举例:将限制t和x投影到坐标轴上,找出转移区域,由于是向下90°的范围,转切比雪夫后用扫描线即可,也可找t、x限制的不等式的公共解再打扫描线)因而我想说说我怎么把分块打过去的

显然,我们可以逆着来做,由时间在后的点的方案转移到前面的,对于合法的转移,必然有(设i到j,即ti>tj):|xi-xj|≤v*(ti-tj),那么对于绝对值我们可以拆开,(我只讲其中一个),xi-xj≤v*(ti-tj)→xi-v*ti≤xj-v*tj
对于这个,我们可以分块维护块内的方案按xi-v*ti从大到小的前缀最优方案,查询时,分xi大于xj和xi小于xj两部分查询,扫描 n 块时二分找到限制最紧的直接更新,对于xi自己那块,直接 n 扫描,然后再 n 更新自己那块的前缀最优方案
时间复杂度 为 nnlogn

看起来是过不了的,的确。。(我最慢耗时3600ms)
然而,了解分块的本质的,可以想到,分块其实就是局部记忆化,是一种能协调块内、块外操作耗时的暴力,既然它的本质是协调耗时,那么这里我们可以发现,块外操作要乘上logn,但块内并不用,所以我们可以调一下参数,将块数调小,使得其复杂度低一点,同时注意不要让块内的太大,这个比赛的时候想不到正解,就可以二分来找一下最有利的参数,使得你分块能更有效的跑过去。
此外(这个能减少1000ms),加上发现一个也挺有利的剪枝,当块内最大方案比已更新答案小,就不二分进去。(感觉数据比较难卡我们,还是可以的,能减少1000ms)
最终我们卡进了2000ms的大关!!!
最慢一个点跑了1756 ms(然而通过某些对数据类型的了解、记录重复计算的东西,总之就是各种恶心优化,可以优化到1561 ms,当然若是大牛肯定能更快的..但此处还是贴一个比较正常的…)

贴代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#define N 100002
using namespace std;
int n,v,s,ans0,ans1;
struct node{
    int x,y;
}a[N],b[N],c[N],d[N];
int f[N],h[N];
void init(){
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
        scanf("%d %d",&c[i].x,&c[i].y),d[i]=c[i];
    scanf("%d",&v);
    if (n==100000)s=160;
    else
    s=sqrt(n);
}
bool cmp(const node&a,const node&b){
    return a.x<b.x;
}
bool cmp2(const node&a,const node&b){
    return (a.x<b.x)||(a.x==b.x&&a.y<b.y);
}
bool cmp1(const node&a,const node&b){
    return a.y>b.y;
}
void pre(){
    static int ss;
    sort(c+1,c+n+1,cmp2);
    sort(d+1,d+n+1,cmp1);
    h[0]=0,h[1]=s;
    ss=n/s;
    for (int i=2;i<s;i++)h[i]=h[i-1]+ss;
    h[s]=n;
    for (int i=1;i<=n;i++)
        a[i].x=c[i].y*v+c[i].x,b[i].x=c[i].y*v-c[i].x;
    for (int i=1;i<=s;i++)
        sort(a+h[i-1]+1,a+h[i]+1,cmp),sort(b+h[i-1]+1,b+h[i]+1,cmp);
}
int get(int l,int r,int v,const node*a){
    static int m,rr,ll;
    ll=l,rr=r;
    while (l<=r){
        if (a[m=(l+r)/2].x<v)l=m+1;else r=m-1; 
    }
    while (a[l+1].x==v&&l<rr)l++;
    while (a[l].x<v)l++;
    if (ll>l)l++;
    if (l>rr)l--;
    return l;
}
int get1(int l,int r,int v,int v1,const node*a){
    static int m,rr,ll;
    ll=l,rr=r;
    while (l<=r){
        if (a[m=(l+r)/2].x<v||(a[m].x==v&&a[m].y<v1))l=m+1;else r=m-1; 
    }
    while (a[l].x<v||(a[l].x==v&&a[l].y<v1))l++;
    while (a[l].x>v||(a[l].x==v&&a[l].y>v1))l--;
    return l;
}
void work(){
    static int s1,z;
    for (int i=1;i<=n;i++){
        int u=get1(1,n,d[i].x,d[i].y,c);
        for (s1=1;!(h[s1]>=u);s1++);
        z=d[i].y*v+d[i].x;
        for (int j=1;j<s1;j++)
            if (a[h[j]].x>=z&&a[h[j-1]+1].y>f[u])
                f[u]=max(f[u],a[get(h[j-1]+1,h[j],z,a)].y);
        z=d[i].y*v-d[i].x;
        for (int j=s1+1;j<=s;j++)
            if (b[h[j]].x>=z&&b[h[j-1]+1].y>f[u])
                f[u]=max(f[u],b[get(h[j-1]+1,h[j],z,b)].y);
        for (int j=h[s1-1]+1;j<=h[s1];j++)
            if ((c[j].y-c[u].y)*v>=abs(c[j].x-c[u].x))
                f[u]=max(f[u],f[j]);
        f[u]++;
        for (int j=get(h[s1-1]+1,h[s1],c[u].y*v+c[u].x,a);j>h[s1-1];j--)
            a[j].y=max(a[j].y,f[u]);
        for (int j=get(h[s1-1]+1,h[s1],c[u].y*v-c[u].x,b);j>h[s1-1];j--)
            b[j].y=max(b[j].y,f[u]);
        ans1=max(ans1,f[u]);
    }
    for (int i=1;i<=n;i++)
        if (abs(c[i].x)<=v*c[i].y)ans0=max(ans0,f[i]);
}
void write(){
    printf("%d %d\n",ans0,ans1);
}
int main(){
    init();
    pre();
    work();
    write();
    return 0;
}

第二题 军训
题目大意
给定n个点,每个点有两个权值ai和bi,还有一个limit值,求一种方案,通过用若干个挡板将n个点隔开,定义每部分的maxai为块内aj的最大值,sumbi为块内bj的和,使得maxai之和≤limit的情况下,max(sumbi)最小,并输出max(sumbi)

考的时候一个紧张(最后一个小时)想到这是道水题,想都没想就打二分,但由于没仔细想判断直接贪心,就爆零了..

正如上述,我们可以二分答案,然后dp判断,设f[i]为做到i时的maxai之和,我们可以预处理每个点最前到第x个点时a[x~i-1]≤a[i]然后用线段树更新这一部分的maxai之和,注意去除当bl到bi之和大于二分值的方案,可用一个指针l来移动,最后f[n]≤limit为true

贴代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define N 20001
using namespace std;
int n,m,l,r,mid;
int a[N][2],f[N*4][3],b[N],d[N];
void init(){
    scanf("%d %d",&n,&m);
    for (int i=1;i<=n;i++){
        scanf("%d %d",&a[i][0],&a[i][1]),r+=a[i][1];
        l=max(a[i][1],l);
        while (d[0]&&a[d[d[0]]][0]<a[i][0])d[0]--;
        b[i]=d[d[0]];
        if (!b[i])b[i]=-1;
        d[++d[0]]=i;
    }
}
void down(int l,int r,int s){
    if (f[s][2]){
        f[s][1]=f[s][0]+f[s][2];
        if (l!=r)f[s+s][2]=f[s+s+1][2]=f[s][2];
        f[s][2]=0;
    }
}
void clear(int l,int r,int s,int ll){
    down(l,r,s);
    if (l==r){
        f[s][0]=f[s][1]=f[s][2]=0;
        return;
    }
    static int ss;
    if ((ss=(l+r)/2)>=ll)clear(l,ss,s+s,ll);
    else
        clear(ss+1,r,s+s+1,ll);
    f[s][0]=min(f[s+s][0],f[s+s+1][0]);
    f[s][1]=min(f[s+s+1][1],f[s+s][1]);
}
void ins(int l,int r,int s,int ll,int rr,int v){
    down(l,r,s);
    if (rr<l||r<ll)return;
    if (ll<=l&&r<=rr){
        f[s][2]=v;
        down(l,r,s);
        return;
    }
    ins(l,(l+r)/2,s+s,ll,rr,v),ins((l+r)/2+1,r,s+s+1,ll,rr,v);
    f[s][1]=min(f[s+s][1],f[s+s+1][1]);
}
void inse(int l,int r,int s,int ll,int v){
    down(l,r,s);
    if (l==r){
        f[s][0]=f[s][1]=v,f[s][2]=0;
        return;
    }
    static int ss;
    if ((ss=(l+r)/2)>=ll)
    inse(l,ss,s+s,ll,v);else inse(ss+1,r,s+s+1,ll,v);
    f[s][0]=min(f[s+s][0],f[s+s+1][0]);
    f[s][1]=min(f[s+s][1],f[s+s+1][1]);
}
int get(int l,int r,int s,int ll,int rr){
    down(l,r,s);
    if (r<ll||rr<l)return 1000000000;
    if (ll<=l&&r<=rr)return f[s][1];
    return min(get(l,(l+r)/2,s+s,ll,rr),get((l+r)/2+1,r,s+s+1,ll,rr));
}
bool jian(int x){
    static int s1,l,y;
    static long long s,ss;
    s=l=0;
    clear(1,n+1,1,n+1);
    clear(1,n+1,1,1);
    for (int i=1;i<=n;i++){
        s+=a[i][1];
        while (s>x)clear(1,n+1,1,++l),s-=a[l][1];
        if ((y=max(l,b[i]+1))<=i)
        ins(1,n+1,1,y,i,a[i][0]);
        inse(1,n+1,1,i+1,ss=get(1,n+1,1,l+1,i));
    }
    for (l+1;l<n;l++)clear(1,n+1,1,l+1);
    return get(1,n+1,1,n+1,n+1)<=m;
}
void work(){
    static int min;
    min=l;
    while (l<=r){
        if (!jian(mid=(l+r)/2))l=mid+1;else r=mid-1;
    }
    while (l>min&&jian(l-1))l--;
    while (!jian(l))l++;
}
void write(){
    printf("%d",l);
}
int main(){
    init();
    work();
    write();
    return 0;
}

最后一题 选课
你真的认为选课是那么容易的事吗?HYSBZ的ZY同志告诉你,原来选课也会让人产生一种想要回到火星的感觉。假设你的一周有n天,那么ZY编写的选课系统就会给你n堂课。但是该系统不允许在星期i和星期i+1的时候选第i堂课,也不允许你在星期n和星期一的时候选第n堂课。然后连你自己也搞不清哪种选课方案合法,哪种选课不合法了。你只想知道,你到底有多少种合法的选课方案。

这个我连暴力都没时间打…

由于专业性较强
我把关键证明附上:
(转题解和网上的)
90%算法:容斥原理,假设有 1 堂课所在的天数是错误的情况数是 W(1),
则每个错误的课程 i 可以放在 i 或 i+1 天,剩下的选课方法数是(n-1)!。
所以 w(1)=C(n,1)2(n-1)!,这一步很容易想到,有点坑的是 2 个以上的
情况,假设有 k 堂课所在的天数是错误的情况数是 W(K),总共 n 堂课分别记为 1,2,…n,它们可放的天数可以表示为(1,2)(2,3)(3,4)…(n,1)
现在我们把括号去掉即得到一个数列 1,2,2,3,3,4,….n,1,现在
我们从里面取出 K 个数,分别表示 k 堂课所在的天数,现在只要求出满足
这个条件的取法数就可以了。
定理:假定数n个顶点沿一圆周排列,则从其中选取k个不相邻顶点的方法数是n/k*C(n-k-1,k-1)
证明:http://wlzy.aynu.edu.cn/sx/wlkc/zhsx/text/chapter01/break/part4/answer/ti27.htm

证明:对于任意一个顶点A,先取A,然后再从不和A相邻的n-3个其他顶点中取k-1个不相邻顶点,显然可得到符合定理要求的组合,这个组合的个数为C((n-3)-(k-1)+1,k-1)=C(n-k-1,k-1)。一共有n个顶点,而且在每个组合中有k个元素,即可完成证明。

那么W(K)=(n-k)!*C(2n-k-1,k-1)*2n/k

有少数人会问为什么要乘(n-k)!,因为我们只选了K堂错的课,剩余(n-k)堂课可以随意选。

W(K)确定以后就是最裸的容斥原理了。

在实现细节的时候,我们可以发现我们可以事先预处理好 n!,因此对于每组数据的时间复杂度为 O(n),即总时间复杂度为 O(nq),空间复杂度为 O(n) 但是在实现细节的时候,若有人把时间复杂度写成 O(nlog2n*q)的话,就只有 60 分了。

贴代码(其中有线性求逆元及其阶乘的部分)

#include<iostream>
#include<cstdio>
#include<algorithm>
#define N 200001
#define MOD 1000000007
int n,ans,nn;
int ni[N],a[N],ani[N];
void pre(){
    ani[1]=ani[0]=ni[0]=ni[1]=1;
    for (int i=2;i<N;i++)
        ni[i]=(MOD-(long long)MOD/i*ni[MOD%i]%MOD)%MOD;
    a[0]=a[1]=1;
    for (int i=2;i<N;i++)
        ani[i]=(long long)ani[i-1]*ni[i]%MOD,a[i]=(long long)i*a[i-1]%MOD;
}
int C(int x,int y){
    if (!y)return 1;
    return (long long)a[x]*ani[y]%MOD*ani[x-y]%MOD;
}
void work(){
    while (scanf("%d",&n)!=EOF){
        if (n==1)printf("0\n");else{
            ans=0;
            nn=n+n;
            for (int x=n;x;x--)
                ans=(((long long)a[n-x]*C(nn-x-1,x-1)%MOD*2*n%MOD*ni[x]%MOD-ans)%MOD+MOD)%MOD;
            printf("%d\n",(a[n]-ans+MOD)%MOD);
        }
    }
}
int main(){
    pre();
    work();
    return 0;
}
  1. 100%算法:然而,这道题是有递推公式的,不过这种方法偏难,就作为大
    家的课外讨论好了。(该部分分是我为了防 AK 而新加的,但是如果有人 AK
    了那我也没办法)
    这样可以使得回答询问的时间复杂度为 O(1),总时间复杂度为 O(n+q),空
    间复杂度为 O(n)。
    贴标程代码:

    for i := 5 to 100000 do
    begin
    extended_gcd(i-2,mo);
    if odd(i) then
    f[i] := (((f[i-1]*i) mod mo) + (((((f[i-2]*i) mod mo) + 4) * (((x mod mo)+mo) mod mo)) mod mo)) mod mo
    else f[i] := (((f[i-1]*i) mod mo) + (((((f[i-2]*i) mod mo) - 4) * (((x mod mo)+mo) mod mo)) mod mo)) mod mo;
    end;
    while not eof do
    begin
    readln(n);
    writeln(f[n]);
    end;

    注:这道题所用到的知识点据说是错排公式的扩展,叫做限位排列。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值