2017/7/27 离线赛

来源:互联网 发布:淘宝店铺客服电话 编辑:程序博客网 时间:2024/05/22 04:31

T1 猴王大道东

    70分的数据还是很暴力的O(n4),定义dp[i][j]表示到点(i,j)的方案数,我用的是刷表法,每次dp(i,j)时枚举它能到达的点,即xi+1nyj+1m,空间复杂度只要O(n2);90分的话,我考虑了填表法,只要从之前的dp中抽出来需要的就行了,当dp(i,j)时,我们只要求出(1,1)(i1,j1)的矩形dp值的和即二维前缀和,再减去其中颜色和(i,j)相同的dp值的和即可,维护前缀和只要O(1),而求该种颜色的dp值则比较麻烦了,因为有空间的限制,所以不能用二维前缀和,我用的方法是:存下dp到当前点之前各种颜色的dp值之和sum,然而sum不仅仅包括需要的dp值,还有多出来的dp值,如下图:红色的方格为当前需要求的格子,sum存的所有灰色格子的各种颜色的值的和,然而我们只要红色圈圈内的值,所以要减去黄色圈圈内的和蓝色圈圈内的,蓝色圈圈内的好求, 只要在求每一行时存个前缀和就OK了,那黄色区域就要用到用到BIT了。

int main(){    dp[1][1]=1;    add(1,1,1);    sum[c[1][1]]++;    add1(n,c[1][1],1);    for(int i=2;i<=n;i++){        memset(s,0,sizeof(s));    for(int j=2;j<=m;j++){            dp[i][j]=(dp[i][j]+query(i-1,j-1))%P;            dp[i][j]=((dp[i][j]-sum[c[i][j]])%P+P)%P;            dp[i][j]=(dp[i][j]+s[c[i][j]])%P;            dp[i][j]=(dp[i][j]+query1(m-j+1,c[i][j]))%P;            add(i,j,dp[i][j]);            sum[c[i][j]]=(sum[c[i][j]]+dp[i][j])%P;            s[c[i][j]]=(s[c[i][j]]+dp[i][j])%P;            add1(m-j+1,c[i][j],dp[i][j]);        }    }    Pt(dp[n][m]);    Pt(dp[n][m]);    putchar('\n');    return 0;}

    至于100分的解法,我只能去看题解了。用的是CDQ分治,我们可以在行上进行CDQ分治,在列上扫一遍,复杂度是O(n2logn),每次CDQ(L,R)时,我们先CDQ(L,mid)(mid=(L+R)/2),然后将按列扫一遍,将(L,mid)中求出的dp值转移到(mid+1,R)中。

void Mod_pls(int &a,int b){    if((a+=b)>=P)a-=P;}void Mod_mns(int &a,int b){    if((a-=b)<0)a+=P;}int n,m,k;int c[M][M],dp[M][M],s[M*M];void CDQ(int L,int R){//L~R行     if(L==R)return;    int mid=(L+R)>>1;    CDQ(L,mid);    for(int j=1,tot=0;j<=m;j++){//每一列         for(int i=mid+1;i<=R;i++){            Mod_pls(dp[i][j],tot);            Mod_mns(dp[i][j],s[c[i][j]]);        }        for(int i=L;i<=mid;i++){            Mod_pls(tot,dp[i][j]);            Mod_pls(s[c[i][j]],dp[i][j]);        }    }    for(int j=1;j<=m;j++)    for(int i=L;i<=mid;i++)    s[c[i][j]]=0;    CDQ(mid+1,R);}int main(){    Rd(n);Rd(m);Rd(k);    for(int i=1;i<=n;i++)    for(int j=1;j<=m;j++)    Rd(c[i][j]);    dp[1][1]=1;    CDQ(1,n);    Pt(dp[n][m]);    putchar('\n');    return 0;}

T2 花果山发展

    比赛的时候只敲了个60分的暴力,想过要用BIT维护,但是没想到怎么实现,实际只要排一下序就好了。分别求竖着的道路和横着的道路的路灯数,先求竖着的道路(横着的一样就行了),我们把竖着的道路按横坐标从小到大排序,再复制出来一组横着的道路,原来那组横着的道路按座左端点从小到大排序,后来那组横着的道路按右端点从小到大排序,然后扫一遍竖着的道路,扫到一条道路的时候,把按左端点排序的数组中左端点小于等于扫到的道路的横坐标的道路的纵坐标在BIT里+1,按右端点排序的右端小于横坐标的道路的纵坐标在BIT里-1(因为这条横着的道路在之前肯定+1了),然后结果就是query(R)query(L1)f(i)f(i)表示有多少条道路的端点与竖着的第i条道路重合,用map存一下就好了。

typedef pair<int,int> P;map<P,bool>mp;struct node{    int x,L,R,id;}e1[M],e2[M],tmp[M];//竖着和横着bool cmp(node A,node B){    return A.x<B.x;}bool cmp1(node A,node B){    return A.L<B.L;}bool cmp2(node A,node B){    return A.R<B.R;}int n,cnt1,cnt2;int BIT[M],ans[M];ll res;void add(int x,int a){    int i=x;    while(i<=100000){        BIT[i]+=a;        i+=i&-i;    }}int query(int x){    int res=0,i=x;    while(i){        res+=BIT[i];        i-=i&-i;    }    return res;}int main(){    Rd(n);    int x1,y1,x2,y2;    for(int i=1;i<=n;i++){        Rd(x1);Rd(y1);Rd(x2);Rd(y2);        if(x1==x2)e1[++cnt1]=(node){x1,min(y1,y2),max(y1,y2),i};        else{            e2[++cnt2]=(node){y1,min(x1,x2),max(x1,x2),i};            tmp[cnt2]=e2[cnt2];            mp[P(x1,y1)]=1;            mp[P(x2,y1)]=1;        }    }    sort(e1+1,e1+cnt1+1,cmp);    sort(e2+1,e2+cnt2+1,cmp1);    sort(tmp+1,tmp+cnt2+1,cmp2);    int p1=1,p2=1;    for(int i=1;i<=cnt1;i++){        int x=e1[i].x;        while(p1<=cnt2&&e2[p1].L<=x){add(e2[p1].x,1);p1++;}        while(p2<=cnt2&&tmp[p2].R<x){add(tmp[p2].x,-1);p2++;}        int t=query(e1[i].R)-query(e1[i].L-1);        if(mp.find(P(x,e1[i].L))!=mp.end())t--;        if(mp.find(P(x,e1[i].R))!=mp.end())t--;        res+=t;        ans[e1[i].id]=t;    }    mp.clear();    memset(BIT,0,sizeof(BIT));    for(int i=1;i<=cnt1;i++){        tmp[i]=e1[i];        mp[P(e1[i].x,e1[i].L)]=1;        mp[P(e1[i].x,e1[i].R)]=1;    }    sort(e2+1,e2+cnt2+1,cmp);    sort(e1+1,e1+cnt1+1,cmp1);    sort(tmp+1,tmp+cnt1+1,cmp2);    p1=1,p2=1;    for(int i=1;i<=cnt2;i++){        int y=e2[i].x;        while(p1<=cnt1&&e1[p1].L<=y){add(e1[p1].x,1);p1++;}        while(p2<=cnt1&&tmp[p2].R<y){add(tmp[p2].x,-1);p2++;}        int t=query(e2[i].R)-query(e2[i].L-1);        if(mp.find(P(e2[i].L,y))!=mp.end())t--;        if(mp.find(P(e2[i].R,y))!=mp.end())t--;        ans[e2[i].id]=t;    }    Pt(res);putchar('\n');    for(int i=1;i<=n;i++){        if(i>1)putchar(' ');        Pt(ans[i]);    }    putchar('\n');    return 0;}

T3 猴子与数字

    我一开始拘泥于题目要求[L,R]上的数字了,所以就很难判定了,因为既要大于等于L,又要小于等于R,而且边界很难搞,怎么样能少判点东西呢?那就是求出[1,L1][1,R]上的数,然后再把两个值作差,然而还要判等于的情况,不如直接变成求[1,L)[1,R+1)上的数,这样只要造出小于右端点的数就好了(真是越来越简单了_(:зゝ∠)_)。在求小于x的数时,我们很容易可以求出长度小于x的数,要用到容斥的思想,然后再去除前导0的情况,长度相同的数要一位一位枚举过来,造出比当前位数小的数,然后就OK啦~

ll C[25][25],P[25][25],tmp;int a[10],b[10],v[25];void dfs(int x,int cnt,int rest,ll r){    if(x>9){        r*=P[10-cnt][rest];        if(cnt&1)tmp-=r;        else tmp+=r;        return;    }    dfs(x+1,cnt,rest,r);    if(b[x]<=rest&&b[x]>=0)dfs(x+1,cnt+1,rest-b[x],r*C[rest][b[x]]);}ll solve(ll x){//[1,x)    ll res=0;    for(int i=0;i<=9;i++)b[i]=a[i];    int len=0;    while(x){v[++len]=x%10;x/=10;}    for(int i=1;i<len;i++){//长度为i         tmp=0;        dfs(0,0,i,1);        res+=tmp;        tmp=0;        b[0]--;        dfs(0,0,i-1,1);        res-=tmp;        b[0]++;    }    for(int i=len;i>=1;i--){//长度相同时第i位         for(int j=(i==len);j<v[i];j++){            b[j]--;            tmp=0;            dfs(0,0,i-1,1);            res+=tmp;            b[j]++;        }        b[v[i]]--;    }    return res;}void Init(){    for(int i=0;i<=10;i++){        P[i][0]=1;        for(int j=1;j<=20;j++)P[i][j]=P[i][j-1]*i;    }    C[0][0]=1;    for(int i=1;i<=20;i++){        C[i][0]=1;        for(int j=1;j<=i;j++)        C[i][j]=C[i-1][j-1]+C[i-1][j];    }}int main(){    ll L,R;    Init();    Rd(L);Rd(R);    for(int i=0;i<10;i++)Rd(a[i]);    Pt(solve(R+1)-solve(L));    putchar('\n');    return 0;}

T1的CDQ分治确实值得仔细琢磨一下,既降低了时间复杂度,又节省了空间。T2用的算法我都会,但是就是没想到,有些问题只要排一下序,一遍扫过来即可。T3则是当时觉得太麻烦,也没有转换思路,没有运用前缀和的思想,这个方法化繁为简的确很明显。

原创粉丝点击