Week-1

这篇博客探讨了如何利用动态规划和 Trie 树解决计算机科学中的经典问题,如最大异或和、序列最大收益、最长公共子序列等。文章详细解释了每种算法的思路,提供了高效的解决方案,并给出了相应的 C++ 代码实现。此外,还涉及到了星期计算和阶乘和的问题,展示了解决这类问题的基本技巧和模拟方法。
摘要由CSDN通过智能技术生成

最大异或和

题意: 对一个长度为n的数组,求长度小于m的子数组的最大异或和。

暴力双指针做法复杂度n2会超时。

做法:对于区间和,先预处理出一个前缀异或和,所以题目转化为对数组找两个距离小于m的数使他们的异或值最大,就变成了加了限制条件的最大异或对这道题了。用01trie树做,对于限制条件,可以定义一个cnt数组表示树中每个节点被走过的次数,不在m的区间内的数,他们在树中走过的点cnt全部-1,复杂度 nlogn。

关键点:找区间转化为找点、01trie树找最大异或对、用cnt数组处理限制条件。

代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 100010;

int n,m,s[N],son[N * 31][2],idx,cnt[N * 31];//注意空间开的大小

void add(int x,int v)//加入trie
{
    int p = 0;

    for(int i = 30; i >= 0; i -- )
    {
        int u = x >> i & 1;
        if(!son[p][u])
            son[p][u] = ++ idx;
        p = son[p][u];
        cnt[p] += v;
    }
}

int query(int x)//得到x与trie中存在数的异或最大值
{
    int p = 0,ans = 0;

    for(int i = 30; i >= 0; i -- )
    {
        int u = x >> i & 1;

        if(cnt[son[p][u ^ 1]])//如果能走
        {
            p = son[p][u ^ 1];
            ans |= (1 << i);
        }
        else
            p = son[p][u];
    }
    return ans;
}

int main()
{
    int ans = 0;

    cin>>n>>m;
    for(int i = 1; i <= n; i ++ )//预处理前缀异或和
    {
        cin>>s[i];
        s[i] ^= s[i - 1];
    }

    add(s[0],1);//别忘了把s[0]加入trie
    for(int i = 1; i <= n; i ++ )
    {
        if(i > m)
            add(s[i - m - 1],-1);//删除不在区间内的
        ans = max(ans,query(s[i]));
        add(s[i],1);
    }

    cout<<ans;
    return 0;
}

最大的和

水题,直接上代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 100010;
typedef long long ll;

ll n,a[N],s[N],v[N],s1[N],k;

int main()
{
    ll maxx = 0;

    cin>>n>>k;
    for(int i = 1; i <= n; i ++ )
        cin>>a[i];
    for(int i = 1; i <= n; i ++ )
        cin>>v[i];

    for(int i = 1; i <= n; i ++ )
    {
        if(v[i])
            s[i] = s[i - 1] + a[i];
        else
            s[i] = s[i - 1];
        s1[i] = a[i] + s1[i - 1];
    }

    for(int i = 1; i + k - 1 <= n; i ++ )
    {
        ll x =  s[n] - (s[i + k - 1] - s[i - 1]) + (s1[i + k - 1] - s1[i - 1]);
        maxx = max(maxx,x);
    }

    cout<<maxx;
    return 0;
}

序列最大收益

dp,用 dp[i][j] 表示 i 个数里删j个数且第 i 个不删的最大收益.

状态计算:从倒数第二个开始,可以为0,1,2…i - 1,当倒数第二个为 k 时,表示 k - i 中的数都被删去(共i - k - 1 个),1 - k 中的数怎么删无所谓,所以可以得到 dp[i][j] = max(dp[i][j],dp[k][j - (i - k - 1)] + g[a[k]][a[i]]),状态的递推感觉有点像区间dp的那道石子合并.

代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 210;

int a[N],g[N][N],n,s,m,dp[N][N];//表示i个数里删j个数且第i个不删的最大收益

int main()
{
    cin>>n>>s>>m;
    for(int i = 1; i <= m; i ++ )
        cin>>a[i];

    for(int i = 1; i <= n; i ++ )
        for(int j = 1; j <= n; j ++ )
            cin>>g[i][j];

    for(int i = 1; i <= m; i ++ )
        for(int j = 0; j <= s; j ++ )
            for(int k = 0; k <= i - 1; k ++ )//k表示删完之后第i个数上一个的下标
                if(i - k - 1 <= j)
                    dp[i][j] = max(dp[i][j],dp[k][j - (i - k - 1)] + g[a[k]][a[i]]);

    cout<<dp[m][s];
    return 0;
}

不同路径数

普通的dfs,代码:

#include<bits/stdc++.h>
using namespace std;

set<int>s;

int n,m,k;
int p[10][10];
int dx[] = {0,1,-1,0};
int dy[] = {1,0,0,-1};
void dfs(int x,int y,int num,int K)
{
    if(K == k + 1)
       {
           s.insert(num);
           return ;
       }
    for(int i = 0; i < 4; i ++ )
    {
        int nx = x + dx[i];
        int ny = y + dy[i];
        if(nx > 0 && nx <= n && ny > 0 && ny <= m)
        {
            dfs(nx,ny,num * 10 + p[nx][ny],K + 1);
        }
    }
}
int main()
{
    cin>>n>>m>>k;
    for(int i = 1; i <= n; i ++ )
        for(int j = 1; j <= m; j ++ )
        cin>>p[i][j];
     for(int i = 1; i <= n; i ++ )
        for(int j = 1; j <= m; j ++ )
        dfs(i,j,p[i][j],1);
        cout<<s.size();
    return 0;
}

最长公共子序列Ⅱ

数据变大了,不能用最长公共子序列的dp板子,想着靠a中没有重复元素优化(还是太菜了想不出来)

y总思路:新开一个数组c,由于a中元素不重复,将b中与a相同的元素的下标存入c,不存在则置-1,所以只需要c中是上升序列就一定存在b和a是公共序列,问题转化为求c的长上升子序列问题,复杂度降为nlogn。

代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 1000010;

int q[N],a[N],b[N],c[N],id[N],n;

int main()
{
    cin>>n;
    memset(id,-1,sizeof(id));//初始化不能忘
    
    for(int i = 0; i < n; i ++ )
    {
        cin>>a[i];
        id[a[i]] = i;//相当于一个map,存的下标,用来求c
    }

    for(int i = 0; i < n; i ++ )
    {
        cin>>b[i];
        if(id[b[i]] != -1)
            c[i] = id[b[i]];
        else
            c[i] = -1;
    }
    
    q[0] = -1;//先放入一个数,方便比较
    int hh = 0;

    for(int i = 0; i < n; i ++ )//最长上升子序列
    {
        if(c[i] != -1)
        {
            if(c[i] > q[hh])
                q[++ hh] = c[i];
            else
            {
                int t = lower_bound(q,q + hh + 1,c[i]) - q;
                q[t] = c[i];
            }
        }
    }

    cout<<hh;
    return 0;
}


星期几

最古老的模拟题,但其实挺麻烦的,想到自己大一蓝桥杯就有一道这样的填空调一个多小时都调不出来,当时郁闷了好久哈哈,现在不用调试就ac了

代码:

#include<bits/stdc++.h>
using namespace std;

int d,y;
string m;
string s[12] = {"January","February","March","April","May","June","July","August","September","October","November","December"};
int c[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
string w[7] = {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"};

bool is(int x)
{
    if(x % 400 == 0)
        return true;
    if((x % 4) == 0 && x % 100)
        return true;
    return false;
}

int main()
{
    while(cin>>d>>m>>y)
    {
        int res = 0,day = 0;
        for(int i = 1; i <= y - 1; i ++ )
            if(is(i))
                res ++ ;

        day += ((y - 1) * 365 + res);

        int month = 0;

        for(int i = 0; i < 12; i ++ )
            if(m == s[i])
                 month = i;

        for(int i = 0; i <= month - 1; i ++ )
        {
            if(i == 1)
            {
                if(is(y))
                    day += 29;
                else
                    day += 28;
                continue;
            }
            day += c[i];
        }

        day += d;

        day %= 7;
        cout<<w[day]<<endl;
    }
    return 0;
}

阶乘的和

0的阶乘是1!!

#include<bits/stdc++.h>
using namespace std;

int n,a[10];

int fun(int x)
{
    int res = 1;
    while(x)
    {
        res *= x;
        x --;
    }
    return res;
}

int main()
{
    for(int i = 1; i <= 9; i ++ )
        a[i] = fun(i);
    a[0] = 1;

    while(cin>>n && n >= 0)
    {
        if(n == 0)
        {
            cout<<"NO"<<endl;
            continue;
        }
        bool flag = false;
        for(int i = 0; i < (1 << 10); i ++ )
        {
            int res = 0;
            bitset<32>p(i);
            for(int j = 0; j < 10; j ++ )
                if(p[j])
                    res += a[j];  
          
            if(res == n)
            {
                cout<<"YES"<<endl;
                flag = true;
                break;
            }
        }

        if(!flag)
            cout<<"NO"<<endl;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值