【湖南师大附中培训】2016.3.26测试题 后缀数组+二分+贪心+dp+hash

早上去晚了…八点开始的我快九点才到…那个老师说好的八点半集合结果没看见人…逗我呢…

也就这次考得稍好点了,坐看其他几场各位大爷们AK…Orzfqk Orzyzy Orz龙哥 等等等等…

100+60+10……T3数据范围写的20分m<=5,然后第二个点m=6…然后我特判若m<=5则dfs…坑我呢?


T1

题意:

给个字符串,划分不超过K份,从每份中选出字典序最大的子串,然后从这些串中选出字典序最大的串,求问这个串字典序最小是多少,输出这个串。

样例:

input

2
ababa

output

ba

数据范围

对于 20%的数据, 1 <= length(string) <= 100
对于 40%的数据, 1 <= length(string) <= 2000
对于 100%的数据, 1 <= length(string) <= 100000, 1 <= k <= length(string), 所有的字符都是小写字母


考虑后缀数组+二分

二分字典序大小mid,也就是rank。然后开始检查加上这个字母的这份中是否有字典序大于mid的串,若有则把当前字母分到新的一份,没有则继续枚举,最后看看是否能分K份就行了。

考虑一个子区间中最大字典序的子串,可以发现这必定是这个区间的后缀。但每添加一个字母会产生n个后缀,复杂度不能保证。

所以说改为从后往前枚举,然后比较字典序,这样每次添加字母都只会产生一个新的后缀。这样每次就拿这个后缀和sa[mid]比较即可。

然而比较是O(n)的,这样最坏情况下复杂度是O(n^2)的检验。考虑两个字符串比较字典序大小:可以用二分+hash的方法求出LCP,然后比较LCP的下一个字母即可,可以再logn的时间内比较两字符串的大小。

所以总复杂度是 O(nlog2n)

然而我暴力比较的竟然A掉了……

考场代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

typedef long long LL;
const int SZ = 1000010;

int sa[SZ],rank[SZ],tmp[SZ],k,n,K;

bool cmp(int x,int y)
{
    if(rank[x] != rank[y])  return rank[x] < rank[y];
    else
    {
        int a = x + k < n ? rank[x + k] : -1;
        int b = y + k < n ? rank[y + k] : -1;
        return a < b;
    }
}

void get_sa(char s[])
{
    for(int i = 0;i <= n;i ++)
    {
        sa[i] = i;
        rank[i] = i == n ? -1: s[i];
    }
    for(k = 1;k <= n;k <<= 1)
    {
        sort(sa,sa + 1 + n,cmp);

        tmp[sa[0]] = 1;
        for(int i = 1;i <= n;i ++)
            tmp[sa[i]] = tmp[sa[i - 1]] + cmp(sa[i - 1],sa[i]);
        for(int i = 0;i <= n;i ++)
            rank[i] = tmp[i];
    }
}


char s[SZ];

bool smaller(int l,int r,int x)
{
    for(int i = l;i <= r;i ++)
        if(s[i] != s[x + i - l])
            return s[i] < s[x + i - l];
    return true;
}

int cut[SZ][2],t;

bool check(int mid)
{
    int x = sa[mid],r = n - 1;
    t = 1;
    for(int i = n - 1;i >= 0;i --)
    {
        if(!smaller(i,r,x))
        {
            cut[t][0] = i + 1; cut[t][1] = r;
    //      printf("%d %d %d\n",i,r,t);
            r = i,t ++;
        }
    }
    cut[t][0] = 0; cut[t][1] = r;
//  printf("%d %d %d\n\n",t,mid,x);
    return t <= K;
}

void div()
{
    int l = -1,r = n;
    while(r - l > 1)
    {
        int mid = l + r >> 1;
        if(check(mid)) r = mid;
        else l = mid;
    }
    check(r);
//  printf("%d %d\n",l,r);
}

bool smaller(int l,int r,int x,int y)
{
    for(int i = l;i <= r;i ++)
    {
        if(x + i - l > y) return false;
        if(s[i] != s[x + i - l])
            return s[i] < s[x + i - l];
    }
    return true;
}


int main()
{
    freopen("string.in","r",stdin);
    freopen("string.out","w",stdout);
    scanf("%d%s",&K,s);
    n = strlen(s);
    get_sa(s);
//  for(int i = 0;i <= n;i ++)
//      printf("%s\n",s + sa[i]); puts("");
    div();

    int ansl = n + 1,ansr = n + 1;

//  for(int i = 1;i <= t;i ++)
//      printf("%d %d\n",cut[i][0],cut[i][1]);
    for(int i = 1;i <= t;i ++)
    {
        for(int j = cut[i][0];j <= cut[i][1];j ++)
        {
            if(!smaller(j,cut[i][1],ansl,ansr))
            {
        //      printf("%d %d %d %d\n",j,cut[i][1],ansl,ansr);
                ansl = j; ansr = cut[i][1];
            }
        }
    }
    for(int i = ansl;i <= ansr;i ++)
        printf("%c",s[i]);
    return 0;
}
/*
0 
1 a
2 aba
3 ababa
4 ba
5 baba

3
bababbb

*/

T2

题意:

你买股票,鸡汁的你预测到了未来n天某支股票的价格,记为序列 {ai} 。在第i天,必须选择买入一股或者卖出一股,都是 ai 元。卖出一股时手头上必须有至少一股股票,求最大获利。

样例输入 1

4
1 3 2 4

样例输出 1

4

样例输入 2

4
1 2 3 4

样例输出 2

4

数据范围

对于 10%的数据, 1 <= n <= 200
对于 30%的数据, 1 <= n <= 3000
对于 100%的数据, 1 <= n, a_i <= 200000


考虑30分DP:
dp[i][j]表示第i天手持j个股票的最大获利,转移非常好写:
dp[i][j]=max(dp[i1][j+1]+a[i],dp[i1][j1]a[i])

30分稳拿,我60,骗了30 233

正解:

考虑贪心。因为价钱都是正的,所以到最后肯定尽量让持有的股票最少,尽量让买的次数和卖的次数相同,尽量花少的买入,按贵的卖出。

维护一个卖出的集合S,对于奇数天考虑买入,偶数次卖出。先啥也不管全卖了,但奇数天必须买入否则就没法卖了,于是我们把卖出集合中最便宜的换成买入就可以了,实现的话用个堆就行了。

考完后重写的代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;

typedef long long LL;

priority_queue<int> q;

int main()
{
    freopen("stock.in","r",stdin); 
    freopen("stock.out","w",stdout); 
    int n;
    scanf("%d",&n);
    LL ans = 0;
    for(int i = 1;i <= n;i ++)
    {
        int x;
        scanf("%d",&x);
        q.push(-x);
        ans += x;
        if(i & 1)
        {
            int t = -q.top(); q.pop();
            ans -= t * 2;
        }
    }
    printf("%lld",ans);

    return 0;
}


T3

题意:

给一个长度为2*n+1的序列,每个位置的数的范围为[0,m],然后给每个位置同时加x,满足x∈[L,R],问有多少种不同的初始序列使其加上任意的x的异或和为0。x不同但初始序列相同算作同一种方案。

输入格式

第一行四个整数 n, m, L 和 R,意义如题目描述所示

输出格式

一行一个整数,表示可能的幸福度方案的数目,结果对 1000000007 取模

样例输入

1 3 1 3

样例输出

12

数据规模

对于 20%的数据, 1 <= n, m <= 5, 1 <= L <= R <= 10
对于 40%的数据, 1 <= n, m <= 100, 1 <= L <= R <= 100
对于 70%的数据, 1 <= n, m <= 500, 1 <= L <= R <= 500
对于 100%的数据, 1 <= n, m <= 1000, 1 <= L <= R <= 1000

样例解释

对于样例,共有以下几种分配幸福度的方案:
(0,1,2)
(0,2,1)
(0,2,3)
(0,3,2)
(1,0,2)
(1,2,0)
(2,0,1)
(2,0,3)
(2,1,0)
(2,3,0)
(3,0,2)
(3,2,0)


20暴搜……还我十分……

先证明一个东西 [L,R]区间内至多只有一个数x能成为答案。

若存在一个x合法,考虑x的某个为1的位,相当于给数列中所有数的这一位取反。因为是2*n+1个,长度为奇数个,若存在一个x+d(d!=0)合法,则考虑d的某个为1的位,相当于把那一位取反,但因为是长度是奇数所以这样会导致异或值必定不为0。

谁会想到这个啊喂

然后就可以这样dp:

dp[i][j]为选到第i个异或为j的方案数,转移很显然

dp[i][j]+=dp[i-1][j^k],其中k为当前位的值

这样是40分

好像还有就是f[L][k]=\sum f[L/2][j] * f[L/2][l],把上面的递推改成分治了,然后容易理解后面的做法…

貌似再往后需要一些奇怪的东西…毒瘤题毁我一生…

FWT什么的,线段树什么的……就这样吧 我不会了233

贴个考场代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

typedef long long LL;
const int SZ = 1000010;
const int mod = 1000000007;

int n,m,l,r;

namespace one{
    const int SZ = 110;
    int ans;
    int num[SZ];
    bool check()
    {
        for(int d = l;d <= r;d ++)
        {
            int x = 0;
            for(int i = 1;i <= n;i ++)
                x ^= num[i] + d;
            if(!x) return true;
        }       
        return false;
    }
    void dfs(int pos)
    {
        if(pos == n + 1)
        {
            if(check()) ans ++;
            return ;
        }
        for(int i = 0;i <= m;i ++)
        {
            num[pos] = i;
            dfs(pos + 1);
            num[pos] = 0;
        }
    }

    int solve()
    {
        dfs(1);
        return ans;
    }
}

namespace two{
    const int SZ = 3010;
    int dp[SZ][SZ];

    int DP(int x,int y)
    {
        memset(dp,0,sizeof(dp));
        dp[0][0] = 1;
        for(int i = 1;i <= n;i ++)
        {
            for(int j = 0;j <= 256;j ++)
            {
                for(int k = x;k <= y;k ++)
                {
                    dp[i][j] = (dp[i][j] + dp[i - 1][j ^ k]) % mod;
                }
            }
        }
        return dp[n][0];
    }

    int solve()
    {
        return 0;
    }

}

int main()
{
    freopen("xor.in","r",stdin);
    freopen("xor.out","w",stdout);
    scanf("%d%d%d%d",&n,&m,&l,&r);
    n = n * 2 + 1;
    if(m <= 5)
    {
        printf("%d\n",one :: solve());
    }
    else
    {
        printf("%d\n",two :: solve());
    }
    return 0;
}

/*

第i位,异或得j,当前是k 
dp[i][j] += dp[i - 1][j ^ k]

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值