2022牛客暑期多校第二场 G、J、K

G

题目大意:

  • 给定一个n,求出只包含1~n的一种排列
  • 这个排列满足max(最长上升子序列长度,最长下降子序列长度)最小

解法:

  1. 构造几组数据后可以发现,如果n为8的时候,构造成5678 1234这种序列,可以把序列分成2组,并且每组的上升序列不会延申到其他组中,而每个组之间产生的为下降序列
  2. 所以对n进行分组,假设分成k组,每组x个的话,这个排列的值就是max(k, x),而k * x >= n,那么想要值最小,就是k和x尽量接近,那么可知
    k = x = ⌈ n ⌉ k=x=\left \lceil \sqrt{n} \right \rceil k=x=n
#include <iostream>
#include <cmath>

using namespace std;

int main()
{
    int t;
    cin >> t;
    while(t --)
    {
        int n;
        cin >> n;
        int x = sqrt(n);
        if(x * x < n) x += 1;
        for(int i = 1;i <= x;i ++)
        {
            for(int j = n - x + 1;j <= n;j ++) cout << j << " ";
            n -= x;
            if(n < x) break;
        }
        for(int i = 1;i <= n;i ++) cout << i << " ";
        cout << endl;
    }
    return 0;
}

J

题目大意:

  • 给定一个n个元素的数组
  • 可以把数组中的 a i a_{i} ai变成 b i b_{i} bi
  • 变换之后的花费是
    c o s t = ∑ i = 1 n ( a i − b i ) 2 cost=\sum_{i=1}^{n} (a_{i}-b_{i})^2 cost=i=1n(aibi)2
  • 变换之后b数组需要满足是等差数列,求最小花费

解法:

  1. b 1 b_{1} b1以及公差 d d d为变量,那么对于 c o s t cost cost求法可以得到以下式子:
    c o s t = ∑ i = 1 n [ a i − b 1 − ( i − 1 ) × d ] 2 cost = \sum_{i=1}^{n}[a_{i}-b_{1}-(i-1)\times d]^2 cost=i=1n[aib1(i1)×d]2
  2. 可知cost函数是一个二元均方误差函数,所以这个函数是一个凹函数,那么求极小值解就可以利用三分套三分求,在三分 d d d之后,d就变成了常量,然后对上面的式子进行求导:
    ∑ i = 1 n − 2 × ( a i − b 1 − ( i − 1 ) × d ) \sum_{i=1}^{n}-2\times (a_{i}-b_{1}-(i-1)\times d) i=1n2×(aib1(i1)×d)
    ⇒ − 2 ∑ i = 1 n a i + 2 n b 1 + n ( n − 1 ) d \Rightarrow -2\sum_{i=1}^{n}a_{i}+2nb_{1}+n(n - 1)d 2i=1nai+2nb1+n(n1)d
    令这个式子等于0,求出来的 b 1 b_{1} b1值就是 c o s t cost cost在暂定的 d d d之下的极值,那么就不需要三分套三分了,只需要三分 d d d
    − 2 ∑ i = 1 n a i + 2 n b 1 + n ( n − 1 ) d = 0 -2\sum_{i=1}^{n}a_{i}+2nb_{1}+n(n - 1)d=0 2i=1nai+2nb1+n(n1)d=0
    b 1 = ∑ i = 1 n a i n − ( n − 1 ) d 2 b_{1}=\frac{\sum_{i=1}^{n}a_{i}}{n} -\frac{(n-1)d}{2} b1=ni=1nai2(n1)d
#include <iostream>
#include <cstdio>
#include <cctype>

using namespace std;

const int N = 1e5 + 10;
long double a[N], sum;
int n;

namespace GTI
{
    char gc(void)
       {
        const int S = 1 << 16;
        static char buf[S], *s = buf, *t = buf;
        if (s == t) t = buf + fread(s = buf, 1, S, stdin);
        if (s == t) return EOF;
        return *s++;
    }
    int gti(void)
       {
        int a = 0, b = 1, c = gc();
        for (; !isdigit(c); c = gc()) b ^= (c == '-');
        for (; isdigit(c); c = gc()) a = a * 10 + c - '0';
        return b ? a : -a;
    }
}
using GTI::gti;

long double get(long double d)
{
    return sum / n - (n - 1) / 2.0 * d;
}

long double f(long double d)
{
    long double b1 = get(d);
    long double res = 0;
    for(int i = 1;i <= n;i ++)
        res += ((a[i] - b1 - (i - 1) * d) * (a[i] - b1 - (i - 1) * d));
    return res;
}

void solve()
{
    n = gti();
    sum = 0;
    for(int i = 1;i <= n;i ++) 
    {
        a[i] = gti();
        sum += a[i];
    }
    long double l = -1e9, r = 1e9;
    while(r - l > 1e-10)
    {
        long double m1 = l + (r - l) / 3;
        long double m2 = r - (r - l) / 3;
        if(f(m1) <= f(m2)) r = m2;
        else l = m1;
    }
    printf("%.9lf\n", (double)f(r));
}

signed main()
{
    int t;
    t = gti();
    while(t --) solve();
    return 0;
}

K

题目大意:

  • 给定一个具有n长度的括号序列s,并且给定一个整数m
  • 求解满足括号序列s长度为m的合法序列S的子串的所有长度为m的合法括号序列S的数量

解法:

  1. f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示合法序列S的前i个位置包含括号序列s的前j个并且左括号数量比右括号数量大k个的合法括号序列数量

  2. 如果s第j个字符是左括号,那么S的i个位置有2种选择
    选择放 s j s_{j} sj
    f [ i − 1 ] [ j − 1 ] [ k − 1 ] f[i-1][j-1][k -1] f[i1][j1][k1]
    前提是 k − 1 > = 0 k-1>=0 k1>=0
    不放 s j s_{j} sj
    f [ i − 1 ] [ j ] [ k + 1 ] f[i-1][j][k+1] f[i1][j][k+1]
    前提是 k + 1 < = m k+1<=m k+1<=m
    对于右括号的以此类推

  3. 特殊情况
    f [ 0 ] [ 0 ] [ 0 ] = 1 f[0][0][0]=1 f[0][0][0]=1
    f [ i ] [ 0 ] [ k ] = 满足 i 和 k 的合法括号序列数量 , ( 1 ≤ i ≤ m , 0 ≤ k ≤ i ) f[i][0][k]=满足i和k的合法括号序列数量,(1\le i\le m,0 \le k\le i) f[i][0][k]=满足ik的合法括号序列数量,(1im,0ki)

#include <iostream>
#include <cstring>

using namespace std;

const int N = 210, mod = 1e9 + 7;
int f[N][N][N], n, m;
char s[N];

void init()
{
    f[0][0][0] = 1;
    for(int i = 1;i <= m;i ++)
        for(int j = 0;j <= i;j ++)
            f[i][0][j] = (f[i][0][j] + (j - 1 < 0 ? 0:f[i - 1][0][j - 1]) + f[i - 1][0][j + 1]) % mod;
}

void solve()
{
    memset(f, 0, sizeof f);
    cin >> n >> m >> (s + 1);
    init();
    for(int i = 1;i <= m;i ++)
        for(int j = 1;j <= min(i, n);j ++)
            for(int k = 0;k <= i;k ++)
            {
                if(s[j] == '(')
                {
                    if(k - 1 >= 0) f[i][j][k] = (f[i][j][k] + f[i - 1][j - 1][k - 1]) % mod;
                    if(k + 1 <= m) f[i][j][k] = (f[i][j][k] + f[i - 1][j][k + 1]) % mod;
                }
                else
                {
                    if(k + 1 <= m) f[i][j][k] = (f[i][j][k] + f[i - 1][j - 1][k + 1]) % mod;
                    if(k - 1 >= 0) f[i][j][k] = (f[i][j][k] + f[i - 1][j][k - 1]) % mod;
                }
            }
    cout << f[m][n][0] << endl;
}

int main()
{
    int t;
    cin >> t;
    while(t --) solve();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值