12.26模拟赛

T1:

其实非常简单。考试的时候复杂化了。考虑到了各种高级算法。。。。。

区间出现次数考虑前缀差分。考虑什么时候符合要求。

对应字符次数相同意味着左右端点的字符出现的相对次数相同。(即纵向对字符再差分)

所以对纵向差分的值做一个hash即可。

小trick搞定。

注意本题卡哈希。

(我用的分治。复杂度差一些)

 

T2:

 

方法众多。

观察复杂度是O(NK)或者O(NKlogN)的

 

因为有超车情况。。。

一个思路是,考虑d-t图像

 

 绿色的点是这一次的碰撞点。

怎么找?可以O(NK)所以每次碰撞可以O(N)来找。

该点一定在左凸包上!

然后反过来

左转:水平可见直线~!

这种特殊的半平面交,按照斜率排序,单调栈即可(一般的半平面交是封闭区间,按级角序排序)

凸包中间属于两侧直线交出的点就是碰撞点(斜率正负交界,易证只有一个)

#include<bits/stdc++.h>
#define reg register int
#define il inline
#define numb (ch^'0')
using namespace std;
typedef long long ll;
il void rd(int &x){
    char ch;x=0;bool fl=false;
    while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
namespace Miracle{
const int N=100000+5;
const int eps=0.0000001;
struct po{
    double x,y;
    bool friend operator <(po a,po b){
        if(fabs(a.x-b.x)<eps) return a.y>=b.y;
        return a.x<b.x;
    }
}sta[N];
int top1;
struct line{
    double k,b;
    int id;
    int die;
    bool friend operator <(line a,line b){
        if(fabs(a.k-b.k)<eps) return a.b>b.b;
        return a.k<b.k;
    }
}l[N],zhan[N];
int pos[N];
int top2;
int n,L,K;
po calc(line a,line b){//warning!!! pingxing !!
    po ret;
    ret.x=((double)b.b-(double)a.b)/((double)a.k-(double)b.k);
    ret.y=b.k*ret.x+b.b;
    return ret;
}
void sol(){
    top1=0;
    top2=0;
    int i=1;
    while(l[i].die) ++i;
    zhan[++top2]=l[i];++i;
    for(;i<=2*n;++i){
        while(i<=2*n&&fabs(l[i].k-l[i-1].k)<eps) ++i;
        while(i<=2*n&&l[i].die==1) ++i;
        if(i>2*n) break;
        while(top1&&calc(l[i],zhan[top2])<sta[top1]){
            --top1;
            --top2;
        }
        sta[++top1]=calc(l[i],zhan[top2]);
        zhan[++top2]=l[i];
    }
    //cout<<" top2 "<<top2<<endl;
    for(reg i=1;i<top2;++i){
    //    cout<<zhan[i].id<<endl;
        if(zhan[i].id<=n&&zhan[i+1].id>n){
            printf("%d %d\n",zhan[i].id,zhan[i+1].id-n);
            l[pos[zhan[i].id]].die=1;
            l[pos[zhan[i+1].id]].die=1;
            return;
        }
    }
    return;
}
int main(){
    rd(n);rd(L);rd(K);
    int v,x;
    for(reg i=1;i<=n;++i){
        rd(x);rd(v);
        l[i].id=i;
        l[i].k=-(double)1/(double)v;l[i].b=-(double)x+eps;
    }
    for(reg i=n+1;i<=n+n;++i){
        rd(x);rd(v);
        l[i].id=i;
        l[i].k=(double)1/(double)v;l[i].b=-((double)L/(double)v+(double)x);
    }
    sort(l+1,l+n*2+1);
    for(reg i=1;i<=2*n;++i){
        pos[l[i].id]=i;
    }
//    for(reg i=1;i<=2*n;++i){
//        cout<<i<<" : "<<l[i].k<<" "<<l[i].b<<" "<<l[i].id<<endl;
//    }
    for(reg i=1;i<=K;++i){
        sol();
    }
    return 0;
}

}
signed main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2018/12/26 15:57:25
*/
View Code

 

 

或者,考虑两个点什么时候会撞上

一定是两边的粒子在某个时刻领先于其他竞争粒子。

把直线按照横截距排序

橙色的每个区间都可以用单调栈来计算。

两部分直线分别计算

然后形成若干区间

双指针扫描即可。第一个交点就是答案

其实和上面方法本质相同(甚至是新的一种水平可见直线的做法?)

 

考虑不到图像?

那就尝试考虑过程

两个碰撞上的粒子,是所有剩下粒子中第一个碰撞的。

所以考虑二分碰撞时间T

如果存在一对粒子T时间路程总和>=L那么可以碰撞。

O(NKlog1e9)
卡卡常可过。

 

进一步考虑两个碰上的粒子满足

L=(T-t1)*v1+(T-t2)*v2

那么,如果二分了一个T

实际上是在找是否存在v1,t1,v2,t2

使得L<=(T-t1)v1+(T-t2)*v2

两部分可以分开找最大值

最大化:f=Tv1-t1v1

t1v1=Tv1-f

斜率优化!

可以对剩余粒子维护分别维护两个凸壳。然后二分

O(NK+Klog^2N+NlogN)

每撞一次,重建一遍凸壳。

 

 

T3:

 

 

质因子个数很少。又要取模,所以不能爆搜。

就考虑状压DP

记录什么状态?状态要能够支持找到能否加入一个约数

公约数只和质因子有关

6位三进制数记录质因子用了几个。

但是对于6,和2,3显然不同,但是状态就一样了。

如果不能够选择,排除只有一个质因子的情况,那么就是这个约数存在两个不同的质因子,这两个质因子都在之前不同的数中出现过。

所以,暴力一些,记录15位二进制数表示(f1,f2)质数对能否在同一个数中加入进来。

状态转移

枚举用了不到2个的质因子的子集

如果有限制,则不行

否则更新到新的状态。

新的状态处理:三进制质因子部分直接更新。二进制部分,考虑如果某个质因子对,f1之前选择过,f2现在选择了,那么(f1,f2)已经出现在两个不同的数里了,所以不能同时出现。

对于其他情况,一个质数对要么已经变成非法,要么一个没有出现过,要么两个都在这一次出现,所以没有问题

本质上我们通过限制质因子出现与否,以及出现在几个数中,限制了合法条件。

 

实现的话,状态看似很多,其实很多冗余状态。所以状态不连续。

考虑用map存状态(可以dfs记忆化)

还可以把map存成队列,然后状态从小到大更新,本质上实现了数组的阶段循环或者说花费logn时间实现了一个拓扑排序。(直接一般队列是错的,类比DAG,分层图编号小的不一定拓扑序小啊!)

#include<bits/stdc++.h>
#define reg register int
#define il inline
#define numb (ch^'0')
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef long long ll;
il void rd(int &x){
    char ch;x=0;bool fl=false;
    while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
namespace Miracle{
const int N=233;
const int mod=1e9+7;
ll n;
int tot,cnt[N];
map<int,ll>dp;
map<int,ll>::iterator r;
ll pw[8]={1,3,9,27,81,243,729,2187};
ll ans;
int getnew(int st,int chos){
//    cout<<" getnew "<<st<<" "<<chos<<endl;
    int ret=st;
    int bit=1;
    for(reg i=0;i<tot;++i){
        if((chos&(1<<i))&&st/bit%3<2) ret+=bit;
        bit*=3; 
    }
    for(reg i=0;i<tot;++i){
        for(reg j=i+1;j<tot;++j){
            if((chos&(1<<i))&&(st/bit%2==0)&&(st/pw[j]%3!=0)) ret+=bit;
            else if((chos&(1<<j)&&(st/bit%2==0)&&(st/pw[i]%3!=0))) ret+=bit;
            bit<<=1;
        }
    }
//    cout<<" ret "<<ret<<endl;
    return ret;
}
ll getsz(int chos){
    ll ret=1;
//    cout<<" chos "<<chos<<endl;
    for(reg i=0;i<tot;++i){
        if(chos&(1<<i)) ret=(ret*cnt[i+1])%mod;
    }
//    cout<<" ret "<<ret<<endl;
    return ret;
}
void divi(ll x){
    for(ll i=2;i*i<=x;++i){
        if(x%i==0){
            ++tot;
            while(x%i==0) ++cnt[tot],x/=i;
        }
    }
    if(x>1) cnt[++tot]=1;
}
int main(){
    scanf("%lld",&n);
    divi(n);
    dp.insert(mp(0,1));
    r=dp.begin();
    while(r!=dp.end()){
        (ans+=r->se)%=mod;
        //cout<<r->fi<<" "<<r->se<<endl;
        int re=0;
        int bit=1;
        int now=r->fi;
        for(reg i=0;i<tot;++i){
            if(now/bit%3<2) re|=(1<<i);
            bit*=3;
        }
        int ban[16],top=0;
        for(reg i=0;i<tot;++i){
            for(reg j=i+1;j<tot;++j){
                ++top;
                if((now/bit)&1) ban[top]=(1<<i)|(1<<j);
                else ban[top]=0;
                bit<<=1;
            }
        }
        //cout<<" re "<<re<<endl;
        for(reg to=re;to;to=(to-1)&re){
        //    cout<<"to "<<to<<endl;
            bool cont=false;
            for(reg i=1;i<=top&&!cont;++i){
                if(ban[i]&&(ban[i]|to)==to) cont=true;
            }
            if(cont) continue;
        //    cout<<" ok "<<to<<endl;
            int go=getnew(now,to);
            if(dp.find(go)==dp.end()) dp.insert(mp(go,0));
            (dp[go]+=(r->se)*getsz(to))%=mod;
        }
        ++r;
    }
    ans=(ans-1+mod)%mod;
    printf("%lld",ans);
    return 0;
}

}
signed main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2018/12/26 18:58:23
*/
View Code

 

 

总结:

1.T1还是想复杂了。对于题目的度还是把握不好。hash的边走边记录的思想还是不太熟练。对于一个位置多次查询的新旧对比(类似天天爱跑步?)

2.T2一直在想怎么找一个直线和其他直线的前K个交点。。。。还是没有把握好度。由于K比较小。其实O(NK)的复杂度的话,考虑O(N)找到一个,比一股脑塞进去一堆还是要简单自然的。

甚至开始考虑两个点为什么会碰撞也是考虑过的,,,但是二分还是没有想到。还是有些复杂化

3.T3这种不靠谱的状态设计。。。状态数不会太多?还是状压dp比较有把握并且其他题目处理好了再说吧。。。

还是度把握不好。。之后考试应该会有所进步。

最好的方法还是,观察题目的性质,考虑简化的问题,探寻题目条件为什么会成立,为什么会不合法

其次,再是考虑套路(二分,分治,时光倒流,正难则反,容斥),考虑蒙蒙算法

或者手动推推例子找规律

posted @ 2018-12-26 20:48  *Miracle*  阅读(252)  评论(0编辑  收藏  举报