牛客练习赛63(C二分套三分 D 二维dp E(kmp+矩阵快速幂) F nim博弈推论 树上主席树)

题目链接

C-牛牛的揠苗助长

牛客练习赛63(C二分套三分 D 二维dp E(kmp+矩阵快速幂) F nim博弈推论 树上主席树)_第1张图片

二分天数然后三分高度check即可。

#include
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define per(i,a,b) for(int i=a;i>=(b);--i)
#define mem(a,x) memset(a,x,sizeof(a))
#define pb push_back
#define pi pair
#define mk make_pair
using namespace std;
typedef long long ll;
ll gcd(ll a,ll b) { return b?gcd(b,a%b):a;}

const int N=1e5+10;
ll a[N],n,b[N];
ll run(ll mid)
{
    ll ans=0;
    rep(i,1,n) ans+=abs(b[i]-mid);
    return ans;
}
int cal(ll mid)
{
    ll d=mid%n;
    for(ll i=1;i<=n;++i) {
        b[i]=a[i]+mid/n;
        if(d>=i) b[i]++;
    }


    ll l=b[1],r=b[1];
    rep(i,1,n) l=min(l,b[i]),r=max(r,b[i]);


    while(l+10>1;
        ll m2=m1+r>>1;
        if(run(m1)>=run(m2))l=m1;
        else r=m2;
    }

    for(ll i=l;i<=r;++i) if(run(i)<=mid) return 1;
    return 0;
}

void solve()
{
    cin>>n;
    rep(i,1,n) scanf("%lld",&a[i]);
    ll l=1,r=1e15,ans=1;
    while(l<=r){
        ll mid=l+r>>1;
        if(cal(mid)) r=mid-1,ans=mid;
        else l=mid+1;
    }
    cout<

D-牛牛的01限定串

牛客练习赛63(C二分套三分 D 二维dp E(kmp+矩阵快速幂) F nim博弈推论 树上主席树)_第2张图片

这题我想歪了,难点在于如何计算相似后缀的权值和?我用区间dp去写了

根据官方题解的方法是,后缀转换为前缀,不过转换为矩阵方式也是很妙的方法。

dp[i][j]代表t串第i+j个字符时i个0  j 个1的方案数

#include
using namespace std;
typedef long long ll;
const ll INF=1ll<<60;
const int N=1005;
ll dp0[N][N],dp1[N][N],valpre,valsuf,a[N][N];
int n,x,y,cnt0,cnt1;
char s[N],t[N];
bool ck(int x,int y)
{
    if(x>cnt0||x<0||y>cnt1||y<0)return false;
    return true;
}
int main()
{
    scanf("%d %d %d %lld %lld",&n,&cnt0,&cnt1,&valpre,&valsuf);
    scanf("%s %s",s,t);
    for(int i=0;i<=n;++i)
    {
        for(int j=0;j<=n;++j)
        {
            dp0[i][j]=INF;
            dp1[i][j]=-INF;
        }
    }
    x=y=0;

    for(int i=0;i

 

另一种dp:dp[i][j]代表t串第i个字符 有j个0 串 (或j个1串的最大权值和)

#include
#define inf 0x3f3f3f3f
using namespace std;
const int N=1005;
typedef long long ll;
int n,c0,c1,mn[N][N],mx[N][N],pre,bf,p0[N],p1[N],b0[N],b1[N];
char s[N],t[N];
int main()
{
    scanf("%d%d%d%d%d",&n,&c0,&c1,&pre,&bf);
    scanf("%s",s+1);
    scanf("%s",t+1);
    for(int i=1;i<=n;i++)
    {
        p0[i]=p0[i-1]+(s[i]=='0');
        p1[i]=p1[i-1]+(s[i]=='1');
    }
    for(int i=n;i>=1;i--)
    {
        b0[i]=b0[i+1]+(s[i]=='0');
        b1[i]=b1[i+1]+(s[i]=='1');
    }
    memset(mn,inf,sizeof(mn));
    memset(mx,-inf,sizeof(mx));


    mn[0][0]=mx[0][0]=0;
    for(int i=0;i

E-牛牛的斐波那契字符串

 

牛客练习赛63(C二分套三分 D 二维dp E(kmp+矩阵快速幂) F nim博弈推论 树上主席树)_第3张图片

这是个好题啊,来看下官方题解:

牛客练习赛63(C二分套三分 D 二维dp E(kmp+矩阵快速幂) F nim博弈推论 树上主席树)_第4张图片

意思就是当f[n]字符串足够长的时候就可以用上面的转移方程,字符串连接只有两种情况 c1、c2。但是转移方程是分 奇偶的  怎么写这类矩阵快速幂呢?

构造矩阵如下

 

\begin{bmatrix} dp[i]\\ dp[i-1]\\ 1\\ \end{bmatrix}=\begin{pmatrix} 2 & 1 &c1+c2 \\ 1& 1 &c2 \\ 0&0 &1 \end{pmatrix} \begin{bmatrix} dp[i-2]\\ dp[i-3]\\ 1\\ \end{bmatrix}

就可以完美解决奇偶问题啦。由于每次求了两个dp[i]  所以 快速幂的时候指数n要除2

具体看代码了。

#include
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int MAXN=3;
int i,i0,n,nex[100005],ne[100005];
string pre[55],suf[55],s,str[55];
struct Matrix
{
    ll mat[MAXN][MAXN];
    Matrix() {}
    Matrix operator*(Matrix const &b)const
    {
        Matrix res;
        memset(res.mat, 0, sizeof(res.mat));
        for (int i = 0 ;i < MAXN; i++)
        for (int j = 0; j < MAXN; j++)
        for (int k = 0; k < MAXN; k++)
            res.mat[i][j] = (res.mat[i][j]+this->mat[i][k] * b.mat[k][j]%mod)%mod;
        return res;
    }
};

Matrix pow_mod(Matrix base, ll n)
{
    Matrix res;
    memset(res.mat, 0, sizeof(res.mat));
    for (int i = 0; i < MAXN; i++)
        res.mat[i][i] = 1;
    while (n > 0)
    {
        if (n & 1) res = res*base;
        base = base*base;
        //printf("res.mat:%lld\n",res.mat[0][0]);
        n >>= 1;
    }
    return res;
}

struct MAT
{
    int mat[MAXN][MAXN];
    MAT operator*(const MAT &a)const
    {
        MAT b;
        memset(b.mat,0,sizeof(b.mat));
        for(int i=0;i>=1;
    }
    return r;
}

void get(string b)  //常规处理方法
{

    ne[0]=-1;
    for(int i=0,j=-1;i>n;
    cin>>str[1]>>str[2]>>s;
    int d=0;
    for(int i=3;!d&&i<=n;i++)
    {
        if(i!=3&&str[i-1].length()>=s.length()&&str[i-2].length()>=s.length())d=i;
        else str[i]=str[i-2]+str[i-1];
    }
    if(!d)cout<

 

 

 

 

F-牛牛的树形棋

牛客练习赛63(C二分套三分 D 二维dp E(kmp+矩阵快速幂) F nim博弈推论 树上主席树)_第5张图片

官方的dsu做法太复杂了,代码复杂,这里安利一个 树上主席树的做法(可能我擅长主席树)

不过他的nim博弈分析不错:搬来

牛客练习赛63(C二分套三分 D 二维dp E(kmp+矩阵快速幂) F nim博弈推论 树上主席树)_第6张图片

子树的maxdeep就是子树每个节点的maxdeep值,xor_sum就是每个节点的maxdeep的异或和,max_deep[x]就是x这个子树的max_deep

这里了解一下nim博弈:

n堆石子,每次从一堆中至少选1个石子,无法行动时则输了。

结论:n堆石子异或和为0则先手必败,否则先手必胜

至于怎么先手必胜  比如:3堆石子:1 2 4  异或和sum=7  那么对4这个节点  不考虑4的异或和:7^4=3  那么我只要将4这堆石子变成3  那么剩余的石子的异或和为0,达到了必败态。

了解了nim博弈后来看这句话:

就懂大概做法了。

怎么树上主席树呢?跑一个dfs序或者时间戳  按照dfs序建主席树即可。并不是严格的树上主席树,而是将树变成一维了。

 

#include
using namespace std;
const int N=5e5+10;
typedef long long ll;
int root[N],ls[40*N],rs[40*N],sum[40*N];
int n,m,mx[N],dfn[N],in[N],out[N],cnt,sz;
long long res;
vectorG[N];
void dfs(int u,int fa)
{
    dfn[++sz]=u;//dfs序
    in[u]=sz;
    for(int v:G[u]){
        if(v==fa) continue;
        dfs(v,u);
        mx[u]=max(mx[u],mx[v]+1);
    }
    out[u]=sz;
}
void up(int pre,int &o,int l,int r,int pos)
{
    o=++cnt;

    ls[o]=ls[pre];sum[o]=sum[pre]+1;rs[o]=rs[pre];
    if(l==r) return ;
    int mid=l+r>>1;
    if(pos<=mid) up(ls[pre],ls[o],l,mid,pos);
    else up(rs[pre],rs[o],mid+1,r,pos);
}
int qu(int pre,int o,int l,int r,int pos)
{
    if(l==r) return sum[o]-sum[pre];
    int mid=l+r>>1;
    if(pos<=mid) return qu(ls[pre],ls[o],l,mid,pos);
    return qu(rs[pre],rs[o],mid+1,r,pos);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i=mx[i])continue;
            ans+=qu(root[in[i]-1],root[out[i]],0,n,sum^mx[i]);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

 

你可能感兴趣的:(牛客题解,数据结构---主席树,dp--字符串dp)