2021牛客暑期多校训练营8

D
解题思路:由b数组和c数组我们可以由a[1]直接确定a数组,所以只需枚举a[1]即可,如果直接枚举值会超时。所以我们可以枚举a[1]的每一位二进制位能有几种情况然后相乘即可。只是c数组它表示a[i-1]+a[i],这一位的可能情况与前一位有关,所以我们得想办法换一个。而加法运算有一个等式:

a+b = a|b + a&b

所以我们可以让d数组表示为a[i-1]&a[i],那么d[i] = c[i]-b[i]。
然后就可以根据b、d数组确定出a[1]的方案。如何确定呢?b[i]、d[i]无非就一下情况:

  1. b[i]这一位是1,d[i]这一位是1,那么a[i],a[i-1]这一位必须是1。
  2. b[i]​​这一位是1​​,d[i]​​这一位是0​​,那么这两位必须为一个1一个0
  3. b[i]​​这一位是0,d[i]​​这一位是0​​,两位必须都为0

然后直接枚举a[1]的每一位即可。

#include <bits/stdc++.h>
#define endl "\n"
using namespace std;

typedef long long ll;
typedef pair<int,int> pii;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int inf = 0x3f3f3f3f;
const int N = 1e5+10;
const int mod = 1e9;
inline ll rd()
{
    ll 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<<1)+(x<<3)+(ch^48); ch=getchar();}
    return x*f;
}

ll b[N], c[N], d[N];

int main()
{
    ll ans = 1;
    int n = rd();
    for(int i = 2; i <= n; i++)
        b[i] = rd();
    for(int i = 2; i <= n; i++)
        c[i] = rd(), d[i] = c[i] - b[i];
    for(int i = 0; i <= 31; i++)
    {
        int p1 = 1, p0 = 1;//上一位默认可以为1和0
        for(int j = 2; j <= n; j++)
        {
            int b1 = 0, b0 = 0;//当前位能否为1和0
            int h = b[j]>>i&1;// |
            int y = d[j]>>i&1;// &
            if(h && y)
                b1 = p1;
            else if(h && !y)
                b1 = p0, b0 = p1;
            else if(!h && !y)
                b0 = p0;
            p1 = b1, p0 = b0;
        }
        ans *= p1+p0;
    }
    cout << ans << endl;
    return 0;
}

K
解题思路:因为Pi为无理数,所以在经过一个点时可以看成将周围四个格子都经过了。然后有两种走法,一个是走min(d, w),每次增加2个格子,一个是走对角线每次增加三个格子,需要走sqrt(d * d+w * w)。然后只需枚举一下最大值即可。如果走的是直线,如果走完最后一步后还剩下的加上最后的一步的距离可以换成走斜线,那么答案显然更优。同理如果走斜线的最后一步可以在走一步直线,或者少走一步斜线,走两步斜线,那么答案会更优。

#include <bits/stdc++.h>
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
const double PI = acos(-1);
signed main() {
    int t;
    scanf("%d", &t);
    while(t--) 
    {
        double w, d, tmp, dis;
        ll ans = 0;
        scanf("%lf%lf", &w, &d);
        tmp = min(w, d);
        dis = sqrt(w * w + d * d);
        for(int i = 0; i <= 2; ++i)
        {
            //枚举直线数目
            if(i * tmp <= PI) ans = max(ans, (ll)(4 + 2 * i + floor((PI - tmp * i) / dis) * 3));
            // 枚举斜线数目
            if(i * dis <= PI) ans = max(ans, (ll)(4 + 3 * i + floor((PI - dis * i) / tmp) * 2));
        }
        printf("%lld\n", ans);
    }    
    return 0;
}

F
解题思路:考虑离线做法,用biset模拟三维滚动数组。遍历i,j。bitset[j][] 表示能到达i,j的点。那么如果i,j不是墙,能到达bitset[j-1]的一定也能到达bitset[j],所以可以用bitset[j] |= bitset[j-1], 能到达i-1, j 的点也可以到达i, j,因为是滚动数组,所以没更新的bitset[j]就表示能到达i-1,j的那个点。

#include <bits/stdc++.h>
using namespace std;
const int N = 505;
 
struct node {
    int t, x, y, id;
};
 
vector<node>q[N][N];
bitset<N *N>b[N];
int ans[500005];
char s[N][N];
 
int main() 
{
    int n, m, Q, x1, x2, y1, y2, t;
    ios::sync_with_stdio(false);
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            cin >> s[i][j];
    cin >> Q;
    for (int i = 1; i <= Q; i++) 
    {
        cin >> t >> x1 >> y1 >> x2 >> y2;
        q[x2][y2].push_back({t, x1, y1, i});//注意:是反着的,终点为x2,y2
        //将x1,y1到x2,y2 变成 x2,y2到x1,y1
    }
    b[0].reset();
    //类滚动数组
    for (int i = 1; i <= n; i++) 
    {
        for (int j = 1; j <= m; j++) 
        {
 
            if (s[i][j] == '0') 
            {
                b[j] |= b[j - 1];//拷贝上一份
                b[j][i * m + j] = 1;//自己到自己是1
            } 
            else
                b[j].reset();//全部是0,当前点是无论如何都不能到达任何地方的
            for (auto it : q[i][j]) 
                if (it.t == 1)
                   ans[it.id] = (it.y == j && b[j][it.x * m + it.y]);
                else if (it.t == 2)
                   ans[it.id] = (it.x == i && b[j][it.x * m + it.y]);
                else
                   ans[it.id] = b[j][it.x * m + it.y];
        }
    }
    for (int i = 1; i <= Q; i++)
        if (ans[i])
            cout << "yes" << endl;
        else
            cout << "no" << endl;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值