河南萌新联赛2024第(四)场的个人题解(适合小白)

河南萌新联赛2024第(四)场的题目链接

文章目录

A

A题目链接
思路:
  sum=计算原来每个连通块的士兵数量总和的平方。
  枚举每个点,若破坏当前点,当前点所在的连通块的计算值,记录ma=没破坏前的计算值-破坏后的计算值,记录最大值

涉及的知识:tarjan算法
不明白的话,可以看我的第二篇博客LCA算法

有用的知识:__int128
  占用128字节的整数存储类型,范围为 - 2127 ~ 2127-1,由于不在 C++C++ 标准内,没有配套的 printf scanf cin cout 输入输出,只能手写。
  如果遇到 long long ( __int64)开不下的情况,可以使用 __int128 来博一把!

inline __int128 read()
{
    __int128 x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
inline void print(__int128 x)
{
    if(x<0)
    {
        putchar('-');
        x=-x;
    }
    if(x>9)
        print(x/10);
    putchar(x%10+'0');
}

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1000100;
int a[N];
int h[N],e[N*2],ne[N*2],idx;
int dfn[N],low[N],timestamp;
__int128 sum2[N];
int place[N],cnt;
vector<int> ge[N];
//(没破坏前)sum2[i]为第i个连通块的士兵总和
//cnt为每块连通块打上编号,place[i]为i所在第几个连通块
//ge[i]代表i是割点,割完i后所产生的每个连通块士兵总和
void print(__int128 x)
{
    if(x/10==0) cout<<(int)x;
    else
    {
        print(x/10);
        cout<<(int)(x%10);
    }
}
void add(int x,int y)//建邻接表
{
    e[idx]=y,ne[idx]=h[x],h[x]=idx++;
}
int tarjan(int u,int from,int fa)
{
    int s=a[u];
    place[u]=cnt;
    sum2[cnt] += a[u];
      
    dfn[u]=low[u]=++timestamp;//盖戳
    for(int i=h[u];~i;i=ne[i])//枚举u的儿子们
    {
        int j=e[i];
        if(!dfn[j])//没遍历过
        {
            int g=tarjan(j,i,u);
            s+=g;
            low[u]=min(low[u],low[j]);
            if(dfn[u]<=low[j])//u是割点,压入j子树之和
            {
                ge[u].push_back(g);
            }
        }
        else if(i != (from ^ 1))
            low[u]=min(low[u],dfn[j]);
    }
     
    return s;
}
signed main()
{
    int n,m;
    cin>>n>>m;
    memset(h,-1,sizeof h);
    for(int i=1;i<=m;i++)
    {
        int u,v;
        cin>>u>>v;
        add(u,v);
        add(v,u);
    }
    for(int i=1;i<=n;i++)
        cin>>a[i];
    __int128 sum=0;//没破坏前的总士兵平方
    for(int i=1;i<=n;i++)
    {
        if(!dfn[i])//每个连通块
        {   
            int s=tarjan(i,-1,-1);
            sum+=(__int128)s*s;
            cnt++;
        }
    }
    __int128 ma=0;
    for(int i=1;i<=n;i++)
    {
        __int128 o=0,p=0;
        for(auto j:ge[i])//i是割点
        {
            o+=(__int128)j*j;
            p+=(__int128)j;
        }
        o+= (__int128)(sum2[place[i]]-a[i]-p) * (__int128)(sum2[place[i]]-a[i]-p);
        ma=max(ma,(__int128)sum2[place[i]]*sum2[place[i]]-o);
    }
    print(sum-ma);
}

B

B题目链接
思路:找最大同或对(同1,异0),字典树trie

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+9;;
 
int n,m;
int a[N], son[N*32][2], idx;
 
void insert(int x)//建树
{
    int p = 0;
    for (int i = m-1; i >= 0; i -- )
    {
        int s = x >> i & 1;
        if (!son[p][s]) son[p][s] = ++ idx;
        p = son[p][s];
    }
}
 
int search(int x)
{
    int p = 0, res = 0;
    for (int i = m-1; i >= 0; i -- )
    {
        int s = x >> i & 1;
        if (son[p][s])
        {
            res += 1 << i;
            p = son[p][s];
        }
        else p = son[p][!s];
    }
    return res;
}
 
signed main()
{
    cin>>n>>m;
    int ans = 0;
    for (int i = 1 ; i <= n ; i++) {
        int a[i];
        cin >> a[i];
        ans = max(ans, search(a[i]));//为避免自己会碰上自己
        insert(a[i]);
    }
    cout << ans <<endl;
    return 0;
}

C

C题目链接
思路:球同盒子不同可空模型(挡板法)
3个岗位10个人,分别需1,2,3,剩下4人
4人去3岗位:C(4+3-1,3-1)=15
3人去3岗位:C(3+3-1,3-1)=10
2人去3岗位:C(2+3-1,3-1)=6
1人去3岗位:C(1+3-1,3-1)=3
0人去3岗位:C(0+3-1,3-1)=1
总结公式:全部相加为C(3+4,3)=35

#define int long long
const int N = 2010, mod = 998244353;
int c[N][N];
void init()
{
    for (int i = 0; i < N; i ++ )
        for (int j = 0; j <= i; j ++ )
            if (!j) c[i][j] = 1;
            else c[i][j] = ((c[i - 1][j] + c[i - 1][j - 1]) % mod+ mod)%mod;
}
signed main()
{
    int n,m;
    cin>>n>>m;
    init();
    for(int i=0;i<n;i++)
    {
        int a;
        cin>>a;
        m-=a;
    }
    int res=0;
 	 res += (c[m+n][n] % mod + mod)%mod;
    cout<<res;
    return 0;
}

E

E题目链接
思路:
  预处理:欧拉筛选素数,(因为2≤x≤y≤1e8),二分找到相应的下标,计算出个数,只有质数2是偶数才能消掉,2配上3,5无敌了

相关知识:与(AND)运算
  运算规则:全一为一,有零为零。即只有两个操作数对应的二进制位都为1时,结果才为1,其他情况均为0(也可以说,只要有0,结果就为0)

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e8+10;
 
int primes[N], cnt;
bool st[N];
 
void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}
 
 
signed main()
{
    int n;
    cin >> n;
    get_primes(N);
    while(n--)
    {
        int x,y;
        cin>>x>>y;
         
        int index1=0,index2=0;
        int l = 0, r = cnt - 1;
        while (l < r)
        {
            int mid = l + r >> 1;
            if (primes[mid]>=x) r = mid;
            else l = mid + 1;
        }
        index1=r;
        l = 0, r = cnt - 1;
        while (l < r)
        {
            int mid = l + r >> 1;
            if (primes[mid]>=y) r = mid;
            else l = mid + 1;
        }
        index2=r;
        if(primes[index2]!=y)
            index2-=1;
        int res = index2-index1+1;
        cout << res << " ";
        if(x<=2 && y>=5)
        {
            cout<<res-2<<endl;;
        }
        else
            cout<<0<<endl;
    }
   
}

G

G题目链接
思路:
  操作0,把 [ l , r ] 区间替换成全字母,用memset(起始下标,要填的字母,长度)函数实现;
  操作1和操作2,正反一样,所以只考虑正:
  用欧拉筛选素数的方法,找出st[]:素数是本身最大质因子,合数是最大因子(如st[5]=5,st[8]=4,st[16]=8)成对的因子(15,24,2*8),来找出x(双指针的l已固定最开始,r(相当于x)用st[len]来找出),然后比较字符串用memcmp(str1,str2,要比较的长度)函数实现(要比较的长度从始至终为(r-l+1)-x)

涉及的知识:欧拉筛选素数

const int N= 100000000;(可开大)
 
int primes[N], cnt;
int st[N];//正常的st表是表示是否被筛过了,这里的st表是【素数是本身,合数是最小质因子】

void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i])
        {
            primes[cnt ++ ] = i;
            st[i]=i;//原版:没有
        }
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = primes[j];//原版:st[...]=true
            if (i % primes[j] == 0) break;
        }
    }
}
int main()
{
    get_primes(1000000000);//筛选出一亿范围内的素数
}

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
 
const int N= 2000010;
 
int primes[N], cnt;
int st[N];
 
void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) 
        {
            primes[cnt ++ ] = i;
            st[i]=i;
        }
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = i;
            if (i % primes[j] == 0) break;
        }
    }
}
signed main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    get_primes(N);
    int n,m;
    cin >> n >>m;
    char s[N];
    cin>>s+1;
    while(m--)
    {
        int o,l,r;
        cin>>o>>l>>r;
        if(o==0)
        {
            char c;
            cin>>c;
            memset(s+l,c,r-l+1);
        }
        else
        {
            int len=r-l+1;
            int ans=len;
            while(len>1)
            {
                int x=ans/st[len];
                if(!memcmp(s+l,s+l+x,(r-l+1)-x))
                {
                    ans=x;
                }
                len/=st[len];
            }
            cout<<ans<<endl;
        }
    }
}

I

I题目链接
思路:x的子树(包括自己)的大小 * y的子树(包括自己)的大小,乘法原理

#include<bits/stdc++.h>
using namespace std;
#define int long long
 
const int N=300010;
vector<int> g[N];
bool vis[N];
int sz[N];
void dfs(int x)
{
    vis[x]=true;
    for(auto u:g[x])
    {
        if(vis[u])
            continue;
        dfs(u);
        sz[x]+=sz[u];
    }
}
signed main()
{
    int n,x,y,u,v;
    cin>>n>>x>>y;
    for(int i=0;i<n-1;i++)
    {
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    for(int i=1;i<=n;i++)
        sz[i]=1;
    dfs(x);
    int res1=sz[y];
    memset(vis,false,sizeof vis);
    for(int i=1;i<=n;i++)
        sz[i]=1;
    dfs(y);
    int res2=sz[x];
    int ans=(res1)*(res2);
    cout<<ans<<endl;
     
}

J

J题目链接
思路:
  题目给的数列是斐波那契数列,预处理打表出斐波那契数列。斐波那契数列增长很快,到了F[25]=121393,超过了n的范围,所以生成的点集最多24个点。用输入的x和k写个for循环便能得出生成的点集。
  题意中的"对于其中任意一棵子树,该子树的根节点可以到达该子树内任意一点(包括子树根节点自己)""机宝想找一个结点,使得该结点能到达点集中任意一点,并且在所有符合要求的结点中,该结点距离根节点最远。"显然是找生成点集中所有点的最近公共祖先,因为只有最近公共祖先能达到生成点集中的所有点且离根节点最远。LCA(x1,x2,x3,…)=LCA(LCA(x1,x2),x3,…),可以用倍增法先找出两个点的最近公共祖先,再用这个最近公共祖先与下一个点求出最近公共祖先,依此类推,最多求23次
  时间复杂度是O(23qlogn)

涉及的知识:LCA算法
不明白的话,可以看我的第二篇博客LCA算法

#include<bits/stdc++.h>
using namespace std;
#define int long long
  
const int N=200010,M=N*2;
int h[N],ne[M],e[M],idx;//邻接表必备四件套
int depth[N],fa[N][30];//lca倍增法
int fibo[30];//斐波那契数列
int n,r,q,u,v;
  
void add(int a,int b)//邻接表
{
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}
void bfs()//bfs建st表
{
    memset(depth,0x3f,sizeof depth);
    queue<int> q;
    q.push(r);
    depth[r]=1,depth[0]=0;
    while(q.size())
    {
        int t=q.front();
        q.pop();
  
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(depth[j]>depth[t]+1)
            {
                depth[j]=depth[t]+1;
                q.push(j);
                fa[j][0]=t;
                for(int k=1;k<=25;k++)
                    fa[j][k]=fa[fa[j][k-1]][k-1];
            }
        }
    }
}
int lca(int a,int b)//找a与b最近公共祖先
{
    if(depth[a]<depth[b])
        swap(a,b);
    for(int k=25;k>=0;k--)
        if(depth[fa[a][k]]>=depth[b])
            a=fa[a][k];
    if(a==b)
        return a;
  
    for(int k=25;k>=0;k--)
        if(fa[a][k]!=fa[b][k])
        {
            a=fa[a][k];
            b=fa[b][k];
        }
    return fa[a][0];
}
signed main()
{
    //初始化
    memset(h,-1,sizeof h);
    fibo[1]=1;
    fibo[2]=2;
    for(int i=3;i<=25;i++)//斐波那契数列第25项超过100000
    {
        fibo[i]=fibo[i-1]+fibo[i-2];
    }
    cin>>n>>r>>q;
    for(int i=0;i<n-1;i++)
    {
        cin>>u>>v;
        add(u,v);
        add(v,u);
    }
    bfs();
    for(int i=0;i<q;i++)
    {
        int x,k;
        cin>>x>>k;
          
        if(k>=25)
            cout<<0<<endl;
        else
        {
            int node1=x+fibo[k];
            if(node1>n)
            {
                cout<<0<<endl;
                continue;
            }
            for(int i=k+1;x+fibo[i]<=n;i++)
            {
                int node2=x+fibo[i];
                node1=lca(node1,node2);
            }
            cout<<node1<<endl;
        }
    }
  
}

K

K题目链接
思路:树状数组,acwing的楼兰图腾一样
  找每个数左边比它大 * 右边比它小(乘法原理),注意相同的(容斥原理)
  找每个数左边比它小 * 右边比它大,注意相同的

#include<bits/stdc++.h>
using namespace std;
 
#define int long long
 
const int N = 100010;
 
int n;
int a[N];
int tr[N];
int Greater[N], lower[N];
int l[N],r[N];
 
int lowbit(int x)
{
    return x & -x;
}
 
void add(int x, int c)
{
     for (int i = x; i <= N; i += lowbit(i)) 
        tr[i] += c;
}
 
int sum(int x)
{
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) 
        res += tr[i];
    return res;
}
 
signed main()
{
    int t;
    cin>>t;
    while(t--)
    {
        cin>>n;
        for (int i = 1; i <= n; i ++ ) 
            cin>>a[i];
      
        memset(tr, 0, sizeof tr);//记得初始化,因为是多组测试样例
        for (int i = 1; i <= n; i ++ )
        {
            int y = a[i];
            Greater[i] = sum(N) - sum(y-1);
            lower[i] = sum(y);
            l[i]=sum(y)-sum(y-1);
            add(y, 1);
        }
         
        memset(tr, 0, sizeof tr);
        int res1 = 0, res2 = 0;
         
        for (int i = n; i>=1; i -- )
        {
            int y = a[i];
            res1 += Greater[i] * (int)(sum(y));
            res2 += lower[i] * (int)(sum(N) - sum(y-1));
            r[i]=sum(y)-sum(y-1);
            add(y, 1);
        }
        int ans=res1+res2;
        for (int i = 1; i <= n; i ++ )
        {
            ans -= l[i]*r[i];//容斥原理
        }
        cout<<ans<<endl;
 
    }
}

L

L题目链接
思路:容斥原理+快速幂求逆元+概率+dp
  每个字符都要进行取的操作,当取第 i 个字符时候,维护以s[ i ]结尾的字符串的概率,和以其他字符结尾的字符串的概率。因为求非严格单调串的概率,计算出非严格单调上升和非严格单调下降的串的概率后减去由相同字符组成的串的概率即可。
  dp[ i ] [ j ]中 i 代表 a ~ z ( 0 ~ 26 ) 为结尾的好串概率
  j == 0代表非严格单调上升
  j == 1代表非严格单调下降
  j == 2代表不升不降相同

#include <bits/stdc++.h>
using namespace std;
#define int long long
 
const int mod = 998244353;
int dp[26][3];
//dp[i][j]中i代表a~z(0~26)为结尾的好串概率
//j==0代表非严格单调上升
//j==1代表非严格单调下降
//j==2代表不升不降相同
 
int qmi(int a, int b, int p)//快速幂
{
    int res = 1;
    while (b)
    {
        if (b & 1) res = res * a % p;
        a = a * (int)a % p;
        b >>= 1;
    }
    return res;
}
int inv(int x)//逆元
{
    return qmi(x, mod - 2, mod);
}
 
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
     
    int n;
    cin>>n;
    string s;
    cin>>s;
    s=" "+s;
    int zero=1;//空串概率
    for(int i=1;i<=n;i++)
    {
        int x;
        cin>>x;
        x=inv(x);
        int ix=(1-x+mod)%mod;
        for(int j='a';j<='z';j++)//dp[s[i]][0/1]更新以s[i]为结尾的好串概率,就是抓到了s[i]
        {
            if(j<s[i])//上升
            {
                dp[s[i]-'a'][0]=(dp[s[i]-'a'][0]+x*dp[j-'a'][0]%mod)%mod;
                 
            }
            else if(j>s[i])//下降
            {
                dp[s[i]-'a'][1]=(dp[s[i]-'a'][1]+x*dp[j-'a'][1]%mod)%mod;
            }
        }
         
        for(int j='a';j<='z';j++)//dp[j][0/1/2]更新以j为结尾的好串概率,就是抓不到s[i]
        {
            if(j==s[i])
                continue;
            for(int k=0;k<3;k++)
                dp[j-'a'][k]=(dp[j-'a'][k]*ix%mod)%mod; 
        }
         
        for(int k=0;k<3;k++)//dp[s[i]][0/1/2]更新以s[i]为开头的好串概率,就是空串概率*抓到s[i]
            dp[s[i]-'a'][k]=(dp[s[i]-'a'][k]+zero*x%mod)%mod; 
 
        zero=(zero*ix%mod)%mod;//更新空串概率
    }
     
    int ans=0;
    for(int j=0;j<26;j++)
    {
        ans=ans+dp[j][0];
        ans%=mod;
        ans=ans+dp[j][1];
        ans%=mod;
        ans=(ans+mod-dp[j][2])%mod;	//容斥原理,把重合部分减掉
    }
    cout<<ans;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值