zoukankan      html  css  js  c++  java
  • 浙工大新生赛莫队处理+区间DP+KMP+分析题

    题目描述

    读入一个长度为n的整数数列a1,a2,…,an,以及一个整数K。
    q组询问。
    每组询问包含一个二元组(l, r), 其中1≤l≤r≤ n,
    求所有满足以下条件的二元组(l2, r2)的数目:
    1: 1≤l≤l2≤r2≤r≤n,
    2: 是k的倍数。

    输入描述:

    第一行一个整数T,表示有T组数据。
    对于每组数据,
    第一行输入3个整数, n, q, k,
    接下来一行输入n个整数,a1,a2,…,an
    接下来q行,每行输入2个整数 l, r

    输出描述:

    对于每一个询问,输出满足条件的二元组(l
    2
    ,r
    2
    )的数目。
    示例1

    输入

    1
    5 2 4
    4 1 4 8 8
    2 2 
    2 4

    输出

    0
    3

    说明

    当(l, r)为(2, 4)时,
    (l2,r2)=(3,4),a3+a4=12,是4的倍数
    (l2,r2)=(3,3),a3=4,是4的倍数
    (l2,r2)=(4,4),a4=8,是4的倍数

    备注:

    1≤ n,q ≤5×104,
    1≤ l ≤ r ≤n,
    1≤ ai, k ≤109,
    保证∑n ≤6×104,∑q ≤6×104
     
     
    注意B[n+1]=0要进行的,因为指针移动过程中维护的是一个左开右闭区间,那么当询问中l=1的时候,显然离散后的A[0]要有一个合适的指代值,如果不进行加0的话。
    那么有两种情况 :第一种情况原前缀和序列中没有0,那么就好办了,离散后A[1]-A[n]的指代值只需要于1-tot中取然后默认A[0]=0;
                                  但如果是原前缀和序列中存在0,根据上面的处理方法,离散后存在A[i](i>=1&&i<=n)使得A[i]=0并且离散化后的值为1,那么A[0]=0但是默认离散化后的值是0,会出现少记的情况。
    但如果事先把B[n+1]=0加进去就不会出现等于0的A[i]离散化后会有两个指代值的情况了。
     
    但也可以不进行 只需要加个特判就可以了,B数组是A数组排序后的值的集合,那么B[1]==0?A[0]=1:A[0]=0就可以了。但是要注意下一次求的时候要先A[0]=0。
     
     
     
    然后指针更新迭代的过程一定是维护着左开右闭的

    他r假设要向右移动的时候,那么先把r向右移动一格,++r,然后ANS再加cnt[A[r]],然后再让cnr[A[r]]++。就是现有满足条件可以还原成这样for(int i=l;i<r;++i) ANS+=(a[i]==a[r]),如果a[i]==a[r],说明的是i+1到r这个区间的总和满足对k取余为0(注意这是一个闭区间,同时注意到最小区间左端点是l+1,维护了左开),然后观察就知道如果他本身是的话,就相当于a[r-1]==a[r]就行了(右闭满足),如果先加cnt[a[r]],那就重复计数计多了。

     l向左移动的时候 同理 --l,for(int i=l+1;i<=r;++i)是这样的 如果a[i]==a[l]说明的是l+1到i这个区间的总和满足对k取余为0(这也是闭区间,变像维护了左开,每次向左移动一次,都把上次没计入的l计入上).
    #include<cstdio>
    #include<cmath>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int N=5e4+88;
    int block;
    struct node{
        int id,l,r;
        bool operator<(const node &A)const{
          return (l-1)/block==(A.l-1)/block?r<A.r:l<A.l;
        }
    }Q[N];
    int A[N],B[N],cnt[N],ans[N];
    int main(){
        int n,m,k,T;
        for(scanf("%d",&T);T--;){
            scanf("%d%d%d",&n,&m,&k);
            for(int i=1;i<=n;++i) {
                scanf("%d",A+i);
                A[i]=(A[i]+A[i-1])%k;
                B[i]=A[i];
            }
            block=(int)sqrt(n);
            B[n+1]=0;
            sort(B+1,B+n+2);
            int tot=unique(B+1,B+n+2)-B-1;
            for(int i=0;i<=n;++i) A[i]=lower_bound(B+1,B+tot+1,A[i])-B-1,cnt[i]=0;
            for(int i=1;i<=m;++i) scanf("%d%d",&Q[i].l,&Q[i].r),Q[i].id=i;
            sort(Q+1,Q+m+1);
            int l=Q[1].l,r=Q[1].l-1,ANS=0;
            for(int i=1;i<=m;++i) {
                while(l<Q[i].l) --cnt[A[l]],ANS-=cnt[A[l++]];
                while(l>Q[i].l) ANS+=cnt[A[--l]],++cnt[A[l]];
                while(r>Q[i].r) --cnt[A[r]],ANS-=cnt[A[r--]];
                while(r<Q[i].r) ANS+=cnt[A[++r]],++cnt[A[r]];
                ans[Q[i].id]=ANS+cnt[A[l-1]];
            }
            for(int i=1;i<=m;++i) printf("%d
    ",ans[i]);
        }
    }

    题目描述

            小杰是学院中公认的计算机大神,有一天小杰受邀去机房帮忙接网线,他三下五除二地就帮忙完成了。可是善于思考地他仍意有未尽,他觉得这次的线路有许多奇妙的性质,故来请教你。
            机房有n台不同的主机(编号为0,1,2,⋯,n−1),共连有n条网线,每台主机恰连着两条网线,通向其他的主机。相互之间有网线相连的主机可以通信,主机可以转发其他主机的信息,换句话说,如果主机A和主机C可以通信,主机B和主机C可以通信,那么主机A和主机B可以通信。现在想在一些主机间添加一些网线,使得任意一个主机故障时,其他的主机之间仍能互相通信(注:在一个主机故障时,所有与该主机直接相连的线路将无法使用)。那么问题来了,在添加的网线最少时,请问有多少种连线的方案,由于连线方案可能非常多,请将方案数对1000,000,007取模。

    输入描述:

    第一行表示主机数n。
    接下来有n行,第i(i=0,1,2,⋯,n−1)行有两个数a,b,表示主机i和主机a,主机i和主机b各有网线相连。
    输入有多组数据,n=0时,表示输入结束,请不要有对该组数据任何输出。

    输出描述:

    每组数据输出一行,表示方案数对1000,000,007取模的结果。(行末不要有多余的空白字符)
    示例1

    输入

    5
    2 3
    4 4
    0 3
    0 2
    1 1
    12
    11 11
    10 10
    9 9
    8 8
    7 7
    6 6
    5 5
    4 4
    3 3
    2 2
    1 1
    0 0
    2
    1 1
    0 0
    0

    输出

    6
    3840
    0

    备注:

    2≤n≤3×105,
    不超过10组数据 n≥2×105,
    不超过20组数据 n≥104,
    总数据组数不超过200
     
    分析之后就可以发现整张图是由不同的环组成。因为只有n条边还限制了每个点有且仅有2条边与其它相连。
     
    那么就是先每个环排序有(m-1)!,再乘每个环都要取两个点C(n,2),然后相邻环之间的连线有两种可能性在乘上。
     
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int N=3e5+88;
    const int mod=1e9+7;
    int fa[N],size[N];
    int findx(int x) {
        return x==fa[x]?x:(fa[x]=findx(fa[x]));
    }
    int main(){
        int n,x,y;
        while(scanf("%d",&n),n){
            int m=n;
            for(int i=0;i<=n;++i) fa[i]=i,size[i]=1;
            for(int i=0;i<n;++i) {
                for(int j=0;j<2;++j){
                scanf("%d",&x);
                int ux=findx(x),uz=findx(i);
                if(uz!=ux) {
                    fa[ux]=uz;
                    size[uz]+=size[ux];
                    --m;
                }
                }
            }
            if(m==1) puts("0");
            else {
                int ans=1;
                for(int i=1;i<m;++i) ans=(1LL*ans*i*2)%mod;
                for(int i=0;i<n;++i) if(fa[i]==i) ans=(1LL*ans*(1LL*size[i]*(size[i]-1)/2%mod))%mod;//括号要括好
                printf("%d
    ",ans);
            }
        }
    }

    给定两个长度为n的整数列A和B,每次你可以从A数列的左端或右端取走一个数。假设第i次取走的数为ax,则第i次取走的数的价值vi=bi⋅ax,现在希望你求出∑vi的最大值。

    输入描述:

    第一行一个数T,表示有T组数据。
    对于每组数据,第一行一个整数n,
    接下来两行分别给出A数列与B数列。

    输出描述:

    每一组数据输出一行,最大的∑v

    i

    示例1

    输入

    2
    2
    1 1000
    2 1
    5
    1 3 5 2 4
    1 2 3 4 5

    输出

    2001
    52

    说明

    对于第二个样例,
    第一次从左边取走a1,v1=a1⋅b1=1,
    第二次从左边取走a2,v2=a2⋅b2=6,
    第三次从右边取走a5,v3=a5⋅b3=12,
    第四次从右边取走a4,v4=a4⋅b4=8,
    第五次取走剩下的a3,v5=a3⋅b5=25。
    总价值∑vi=1+6+12+8+25=52

    还是比较基础的一个区间DP
    #include<bits/stdc++.h>
    using namespace std;
    const int N=1008;
    int dp[N][N];
    int a[N],b[N];
    int main(){
        int T,n;
        for(scanf("%d",&T);T--;){
           scanf("%d",&n);
           for(int i=1;i<=n;++i) scanf("%d",a+i);
           for(int i=1;i<=n;++i) scanf("%d",b+i);
           for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) dp[i][j]=0;
           for(int i=1;i<=n;++i) for(int j=1;j<=n-i+1;++j) {
               dp[j][j+i-1]=max(dp[j+1][j+i-1]+b[n-i+1]*a[j],b[n-i+1]*a[j+i-1]+dp[j][j+i-2]);
           }
           printf("%d
    ",dp[1][n]);
        }
    }

    题目描述

            栗酱在玩一个激燃的战争游戏,游戏里有n个据点,据点之间存在一些道路,将据点两两连通。
            栗酱已经占据了一些据点,在他/她占领的每个据点中,他/她都至少安排了一个士兵,他/她可以在相邻的己方据点间移动任意士兵任意次。
            如果一个据点没有栗酱的士兵,说明这个据点是属于敌方的。
            现在在敌人的进攻之前,栗酱希望能够集中兵力防守,所以要把所有士兵调集到直接和敌人相邻的据点。
            但是他/她想让你写个程序算一下这样安排之后,所有与敌人结点直接相邻的己方结点的士兵数中的最小值最大是多少。
            为了避免一个己方据点成为敌方据点,每个据点至少需要保留一名士兵,注意士兵移动只能由己方据点到达相邻己方据点,不能穿越敌方据点。
            有些时候也会出现没有敌方结点,或者没有己方据点,或者没有敌方据点和己方据点连通的简单情况,对于这种情况,你的程序应该能及时识别出来,并给出"−1"(不包括引号)。

    输入描述:

    第一行一个数据组数T(T≤50)。
    每组数据第一行n,给出结点总数(n≤100),下一行给出n个数字,表示每一个结点有几名栗酱的士兵,如果没有士兵,则代表该结点属于敌方。
    之后给出一张n行n列的邻接矩阵,′Y′表示第i个结点和第j个结点存在道路将其连通。

    输出描述:

    对于每组数据每行给出一个数ans,表示为所求的答案,即所有与敌人结点直接相邻的己方结点的士兵数中的最小值最大是多少。
    示例1

    输入

    2
    7
    4 1 4 0 6 3 6
    NNNNYNN
    NNNNYNN
    NNNNNNN
    NNNNNNN
    YYNNNNY
    NNNNNNN
    NNNNYNN
    2
    0 7
    NY
    YN

    输出

    -1
    7
    其实就是二分后判定最小割就可以了,注意题目说要每个坑要保证有一个人。。。

    #include<cstdio>
    #include<vector>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int N=108;
    const int INF=0x3f3f3f3f;
    char mp[N][N];
    int vis[N];
    int dis[N],cnt[N],q[N],head[N],tot,S,T;
    struct node{
        int to,next,w;
    }e[N*N*2];
    void add(int u,int v,int w){
        e[tot].to=v;e[tot].next=head[u];e[tot].w=w;head[u]=tot++;
    }
    vector<int>amy,bs,ot;
    bool bfs(){
        memset(dis,-1,sizeof(dis));
        int r=0,l=0;
        q[r++]=S;
        dis[S]=0;
        while(l<r){
            int u=q[l++];
            for(int i=head[u];~i;i=e[i].next) {
                int v=e[i].to;
                if(dis[v]==-1&&e[i].w>0) {
                    q[r++]=v;
                    dis[v]=dis[u]+1;
                    if(v==T) return true;
                }
            }
        }
        return false;
    }
    int dfs(int s,int low){
        if(s==T||!low) return low;
        int ans=low,a;
        for(int i=head[s];~i;i=e[i].next) {
            if(e[i].w>0&&dis[e[i].to]==dis[s]+1) {
                if(a=dfs(e[i].to,min(ans,e[i].w))) {
                    ans-=a;
                    e[i].w-=a;
                    e[i^1].w+=a;
                    if(!ans) return low;
                }
            }
        }
        if(low==ans) dis[s]=-1;
        return low-ans;
    }
    int main(){
        int Ta,n;
        for(scanf("%d",&Ta);Ta--;){
            scanf("%d",&n);
            memset(vis,0,sizeof(vis));
            tot=0;
            amy.clear(),bs.clear(),ot.clear();
            memset(head,-1,sizeof(head));
            for(int i=1;i<=n;++i) scanf("%d",cnt+i);
            for(int i=1;i<=n;++i) if(!cnt[i]) vis[i]=1,amy.push_back(i);
            for(int i=1;i<=n;++i) scanf("%s",mp[i]+1); 
            for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) if(mp[i][j]=='Y') add(i,j,INF),add(j,i,INF);
            if((int)(amy.size())==n||(int)(amy.size())==0) {puts("-1");continue;} 
            for(int i=0;i<(int)amy.size();++i) for(int j=head[amy[i]];~j;j=e[j].next) if(!vis[e[j].to]) vis[e[j].to]=2,bs.push_back(e[j].to);
            if(bs.size()==0) {puts("-1");continue;}
            for(int i=1;i<=n;++i) if(!vis[i]) ot.push_back(i);
            int l=0,r=105,ans,aas;
            S=0,T=n+1;
            while(l<=r) {
                int mid=(l+r)>>1;
                tot=0;
                memset(head,-1,sizeof(head));
                for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) 
                if(mp[i][j]=='Y'&&vis[i]!=1&&vis[j]!=1) add(i,j,INF),add(j,i,INF);
                for(int i=0;i<(int)ot.size();++i) add(S,ot[i],cnt[ot[i]]-1),add(ot[i],S,0);
                for(int i=0;i<(int)bs.size();++i) add(bs[i],T,mid),add(T,bs[i],0);
                for(int i=0;i<(int)bs.size();++i) add(S,bs[i],cnt[bs[i]]),add(bs[i],S,0);            
                int ans=0;
                while(bfs()) ans+=dfs(S,INF);
                if(ans==(int)bs.size()*mid) l=mid+1,aas=mid;
                else r=mid-1;
            }
            printf("%d
    ",aas);
        }
    } 

    题目描述

    栗酱有一个长度为n的数列A,一个长度为m的数列B,现在询问A中有多少个长度为m的连续子序列A',
    满足(a'1+b1)%k = (a'2+b2)%k = …… = (a'm + bm)%k。

    输入描述:

    第一行一个数T,表示有T组数据。
    对于每组数据,
    第一行三个整数,n, m, k。
    第一行输入n个数, a1,a2,…,an, 表示A数列中的数,
    第二行输入m个数, b1,b2,…,bm, 表示B数列中的数。

    输出描述:

    每一组数据输出一行,满足条件的连续子序列数量。
    示例1

    输入

    2
    3 2 5
    7 8 7
    8 7
    3 2 5
    7 8 9
    8 7

    输出

    1
    2

    备注:

    T≤15,
    2≤m≤n≤2×105,
    1≤ai,bi,k≤109
     
    分析题意后可以知道,假设先固定左端点,那么只要接下来的数满足一个是取模条件下加了k,然后一个取模条件下减了k,那么就可以预处理出来A,B数组,A数组存的是原A数组的A[i]-A[i-1] %k的值
    B数组存的是(k-(B[i]-B[i-1])%k)%k的值,注意最后一个模k要进行的,,因为可能结果是k。。
    然后显然第一个端点是不计的因为左端点任意选都可以,所以处理时会去掉首位.
    然后kmp去匹配就好了。
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int N=2e5+88;
    int chang[N],n,m,k,duan[N],nxt[N];
    void get(){
        int i=0,j=-1;
        nxt[0]=-1;
        while(i<m) {
            if(j==-1||duan[i]==duan[j]) {
                if(duan[i+1]==duan[j+1]) nxt[++i]=nxt[++j];
                else nxt[++i]=++j;
            }
            else j=nxt[j];
        }
    }
    int KMP(){
        get();
        int i=0,j=0;
        int sum=0;
        while(i<n) {
            if(chang[i]==duan[j]) ++j,++i;
            else {
                j=nxt[j];
                if(j==-1) ++i,j=0;
            }
            if(j==m) ++sum;
        }
        return sum;
    }
    int main(){
        int T;
        for(scanf("%d",&T);T--;){
            scanf("%d%d%d",&n,&m,&k);
            for(int i=0;i<n;++i) scanf("%d",chang+i),chang[i]%=k;
            for(int i=0;i<m;++i) scanf("%d",duan+i),duan[i]%=k;
            for(int i=0;i<n-1;++i) chang[i]=(chang[i+1]-chang[i]+k)%k;
            for(int i=0;i<m-1;++i) duan[i]=(duan[i+1]-duan[i]+k)%k;
            for(int i=0;i<m-1;++i) duan[i]=(k-duan[i])%k;
            --m;--n;
            duan[m]=-1;
            printf("%d
    ",KMP());
        }
    }

    题目描述

            栗酱这次遇到了一个大麻烦,她手里只有一张大钞,面值为A元,但是现在临时需要花费B块钱了。

            她的花费非常紧急,所以她来到一个小卖部来兑,可是小卖部的老板态度极差,拒绝了栗酱的直接兑换请求。

            栗酱没有办法,周边也没有别的店,栗酱只好在店里强行花费一些钱来获取这个零散的B块钱了。

            但是这个店家十分奇怪,在知道栗酱需要B块钱后,会尽可能地不找给栗酱相应的钱,以获取利益。栗酱赶时间,花多少钱反而不那么重要了,请帮栗酱算一下,只买一次东西,一定能得到B块零钱的最少花费是多少。(货币的面值仅存在这些取值{0.01,0.02,0.05,0.10,0.50,1.00,2.00,5.00,10.00,20.00,50.00,100.00})

    输入描述:

    多组数据,数据第一行T表示数据组数。
    每组数据一行,A,B分别代表栗酱当前的大钞和需要的零钱。

    输出描述:

    每组数据一行,输出最少花费。
    示例1

    输入

    2
    0.05 0.01
    0.10 0.05

    输出

    0.02
    0.01

    说明

    第一个样例:
    如果付0.01块,老板会找你两个0.02。
    花0.02老板就没办法了,只能找你一个0.02,一个0.01,或三个0.01

    备注:

    T≤100,
    a>b,
    a,b∈{0.01,0.02,0.05,0.10,0.50,1.00,2.00,5.00,10.00,20.00,50.00,100.00}


    http://blog.csdn.net/irish_moonshine/article/details/78881607 暂时分析不清
  • 相关阅读:
    JavaScript.how-to-debug-javascript
    iOS.mach_absolute_time
    Startup.国外新锐公司及其技术Blog
    Android.FamousBlogs
    iOS.PrototypeTools
    Android.API.Context.getFilesDir()
    Android.Tools.Ant
    Tools.OnlineAPIs
    Java.Class
    Android.StructureOfAndroidSourceCodeRootTree
  • 原文地址:https://www.cnblogs.com/mfys/p/8120606.html
Copyright © 2011-2022 走看看