暑假集训第二周总结

   

     数位dp,kmp,线段树与树状数组,矩阵快速幂,一些STL函数和容器的使用,扫描线;


   1:数位dp是新学的内容 多用于区间统计   eg: 问1~10^18里有多少个数满足某个条件 比如有k个数为1  这时候因为数据太大 不可能直接暴力解决 所以想到用数位dp  从第k位开始(因为位数不足k位的话 不满足条件) 递推分析每一位可能的情况 用dp[i][j]表示当前进行到第i位 共有j个1的情况有多少种 来推导i+1位的情况    这种解决问题的方法就叫做数位dp      典型例题:hdu 2089  hdu 3555  

  这里附一道刚看的题     二分+数位dp    Random Task  

   

One day, after a difficult lecture a diligent student Sasha saw a graffitied desk in the classroom. She came closer and read: "Find such positive integer n, that among numbers n + 1n + 2, ..., n there are exactly m numbers which binary representation contains exactlyk digits one".

The girl got interested in the task and she asked you to help her solve it. Sasha knows that you are afraid of large numbers, so she guaranteed that there is an answer that doesn't exceed 1018.


因为是二分专题 直觉告诉我二分查解  但是数据量太大 每次二分答案之后统计有多少个数满足条件的任务很难完成 这时候应该考虑数位dp    那么对于答案mid   就应该是  dp[mid*2][k]-dp[mid][k]  这样问题就解决了


<span style="font-size:14px;">#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <string>
#include <map>
#include <stack>
#include <vector>
#include <set>
#include <queue>
#pragma comment (linker,"/STACK:102400000,102400000")
#define maxn 15
#define MAXN 100005
#define OO (1LL<<35)-1
#define mod 1000000007
#define INF 0x3f3f3f3f
#define pi acos(-1.0)
#define eps 1e-6
typedef long long ll;
using namespace std;

ll n,m,k,d,ans,tot,flag,cnt;
ll C[70][70];

ll cal(ll x)
{
    ll i,j,res=0,num=0;
    for(i=62;i>=0;i--)    //i 从0到62  当i=4 且x&(1<<i)时  这时i在第五位  前面有0 1 2 3 四位  所以+c[i][k-num]
    {
        if(x&(1LL<<i))
        {
            if(k-num>=0) res+=C[i][k-num];
            num++;
        }
    }
    return res;
}
int main()
{
    ll i,j,t,le,ri,mid,cnt;
    for(i=0;i<=64;i++) C[i][0]=1;
    for(i=1;i<=64;i++)
    {
        for(j=1;j<=i;j++)
        {
            C[i][j]=C[i-1][j]+C[i-1][j-1];
        }
    }
    while(cin>>m>>k)
    {
        le=1; ri=1LL<<62;
        while(le<=ri)
        {
            mid=(le+ri)>>1;
            cnt=cal(2*mid)-cal(mid);
            if(m<=cnt)
            {
                ans=mid;
                ri=mid-1;
            }
            else le=mid+1;
        }
        cout<<ans<<endl;
    }
    return 0;
}</span>


     2: kmp   通俗来说就是字符串匹配算法   这个算法主要是对模版的灵活应用    你可以用a来匹配b   用b的某个子串自身匹配  等等     用处很多 但关键只是合理算出next[]函数  然后能想到如何去使用就好了    这个算法其实和后缀数组特别像   都是理解算法本身 然后调用模版算出一些关键值    最后各种不同的问题只是对关键值的使用不同而已  说实话理解还不到家   有时候给人手算next值都算不明白。。    还需要多想多看多学习  下面附上kmp和后缀数组的模版代码


    

char c[100005];
int nex[100005];
int main()
{
    int i,j,m;
    cin>>c;
    m=strlen(c);
    i=0;j=-1;
    nex[0]=-1;
    while(i<m)
    {
        if(j==-1||c[i]==c[j])
        {
            i++;
            j++;
            nex[i]=j;
        }
        else
        {
            j=nex[j];
        }
    }
    return 0;
}


#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define maxn 200100
using namespace std;
int r[maxn];
int Rank[maxn],sa[maxn],height[maxn];
int wa[maxn],wb[maxn],wv[maxn],ws[maxn];
char a[maxn],b[maxn];
int cmp(int *r,int a,int b,int le)
{
	return r[a]==r[b]&&r[a+le]==r[b+le];
}
void da(int *r,int *sa,int n,int m)
{
	int i,j,p,*x=wa,*y=wb,*t;
	for ( i = 0; i < m; i++) ws[i]=0;
	for ( i = 0; i < n; i++) ws[ x[i] = r[i] ]++;
	for ( i = 1; i < m; i++) ws[i]+=ws[i-1];
	for ( i = n-1; i >= 0; i--) sa[--ws[x[i]]]=i;
	for ( j = 1,p=1; p <n ; j*=2,m=p)
	{
		for ( p = 0,i=n-j; i < n; i++) y[p++]=i;
		for ( i = 0; i < n; i++) if(sa[i]>=j) y[p++]=sa[i]-j;
		for ( i = 0; i < n; i++) wv[i]=x[y[i]];
		for ( i = 0; i < m; i++) ws[i]=0;
		for ( i = 0; i < n; i++) ws[wv[i]]++;
		for ( i = 1; i < m; i++) ws[i]+=ws[i-1];
		for ( i = n-1; i >= 0 ; i--) sa[--ws[wv[i]]]=y[i];
		for (t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
			x[sa[i]] = cmp(y,sa[i-1],sa[i],j)?p-1:p++;
	}
	return ;
}
void calheight( int *r,int *sa,int n)
{
	int i,j,k=0;
	for ( i = 0; i <=n ; i++) Rank[sa[i]]=i;
	for(i=0;i<n;height[Rank[i++]]=k)
		for(k?k--:0,j=sa[Rank[i]-1];r[i+k]==r[j+k];k++);
	return ;
}
struct pi
{
    int min;
}pp[4*maxn];
void build(int le,int ri,int tot)
{
    if(le==ri)
    {
        pp[tot].min=height[le];
        return ;
    }
    int mid;
    mid=(le+ri)/2;
    build(le,mid,2*tot);
    build(mid+1,ri,2*tot+1);
    pp[tot].min=min(pp[2*tot].min,pp[2*tot+1].min);
    return ;
}
int query(int le,int ri,int tot,int ll,int rr)
{
    int p,q;
    p=100000000;
    q=100000000;
    if(le>ri)
        return 0;
    if(le<=ll&&ri>=rr)
    {
        return pp[tot].min;
    }
    int mid;
    mid=(ll+rr)/2;
    if(le<=mid)
    {
        p=query(le,ri,2*tot,ll,mid);
    }
    if(ri>mid)
    {
        q=query(le,ri,2*tot+1,mid+1,rr);
    }
    if(p==100000000&&q==100000000)
        return 0;
    return min(p,q);
}
int main()
{
    int i,n,k,f,le,ri,mid,p;
    while(scanf("%d%d",&n,&k)!=EOF)
    {
        for(i=0;i<n;i++)
        {
            scanf("%d",&f);
            r[i]=f+1;
        }
            r[n]=0;//为了使rank从1开始,防止height[rank[i]-1]越界。
        da(r,sa,n+1,20002);  //r是rank 20002是基数
        calheight(r,sa,n);
        for(i=2;i<=n;i++)
            height[i];      //height[i]数组的定义sa[i-1]和sa[i]的最长公共前缀,而rank从1到n,所以height数组从2到n。
    }
    return 0;
}




    3:  矩阵快速幂   在做概率dp时候遇到的  大致学习了下 是用来解决递推式的问题  比如求斐波那契数列的第100000项 直接出来必然会超时  因为数据大而多 还需要保存每一位的结果    推到后面就会爆掉  类似这样的问题就用到了矩阵快速幂     它的好处在于只需要保存前几位 (一般是两位)的结果  然后每次和一个矩阵相乘  相乘k次以后便可以得到这个递推式的第k项    也许有人会问    相乘k次不也相当于计算了k次么  其实不然   变成乘法以后最大的特点就是可以用2进制优化   这样即使一个10^18的数 最多也仅仅计算18次     这里也体现了一个数按位分析的优势    这个算法除了对付一些推规律可以形成循环的数列不值当以外  解决大多数问题还是很优的   

这里附上我的一个模版  写的是求斐波那契数    具体不同的情况可以通过改矩阵元素 和构造不同的辅助矩阵来解决 

#include <iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
struct data
{
    int a[3][3];
};
struct data cal(struct data p,struct data q)
{
    struct data jg;
    for(int i=1;i<=2;i++)
        for(int j=1;j<=2;j++) jg.a[i][j]=0;
    for(int i=1;i<=2;i++)
        for(int j=1;j<=2;j++)
            for(int k=1;k<=2;k++)
           {
               jg.a[i][j]+=(p.a[i][k]%10000)*(q.a[k][j]%10000);
               jg.a[i][j]%=10000;
           }
    return jg;
}
struct data hanshu(int n)
{
    struct data jg,temp;
    for(int i=1;i<=2;i++)
        for(int j=1;j<=2;j++) jg.a[i][j]=1;
            jg.a[2][2]=0;
    temp.a[1][1]=1;
    temp.a[1][2]=1;
    temp.a[2][1]=1;
    temp.a[2][2]=0;
    while(n!=0)
    {
        if(n&1) jg=cal(jg,temp);
        n>>=1;
        temp=cal(temp,temp);
    }
    return jg;
}
int main(int argc, const char * argv[])
{
    int n;
    struct data ans;
    while(scanf("%d",&n)!=EOF)
    {
        if(n==-1) break;
        if(n==0) printf("0\n");
        else
        {
        ans=hanshu(n-1);
        printf("%d\n",ans.a[1][2]%10000);
        }
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值