• 2022牛客蔚来杯第二场 G J K L D C (H)


    2022牛客蔚来杯第二场

    G.Link with Monotonic Subsequence

    • 题意

    ​ 构造一个n的排列,使得排列的max( lis , lds )最小

    • 题解

      可以按照sqrt(n)来分块

      Eg: n=9

      排列:321 654 987

      其中lis与lds都为sqrt(n),其中lds的长度来自每个块内部,lis来自所有块之间。

      故:只需分块后数字倒叙输出就行

    • 代码

    #include 
    #include 
    #include 
    
    using namespace std;
    
    void solve() {
        int n;
        cin>>n;
        int sq=ceil(sqrt(n));//表示块数以及每个块的个数
        for(int i=1;i*sq<=n;i++) {//把完整的块输出
            int st=(i-1)*sq+1;//起点数值
            int ed=i*sq;//终点数值
            for(int j=ed;j>=st;j--) cout<(n/sq)*sq;i--) cout<>T;
        while(T--) solve();
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    J.Link with Arithmetic Progression

    • 题意

      给定一个数组a,问将数组a变成等差数列b使得(bi-ai)*(bi-ai)之和最小

    • 题解

      线性回归定义:

      故:题目转化为,一堆点(i,ai),求直线(等差数列就是直线呐)b=Ax+B使得式子最小的变化参数A,B,最后求误差式子的值

    • 代码

    #include 
    #include 
    
    using namespace std;
    typedef long long LL;
    typedef double ld;
    const int N=1e5+10;
    
    int a[N];
    
    void solve() {
        int n;
        scanf("%d",&n);
        
        ld x_average=0,y_average=0;//平均值
        for(int i=1;i<=n;i++) {
            scanf("%d",&a[i]);
            y_average+=a[i];
            x_average+=i;
        }
        y_average/=n,x_average/=n;
        
        ld A=0,B=0;
        for(int i=1;i<=n;i++)
            A+=(i-x_average)*(a[i]-y_average),
            B+=(i-x_average)*(i-x_average);
        A/=B;
        B=y_average-A*x_average;//求得最优变换参数A,B
        
        ld ans=0;
        for(int i=1;i<=n;i++) 
            ans+=(A*i+B-a[i])*(A*i+B-a[i]);//差值答案
        printf("%.15lf\n",ans);
            
    }
    
    int main() {
        int T;
        scanf("%d",&T);
        while(T--) solve();
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    K.Link with Bracket Sequence I

    • 题意

      给定一个长度为n的括号序列a,求长度为m的合法括号序列b,同时满足a是b的子序列的序列b的数量

    • 题解

      • 找能够匹配子序列a的序列b,那么容易想到使用最长公共子序列,即动态规划。f[i,j]:=在b的前i位,匹配了a序列的前j位的序列数量
      • 但题目还有个条件要求序列b是合法的,我们可由前置知识得到以左括号+1,右括号-1,则最终合法序列值为0,同时期间不出现值<0。那么在动态数组中多加入一维k,来判定序列是否合法

      动态规划

      • 定义:f[i,j,k]指b序列前i位中匹配了a序列的前j位,同时b序列中的净左括号数量为k

      • 状态转移:

        b[i]放’(‘。(注意:k需要大于0,否则数组越界)

        1. a[j]=‘(’,即刚好两序列的最后一位匹配,那么数量在此情况下等于b序列前i-1位还需匹配a序列前j-1位同时有k-1个括号数量的序列数量
          f [ i , j , k ] + = f [ i − 1 , j − 1 , k − 1 ] f[i,j,k]+=f[i-1,j-1,k-1] f[i,j,k]+=f[i1,j1,k1]

        2. a[j]=‘)’,即b第i位与a第j位不匹配,那么此时数量等于b序列前i-1位需要匹配a序列的前j位,同时左括号数量为k-1的序列数量
          f [ i , j , k ] + = f [ i − 1 , j , k − 1 ] f[i,j,k]+=f[i-1,j,k-1] f[i,j,k]+=f[i1,j,k1]

        b[i]放’)’

        1. a[j]=‘(’,即b第i位与a第j位不匹配,那么此时数量等于b序列前i-1位需要匹配a序列的前j位,同时左括号数量为k+1的序列数量
          f [ i , j , k ] + = f [ i − 1 , j , k + 1 ] f[i,j,k]+=f[i-1,j,k+1] f[i,j,k]+=f[i1,j,k+1]

        2. a[j]=‘)’,即刚好两序列的最后一位匹配,那么数量在此情况下等于b序列前i-1位还需匹配a序列前j-1位同时有k+1个括号数量的序列数量
          f [ i , j , k ] + = f [ i − 1 , j − 1 , k + 1 ] f[i,j,k]+=f[i-1,j-1,k+1] f[i,j,k]+=f[i1,j1,k+1]

      • 注意初始化,f[0,0,0]客观存在所以赋值为1。最终答案为f[m,n,0]

    #include 
    #include 
    #include 
    
    using namespace std;
    typedef long long LL;
    const int N=210,MOD=1e9+7;
    
    char s[N];
    LL f[N][N][N];
    
    void solve() {
        int n,m;
        scanf("%d%d%s",&n,&m,s+1);
        
        for(int i=0;i<=m;i++) //初始化
            for(int j=0;j<=n;j++)
                for(int k=0;k<=m;k++) 
                    f[i][j][k]=0;
        f[0][0][0]=1;
        
        for(int i=1;i<=m;i++)
            for(int j=0;j<=min(i,n);j++)//优化
                for(int k=0;k<=i;k++) {
                  //这样写编译就会爆数组越界
                    //if(k) f[i][j][k]+=f[i-1][j-(s[j]=='(')][k-1] %= MOD;
                    //f[i][j][k]+=f[i-1][j-(s[i]==')')][k+1] %= MOD;
                    
                  if(k) {//b[i]填'(',注意k需要>0,否则数组越界
                        if(s[j]=='(')f[i][j][k]+=f[i-1][j-1][k-1];
                        else f[i][j][k]+=f[i-1][j][k-1];
                        f[i][j][k]%=MOD;
                    }
                  //b[i]填')'
                    if(s[j]==')') f[i][j][k]+=f[i-1][j-1][k+1];
                    else f[i][j][k]+=f[i-1][j][k+1];
                    f[i][j][k]%=MOD;
                }
        printf("%lld\n",f[m][n][0]);
    }
    
    int main() {
        int T;
        scanf("%d",&T);
        while(T--) solve();
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    L.Link with Level Editor I

    • 题意

      有n个世界,每个世界有若干个节点以及若干条有向边;游戏开始从点1出发,游戏中处于某点时,可以选择在这个世界内走向有边的点,也可以选择待着不动直接传送至下一个世界的同一点。问从点1走到m点所经过的最小世界数为多少?

    • 题解

      对于第i个世界可以从第i-1个世界走过来,答案也可以从上一世界的值转移过来,因此可以考虑dp

      状态定义:

      f[i,j]表示可以从1点走到第i个世界的j点的方案中,点1最晚出发的世界编号。由于内存不够,但每次转移只跟上一世界相关,所以滚动数组优化。now[x]表示从1点走到当前世界x点的方案中,点1最晚出发的世界编号。pre数组则表示上一世界的信息。

      状态转移

      对于第i个世界的某条边u->v,可以从上一世界的u点转移过来
      n o w [ v ] = m a z ( n o w [ v ] , p r e [ u ] ) now[v]=maz(now[v],pre[u]) now[v]=maz(now[v],pre[u])
      **注意:**当u为起点时,直接从当前世界走最优,如果不特殊处理使得答案更新错误

    • 代码

    #include 
    using namespace std;
    const int INF = 0x3f3f3f3f;
    int main()
    {
    	ios::sync_with_stdio(0), cin.tie(0);
    	int n, m, ans = INF;//ans求最小值,直接初始化为无穷大
    	cin >> n >> m;
    	vector pre(m + 1), now(m + 1);
    	for (int i = 1; i <= n; i++)
    	{
    		int u, v, k; cin >> k;
    		now[1] = i;//在能从点1到达i世界中的1点的最晚世界编号为i
    		while (k--)
    		{
    			cin >> u >> v;//u->v有向边
    			now[v] = max(now[v], pre[u]);//向前面的世界转移
    			if (u == 1) now[v] = i;//如果边的起点为1,那么从1到世界i的点v的最晚世界编号就是i本身
    		}
        if(now[m]) ans = min(ans, i-now[m]+1);//如果某个世界的到达点m的值被更新了,意味着能到点m,所以更新答案
    		pre = now;//滚动一下数组
    	}
    	if (ans == INF) ans = -1;//如果答案未被更新,意味着没有方案
    	cout << ans;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    D.ink with Game Glitch

    • 题意

      • n个物品,m个转换形式b->d(c/a),a个b可以转换成c个d
      • 所给的转换形式中存在无限循环转换,(1个x变2个y,1个y变2个x)
      • 现在给每个转换形式的比率都加上一个系数w(0
      • 求使得不存在无限转换可能的最大w值
    • 题解

      • 建图,物品看成点,转换比率看成有向有权边
      • 当存在一个环,且环的所有比率乘积大于1,那么存在无限循环转换
      • 由于边多,所以乘法计算多次(若c/a < 1)会导致答案为0。所以不等式两边取log,即上一点转换为:环的所有边log(比率)之和大于0存在无限转换。等价于:环的所有边-log(比率)之和小于0存在无限循环。
      • 那么题目转换为找到一个最大的w是的图不存在负环,负环的寻找可以使用spfa,w的取值可以用二分,因为w存在单调性,当w越大越容易无限转换
    • 代码

    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    const int N=1010,M=2010;
    const double eps=1e-8;
    
    int n,m;
    int h[N],e[M],ne[M],idx;
    double w[M];
    
    void add(int a,int b,double c) {//a加上一条指向b的边,边权为c
        e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
    }
    
    int cnt[N];//记录每点到起点的经过边数
    bool st[N];//某点是否在队列当中
    double dis[N];//记录某点到起点的距离
    
    bool spfa(double mid){//判断有无负环
        queue q;
        for(int i=1;i<=n;i++){
            dis[i]=0;cnt[i]=0;//因为每次二分都进行一次spfa,一定初始化!
            q.push(i);
            st[i]=true;
        }
    
        while(!q.empty()){
            int t=q.front();
            q.pop();
            st[t]=false;
    
            for(int i=h[t];i!=-1;i=ne[i]){
                int j=e[i];
                if(dis[j]>dis[t]+w[i]+mid){
                    dis[j]=dis[t]+w[i]+mid;
    
                    cnt[j]=cnt[t]+1;
                    if(cnt[j]>=n)return true
    
                    if(!st[j]){
                        q.push(j);
                        st[j]=true;
                    }
                }
            }
        }
    
        return false;//所有可疑点都没有负环,所以没有负环
    }
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(0);
        
        cin>>n>>m;
        memset(h,-1,sizeof h);//初始化链表头
        while(m--) {
            int a,b,c,d;
            cin>>a>>b>>c>>d;
            add(b,d,-log(c*1.0/a));//加边以及负转换比率
        }
        
        double l=0,r=1;//二分系数w
        while(r-l>eps) {
            double mid=(l+r)/2;
            if(spfa(-log(mid))) r=mid;//如果有负环意味着无限转换,意味着w太大,所以w应该在更小的范围找
            else l=mid;
        }
        printf("%lf\n",r);
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    C.Link with Nim Game

    • 题意

      • 普通版的Nim游戏,但附加了条件:必胜者希望快速结束,必败者希望拖延

      • 输出游戏进行的总轮数,以及先手第一轮可选择的方案数

    • 题解

      • Nim游戏可得结论:异或总和为0时先手必败,否则必胜。

      • 可证:输的人总能只取一个石子,并且使得对手下一轮也只能拿一个来拖延

      • 先手必胜

        先手第一轮应该取走尽可能多的石子maxv,由结论剩下的轮数就是一人拿一个,所以游戏轮数为石子总和tot-max+1,而可选方案就是所有可以取走maxv的堆数。

        如何求解maxv?对于每一堆都去取最大量石子使得异或和为0(使对方转为必败态),取所有堆的最大值即可。

        其中一堆的最大值如何求?假设我们取走之后剩余x个,最大值就是a[i]-x那么相当于我们先拿走了a[i]个(suma[i]),再放进去x个(suma[i]x),要使得`sum^a[i]^x=0`,两边同异或suma[i]可以解得x=sum^a[i],所以某一堆最大值为a[i]-sum^a[i]

      • 先手必败

        先手第一轮就应该只拿一个,所以总轮数为石子总和tot,可选方案是所有堆中,先手拿走一个之后后手也只能拿一个,符合种情况的堆数。

        如何统计堆数?首先把所有堆都拿走一个,并且记录拿走一个之后的总异或和,对于这种情况的异或和放入set并去重(即只保存拿走一个之后能到达的所有状态);再检验这些状态是否能让后手在下一轮只拿一个,不能就去掉这个不合法状态,那么set中剩下的就是先手拿走一个后使得后手只能拿一个的状态;最后检验所有堆在拿走一个后是否能到达set中的合法状态,记录合法的堆数,堆数即为可选方案。

    • 代码

    #include 
    #include 
    #include 
    
    using namespace std;
    const int N=1e5+10;
    #define int long long//爆int了,哭哭
    
    int n,a[N];
    
    void solve() {
        cin>>n;
        
        int sum=0,tot=0;//异或和,数量和
        for(int i=0;i>a[i],sum^=a[i],tot+=a[i];
        
        if(sum) {//先手必胜
            int maxv=0,ans=0;//第一轮最大取走数量,先手第一轮方案数
            for(int i=0;i S,T;
            for(int i=0;i1) T.insert(SUM);//如果可取走不止一个,说明这个状态不最优,存入待删除的set
                }
            }
            for(auto it:T) S.erase(it);//删除不合法状态
            
          
          
          //以上预处理出了所有先手取走一个且后手也只能拿一个的状态
            int ans=0;//记录方案数
            for(int i=0;i>t;
        while(t--) solve();
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
  • 相关阅读:
    JDBC介绍
    Nagios自定义插件的编写规范
    HTML+CSS-Day11
    java面试
    C++笔记之信号量、互斥量与PV操作
    在跑腿App系统开发中,如何构建系统架构?
    如何优化服务器负载均衡策略?一文讲解
    深入理解rtmp(一)之开发环境搭建
    JavaScript事件处理
    [RootersCTF2019]I_<3_Flask-1|SSTI注入
  • 原文地址:https://blog.csdn.net/m0_49843646/article/details/126066821