八月三十一做题心得

1>>

      如果说,有一个序列的数,这个序列的数字个数为n,那么通过对这个序列的数字+1,-1的操作后,最多可以有多少个相同的数字。

   要嘛有n个数字是相同的,这n个数的和正好整除n,要么就是n-1个数字,排序之后,去掉最大值,或者最小值。和除以n-1就可以得到这个平均数字。

(0条未读通知) 代码查看 (nowcoder.com)

2>>n层完全k叉树有多少长度为m的路径

其实大致来讲 一个节点u必须大于i  一个节点v必须在i内部 就是这个链子长度一个在i内一个不在i内

小结论:n层k叉树,总共有(k^n-1)/k-1个节点

2叉树

1 3 7 对应公式为(2^n-1)/1

3叉树

1 4 13 对应公式为(3^n-1)/2

4叉树

1 5 21对应公式为(4^n-1)/3

...

n层k叉树,总共有(k^n-1)/k-1个节点

其实这就是在计算等比数列的总和嘛!

等比数列通项为:

a(n)=a1*q^(n-1)

S(n)=a1*(1-q^n)/(1-q)

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

inline ll read() {
    char ch;
    ll x = 0;
    bool f = true;
    for (ch = getchar(); !isdigit(ch); ch = getchar())
        if (ch == '-')
            f ^= f;
    for (; isdigit(ch); ch = getchar())
        x = (x << 3) + (x << 1) + ch - 48;
    return f ? x : -x;
}

template <int T>
struct ModInt {
    const static int MD = T;
    int x;
    ModInt(ll x = 0)
        : x(x % MD) {}
    int get() { return x; }
    ModInt operator+(const ModInt& that) const {
        int x0 = x + that.x;
        return ModInt(x0 < MD ? x0 : x0 - MD);
    }
    ModInt operator-(const ModInt& that) const {
        int x0 = x - that.x;
        return ModInt(x0 < MD ? x0 + MD : x0);
    }
    ModInt operator*(const ModInt& that) const {
        return ModInt((long long)x * that.x % MD);
    }
    ModInt operator/(const ModInt& that) const {
        return *this * that.inverse();
    }
    void operator+=(const ModInt& that) {
        x += that.x;
        if (x >= MD)
            x -= MD;
    }
    void operator-=(const ModInt& that) {
        x -= that.x;
        if (x < 0)
            x += MD;
    }
    void operator*=(const ModInt& that) { x = (long long)x * that.x % MD; }
    void operator/=(const ModInt& that) { *this = *this / that; }
    ModInt inverse() const {
        int a = x, b = MD, u = 1, v = 0;
        while (b) {
            int t = a / b;
            a -= t * b;
            std::swap(a, b);
            u -= t * v;
            std::swap(u, v);
        }
        if (u < 0)
            u += MD;
        return u;
    }
    friend ostream& operator<<(ostream& os, ModInt x) {
        os << x.get();
        return os;
    }
};
const int mod = 1e9 + 7;
typedef ModInt<mod> mint;

mint ksm(int n, mint m) {
    mint ans = 1;
    for (; n; n >>= 1, m *= m)
        if (n & 1)
            ans *= m;
    return ans;
}

int main() {
    ll n = read(), m = read(), k = read();
    mint ans = 0;
    for (int i = min(m,n); i >= (m + 1) / 2; i--) {
        mint res = ((ksm(n, k) - 1) / (k - 1)) - ((ksm(i, k) - 1) / (k - 1));
        if (i != m) {
            res *= k - 1;
            res *= ksm(m - i - 1, k);
        }
        if (i * 2 == m) {
            res /= 2;
        }
        ans += res;
    }
    cout << ans << "\n";
    return 0;
}

3>>牛牛的回文串:牛牛的回文串 (nowcoder.com)

题解:

就是说通过对字符串的处理,获得一个回文串。增加一个字符,删除一个字符,以及改变一个字符。最短路首先得到i到j变化的最小值情况

可以通过floyard得出着这几中变换的最小值。

删除一个字符和增加一个字符可以是等价的。

(1)可以直接删除(增加)一个字符。

(2)可以将该字符变成另外一个字符在增加或者删除。

(3)可以增加一个字符后,在改变字符

(4)一边的字符变成了j,另一边的字符增加一个在变成了j

#include<bits/stdc++.h>
using namespace std;
#define ll long long
/*
 解决一个回文串可以发现,添加一个字符和删除一个字符是等价的,删除一个字符相当于在另一侧添加一个字符,所以只需要计算删除一个字符
 所需要的的代价即可,删除一个字符可以用过
 1. 先将该字符change成另一个字符再删除
 2. 添加任意一个字符再修改成该字符相当于添加
 3. 将该字符替换成另一个字符c在通过添加另一个字符
 4. 将该字符替换成另一个字符c,再任意添加一个字符c1,再将c1替换成c
 可以先用floyd来求出任意两个字符转换的最小代价,再去考虑上述四种情况求出cost
 然后使用dp来解决问题
 dp状态转移方程如下
 dp[i][j]表示i~j是回文串的最小代价
 从后往前和从前往后其实都一样
 然后考虑dp[i+1~n]dp[j-1~n]都是已经处理好的了,
 所以
 if(s[i]==s[j])dp[i][j]=dp[i+1][j-1];
 考虑不相等的情况
 1. 可以将s[i]删除val =  dp[i+1][j] + cost[s[i]-'a']
 2. 可以将s[j]删除val =  dp[i][j-1] + cost[s[j]-'a']
 3. 可以将s[i],s[j] 替换成某个字符 k 
 4. 可以将s[i]替换成s[j],或者s[j]替换成s[i]

 */
const int N  = 60;
ll dp[N][N];
ll cost[30],change[30][30];
ll cost_erase[30],cost_add[30];
const ll INF =  1e15+10;
char s[N];
void floyd(){
    for(int k=0;k<26;k++){
        for(int i=0;i<26;i++){
            for(int j=0;j<26;j++){
                change[i][j]=min(change[i][j],change[i][k]+change[k][j]);
            }
        }
    }
    for(int i=0;i<26;i++){
        for(int j=0;j<26;j++){
            cost[i]=min(cost[i],min(cost_add[i],cost_erase[i]));
            cost[i]=min(cost[i],change[i][j]+min(cost_add[j],cost_erase[j]));
            cost[i]=min(cost[i],cost_add[j]+change[j][i]);
            for(int k=0;k<26;k++){
                cost[i]=min(change[i][j]+cost_add[k]+change[k][j],cost[i]);
            }
        }
    }
}
int main(){
    scanf("%s",s+1);
    int m;
    scanf("%d",&m);
    for(int i=0;i<26;i++){
        cost_erase[i]=cost_add[i]=cost[i]=INF;
        for(int j=0;j<26;j++){
            change[i][j]=INF;
        }
    }
    while(m--){
        char op[20],ch[2],ch1[2];
        int val;
        scanf("%s",op);
        if(op[0]=='e'){scanf("%s%d",ch,&val);cost_erase[ch[0]-'a']=min(cost_erase[ch[0]-'a'],1ll*val);}
        if(op[0]=='a'){scanf("%s%d",ch,&val);cost_add[ch[0]-'a']=min(cost_add[ch[0]-'a'],1ll*val);}
        if(op[0]=='c'){scanf("%s%s%d",ch,ch1,&val);change[ch[0]-'a'][ch1[0]-'a']=min(change[ch[0]-'a'][ch1[0]-'a'],1ll*val);}
    }
    floyd();
    int len = strlen(s+1);
    for(int i=len;i>=1;i--){
        for(int j=i+1;j<=len;j++){
            dp[i][j]=INF;
            if(s[i]==s[j])dp[i][j]=dp[i+1][j-1];
            dp[i][j]=min(dp[i][j],dp[i][j-1]+cost[s[j]-'a']);
            dp[i][j]=min(dp[i][j],dp[i+1][j]+cost[s[i]-'a']);
            dp[i][j]=min(dp[i][j],dp[i+1][j-1]+min(change[s[i]-'a'][s[j]-'a'],change[s[j]-'a'][s[i]-'a']));
            for(int k=0;k<26;k++){
                dp[i][j]=min(dp[i][j],dp[i+1][j-1]+change[s[i]-'a'][k]+change[s[j]-'a'][k]);
            }
        }
    }
    if(dp[1][len]==INF)printf("-1\n");
    else printf("%lld\n",dp[1][len]);
}

4>>给你一个数字串,问你有多少个序列的数字可以被3整除

dp的思想。

首先dp[i][j],构造一个二维的dp数组,第一维的i,表示的是当前位置,j可以去枚举余数。

则该dp的数值就是到了该位置,余数为j的序列数量。

因为是序列的数量,所以就是前面位置的所有数量和,之前以及达到的数量,以及可以新添的数量。

可以轻松的得到,dp[i][j]=max(dp[i-1][j]+dp[i-1][(j-a[i]+3)%3]);

答案是对1e9+7去取模。

则首先可以对每一个数位进行预处理,得到余数。在进行之后的每一次dp动态规划。


for(int i=1;s[i];i++)
a[i]=s[i]%3;

dp[0][0]=0;

 for(int i=1;i<=len;i++)
    {
        for(int k=0;k<=2;k++)
        {
            dp[i][k]=(dp[i-1][k]+dp[i-1][(k-a[i]+3)%3])%mod;
        }
        dp[i][a[i]]++;
    }

5>>数位dp的初步了解:

数位dp就是求在一个区间【l,r】范围内,满足某种约束的数字的数量,总和,平方。一般采用的是记忆化的搜索方法。一般是通过前缀和和差分的方法来得到的结果,(1,l-1)与(1,r)的差值。

比如 0-12345.则 对于高位的数字,就有着相应的限制,比如说如果前面有了123则第四位就最高是4.通过limit的传到可以知道该位置的最高位up是多少,默认是9.pos是指当前到了第几位。

一般来讲,就是说,从高位到低位一次的进行递归处理,递归到最后一层,然后慢慢回溯到直到的结果。 如果在中途过程中遇到了之前已经标记过的数字,如果limit没有限制并且不违反要求的情况下就直接return结果就可以。

#include<bits/stdc++.h>
using namespace std;
int l,r;
int a[15],f[15][2];
int dfs(int num,int pre,int k,bool limit)
// 当前位置,上一位置的值,上一位置是不是6,前面的位置是不是上界
{
  if(num==0)return 1;
  //能找到最后状态,说明当前方案可行,返回1
  if(!limit&&f[num][k]!=-1)return f[num][k];
  //如果当前的pos位置已经之前被访问过,并且同样的之前都没有一直是上界,那么我在这个位置再往下搜是毫无意义的,因为搜索到的答案跟之前一样都是那么多种
  //但是如果当前位置为6的话,那么搜索情况就又要不同了,所以需要一个k标记是否为6,6的情况需要单独考虑,因为后面可选择的个数发生变化
  //所以dp[i][j]表示遍历到当前位置,当前位置的上一位是不是6的情况
  //如果已经是上界,那么该位置的大小是会被限制的,就不能借用之前的情况,需要重新从0-上界搜
  int up,sum=0;
  if(!limit)up=9;
  else up=a[num];
  //如果之前选择的全部都为上界,那么这个时候的上界为a[pos],否则为是9
  for(int i=0;i<=up;i++)
  {
    if(i==4)continue;//当前位置为4
    if(i==2&&pre==6)continue;//上一位为6,并且当前位为2
    //都是保证枚举合法性
    sum+=dfs(num-1,i,i==6,limit&&i==a[num]);
    //从高到低位,上一位数字,上一位是否为6
    //上一位是否选择了上界,没有选择上界后面的limit就是0,那么当前这一位就可以选择到9,limit代表了上上位置的状态,上上位置只要选择了该位的上界,并且上一位也选择了上界,那么当前位置的limit=1,就不能选9
  }
  if(!limit)f[num][k]=sum;
  //用来更新前面的答案的,节省前面数位有存在不为上界的时间
  //因为当前面位置都为上界的时候,我的这个时候可行个数是与之前的不同的,就不能用来继承
    return sum;//枚举的答案
}
int slove(int x)
{
  int num=0;
  memset(a,0,sizeof(a));
  while(x!=0)
  {
    num++;
    a[num]=x%10;
    x/=10;
  }
  // cout<<num<<endl;
  return dfs(num,-1,0,1);
}
int main()
{ memset(f,-1,sizeof(f));
  while(scanf("%d%d",&l,&r)&&l!=0&&r!=0)
  {
    // cout<<l<<" "<<r<<endl;
    printf("%d\n",slove(r)-slove(l-1));
  }
  return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值