首页 > 代码库 > [BestCoder] Round #8

[BestCoder] Round #8

1001

http://acm.hdu.edu.cn/showproblem.php?pid=4989

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <stdlib.h>
#include <cmath>
#include <iomanip>
#include <vector>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <cctype>
#define rd(x) scanf("%d",&x)
#define rd2(x,y)  scanf("%d%d",&x,&y)
#define rd3(x,y,z) scanf("%d%d%d",&x,&y,&z)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
int dx[8]={0,0,-1,1,1,1,-1,-1};
int dy[8]={1,-1,0,0,-1,1,-1,1};
const int maxn=102;
int num[maxn];
ll sum[maxn*maxn/2];
int n;


int main()
{
    while(rd(n)!=EOF)
    {
        for(int i=1;i<=n;i++)
            rd(num[i]);
        int len=0;
        for(int i=1;i<=n;i++)
            for(int j=i+1;j<=n;j++)
            sum[++len]=(ll)(num[i]+num[j]);
        sort(sum+1,sum+1+len);
        ll ans=0;
        sum[0]=0;
        for(int i=1;i<=len;i++)
            if(sum[i]!=sum[i-1])
            ans+=sum[i];
        printf("%I64d\n",ans);
    }
    return 0;
}

1002

http://acm.hdu.edu.cn/showproblem.php?pid=4990

解题思路:

int ans=0;
for(int i=1;i<=n;i++)
{ if(i&1) ans=(ans*2+1)%m;
  else  ans=(ans*2)%m;
}  给定n,m求ans。   1<=n, m <= 1000000000
先不考虑取余m,求出n的前几项是 1 2 5 10 21 42
通项公式为  f(1)=1 f(2)=2  f(n)=2*f(n-2)+f(n-1)+1   n>=3
构造通项矩阵
       矩阵A                矩阵B            矩阵A‘
| f(n-2)  f(n-1)  1 |          | 0 2 0 |            | f(n-1) f(n) 1 |
| 0       0       0 |      *       | 1 1 0 |    =      | 0      0    0 |
| 0       0       0 |               | 0 1 1 |               | 0      0    0 |


f(n)=2*f(n-2)+f(n-1)+1这是二阶带常数的=所以是3*3.。。也就是说维护三个系数
然后就根据递推式确认纵列的系数 ,递推式系数为 2 1 1 ,所以f(n)对应的B矩阵第二列为2 1 1
相当于
                                | 0 2 0 |
(f[n-2] f[n-1] 1) *    | 1 1 0 | = (f[n-1] f[n] 1)
                                | 0 1 1 |


要求f(n) 只要让A矩阵乘以n-1次B矩阵,得到最终结果矩阵,结果矩阵的左上角第一个元素就是所求的f(n)
n-1次B矩阵相乘用矩阵快速幂运算就可以
注意元素要用long long 类型

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <stdlib.h>
#include <cmath>
#include <iomanip>
#include <vector>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <cctype>
#define rd(x) scanf("%d",&x)
#define rd2(x,y)  scanf("%d%d",&x,&y)
#define rd3(x,y,z) scanf("%d%d%d",&x,&y,&z)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
int dx[8]={0,0,-1,1,1,1,-1,-1};
int dy[8]={1,-1,0,0,-1,1,-1,1};
int n,mod;

struct mat
{
    ll arr[3][3];
    mat()
    {
        memset(arr,0,sizeof(arr));
    }
};

mat mul(mat a,mat b)
{
    mat ans;
    for(int i=0;i<3;i++)
        for(int k=0;k<3;k++)
        {
            if(a.arr[i][k])
            {
                for(int j=0;j<3;j++)
                {
                    ans.arr[i][j]+=(a.arr[i][k]*b.arr[k][j]);
                    if(ans.arr[i][j]>=mod)
                        ans.arr[i][j]%=mod;
                }
            }
        }
    return ans;
}

mat power(mat p,int k)
{
    if(k==1) return p;
    mat e;
    for(int i=0;i<3;i++)
        e.arr[i][i]=1;
    if(k==0) return e;
    while(k)
    {
        if(k&1)
            e=mul(e,p);
        p=mul(p,p);
        k>>=1;
    }
    return e;
}


int main()
{
    while(rd2(n,mod)!=EOF)
    {
        mat ori;
        ori.arr[0][0]=1;ori.arr[0][1]=2;ori.arr[0][2]=1;
        ori.arr[1][0]=0;ori.arr[1][1]=0;ori.arr[1][2]=0;
        ori.arr[2][0]=0;ori.arr[2][1]=0;ori.arr[2][2]=0;
        mat b;
        b.arr[0][0]=0;b.arr[0][1]=2;b.arr[0][2]=0;
        b.arr[1][0]=1;b.arr[1][1]=1;b.arr[1][2]=0;
        b.arr[2][0]=0;b.arr[2][1]=1;b.arr[2][2]=1;
        mat ans;
        ans=power(b,n-1);
        ans=mul(ori,ans);
        printf("%I64d\n",ans.arr[0][0]);
    }
    return 0;
}

1003

http://acm.hdu.edu.cn/showproblem.php?pid=4991

解题思路:

给出n个数的序列,给定m,求这n个数中有多少个长度为m的上升子序列
比如 序列 1 1 2 m=2 那么满足的序列有  1 2      1 2 一共有两个
1<=n<=10000  1<=m<=100 序列中的数字x满足 0<=x<=987654321,结果对123456789取余


统计问题向树状数组那方面想,如果把输入的x看作一个位置的话,那最多需要开辟987654321
个位置,开不出来这样的大数组.考虑统计的时候按该数在所有数中排第几(用rank表示,从小到大)来进行开辟
位置,那么最多开辟10000个位置。也就是将具体的数和该数在所有数中的rank进行映射。
比如数  1 4 4 2 8  那么 1的rank为1,2的rank为2,4的rank为3,8的rank为4,这样每个数都有对应rank的映射。
dp[i][j]  表示以第i个数结尾且长度为j的上升子序列,1<=i<=n,  1<=j<=m
那么有递推公式  dp[i][j]= ∑ dp[k][j-1]  (  k<i&& num[k]<num[i])  num[i]为输入的数
一开始dp[i][1]=1;1<=i<=n,上面的递归公式统计和,就可以用树状数组


以1 4 4 2  m=2为例说明一下树状数组的统计过程
首先准备工作 ,数和rank映射 mp[1]=1 mp[2]=2 mp[4]=3
对应到输入序列的每个位置上则是 p[1]=1 p[2]=3 p[3]=3 p[4]=2 第四个数的rank为2(在所有数中排第二)
dp[1][1]=1 dp[2][1]=1  dp[3][1]=1  dp[4][1]=1
树状数组中每个位置上的值为 0 0 0
dp[i][j] 当j=2时,遍历n,dp[1][2],首先第一个数1载树状数组中的位置排第一,此时它前面没有数,即dp[1][2]=0
然后更新第一个位置更新为dp[1][2-1], 变为 1 0 0 ,然后第二个数4树状数组中的位置排第3,此时它前面有1个1(最前面的那个)
,且那个1表示长度为1且那个1所在的位置对应的输入序列的数一定比当前第三个位置对应的输入序列的数小(1<4),
所以dp[2][2]=1,然后更新第3个位置为dp[2][1],变为1 0 1,可以看出每次更新都是为后面做准备的。
然后dp[3][2]=1,变为1 0 2
然后dp[4][2]=1,变为1 1 2
所以一共的符合条件的序列个数为 ∑dp[i][m]

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <stdlib.h>
#include <cmath>
#include <iomanip>
#include <vector>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <cctype>
#define rd(x) scanf("%d",&x)
#define rd2(x,y)  scanf("%d%d",&x,&y)
#define rd3(x,y,z) scanf("%d%d%d",&x,&y,&z)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
int dx[8]={0,0,-1,1,1,1,-1,-1};
int dy[8]={1,-1,0,0,-1,1,-1,1};
const int mod=123456789;
const int maxn=10010;
int dp[maxn][110];//dp[i][j]代表以第i个数结尾的长度为j的上升子序列的个数
int num[maxn];//输入的原始数据
int tp[maxn];//原始数据备份
map<int,int>mp;//第i个数在所有数中排第几个,first寸的是具体是什么数
int pos[maxn];//第i个位置的数在所有数中排第几个,从小到大
int n,m;
int N;//消除重复元素后有多少个数


//树状数组部分
int c[maxn];

int lowbit(int x)
{
    return x&(-x);
}

void add(int p,int val)
{
    while(p<=N)
    {
        c[p]+=val;
        if(c[p]>=mod)
            c[p]%=mod;
        p+=lowbit(p);
    }
}

int sum(int p)
{
    int ans=0;
    while(p>0)
    {
        ans+=c[p];
        if(ans>=mod)
            ans%=mod;
        p-=lowbit(p);
    }
    return ans;
}
//树状数组结束

int main()
{
    while(rd2(n,m)!=EOF)
    {
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++)
        {
            rd(num[i]);
            tp[i]=num[i];
            dp[i][1]=1;
        }

        sort(tp+1,tp+1+n);
        N=unique(tp+1,tp+1+n)-(tp+1);//把重复元素挪到后面去,一共有N个不同的元素,分别存在tp[1]到tp[N]中

        for(int i=1;i<=N;i++)
            mp[tp[i]]=i;//具体的数和排第几映射

        for(int i=1;i<=n;i++)
            pos[i]=mp[num[i]];//输入位置和排第几映射

        for(int j=2;j<=m;j++)
        {
            memset(c,0,sizeof(c));
            for(int i=1;i<=n;i++)
            {
                dp[i][j]=sum(pos[i]-1);
                add(pos[i],dp[i][j-1]);//j-1位,给下面的循环做准备
            }
        }
        int ans=0;
        for(int i=1;i<=n;i++)
        {
            ans+=dp[i][m];
            if(ans>=mod)
                ans%=mod;
        }
        printf("%d\n",ans);
    }
    return 0;
}



[BestCoder] Round #8