杭电多校2022.7.28第四场部分题解

A 1001 Link with Bracket Sequence II

题意:给定一个长度为n的数组,若a[i] == 0,那么表示这个位置填入的括号未知,若|a[i]|> 0 表示这个位置填入第|a[i]|种括号,左括号为正,右括号为负,求填满这个括号序列的方案数。

分析:n <= 500,因此我们考虑区间dp。对于所有括号序列,我们从两种情况进行转移,f[l][r]表示对于一个从范围为[l, r]的合法括号序列,且l和r处的括号序列位置相匹配的方案数g[l][r]表示[l, r]的合法括号序列的方案数,且l和r处的括号序列位置相匹配,但是当 g 在转移过程中,g 表示取区间的左右端点的括号类型与 f 表示的区间的左右端点的括号类型不一定相同

为什么要这么设定呢?因为括号的种类有m种,我们如何用这个m呢?就是枚举到两端均是0的时候直接可以乘一个m,但是这只是对于f来说的转移方程。而g是更加普遍的情况,如何转移呢?我们考虑区间分点,g[l][r]种找到一个分界线k,那么可以得出转移方程:g[l][r] += g[l][k - 1] * f[k][r]。这个堆叠方向是人为规定的,是为了避免重复计数。

其实只要想清楚如何防止重复统计就可以,我们一般都认为定义一种转移方式,转移方式有许多,比如这道题我们的 g 是从 g + f 的方式转移,这样恰好能够不重不漏计算出所有满足要求的 g 。

#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <map>
#include <vector>
#include <set>

using namespace std;


typedef long long LL;
const int N = 510, M = N * 44 + 1, mod = 1e9 + 7;

LL a[N];
LL n, m, T;
LL f[N][N], g[N][N];

void solve()
{
    scanf("%lld%lld", &n, &m);
    for (int i = 1; i <= n; i ++ ) scanf("%lld", &a[i]);
    
    memset(f, 0, sizeof f);
    memset(g, 0, sizeof g);
    
    if (n & 1)
    {
        puts("0");
        return;
    }
    for (int i = 0; i <= n; i ++ ) g[i + 1][i] = 1;
    
    for (int len = 2; len <= n; len += 2)
        for (int l = 1; l + len - 1 <= n; l ++ )
        {
            int r = l + len - 1;
            if (a[l] >= 0 && a[r] <= 0)
            {
                int t = 0;
                if (a[l] == 0 && a[r] == 0) t = m;
                else if (a[l] == 0 || a[r] == 0) t = 1;
                else if (a[l] + a[r] == 0) t = 1;
                else t = 0;
                f[l][r] = g[l + 1][r - 1] * t % mod;
            }
            
            for (int k = l; k <= r; k += 2)
                g[l][r] = (g[l][r] + g[l][k - 1] * f[k][r] % mod) % mod;
        }
    
    
    printf("%lld\n", g[1][n]);
    
}

signed main()
{
    scanf("%lld", &T);
    while (T -- )
    {
        solve();
    }
    return 0;
}

K 1011 Link is as bear

题意:给定一个长度为n的数组,指定一种操作,可以使得[l, r]区间内所有值变成a[l] ^ a[l + 1] .. ^ a[r]的值,求最终使得整个数组变为同一个数的最大可能值。

分析:题目有个特殊点在于一定存在两个同等的数相邻。那么这有什么用呢?其实比较复杂我们不如直接看题解的证明,证明完就相当于我们可以构造出任意一个解,你可以随便挑几个数来构成解。那么这该怎么做呢?线性基。线性基就是建立在高斯消元的基础上的求基底的过程。

1.对两个数操作两次可以变成0 0 

2.只要存在连续两个的0,就可以实现取任何数,例:对于 0 0 a b, 若想取b,则可以先让a和0异或两次变成 0 0 0 b -> b b b b。

结论:本题考察给定 n 个整数(可能重复),从中挑选任意个整数,使得选出整数的异或和最大。

代码如下:

#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <map>
#include <vector>
#include <set>

using namespace std;

typedef long long LL;
const int N = 1e5 + 10, M = N * 44 + 1, mod = 1e9 + 7;

LL a[N];
int n, T;

void solve()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ ) scanf("%lld", &a[i]);
    
    int r = 1;
    for (int c = 62; c >= 0; c -- )
    {
        for (int i = r; i <= n; i ++ )
        {
            if (a[i] >> c & 1)
            {
                swap(a[i], a[r]);
                break;
            }
        }
        
        if (!(a[r] >> c & 1)) continue;//此时整列是0
        
        for (int i = 1; i <= n; i ++ )
            if (i != r && (a[i] >> c & 1))
                a[i] ^= a[r];
        
        r ++ ;
        if (r == n + 1) break;//全部处理完了
    }
    
    LL res = 0;
    for (int i = 1; i <= r; i ++ ) res ^= a[i];
    printf("%lld\n", res);
}

int main()
{
    scanf("%d", &T);
    while (T -- )
    {
        solve();
    }
    return 0;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值