2021牛客多校8(ADFJ)

这篇博客探讨了如何利用位运算解决数学问题,包括计算序列满足特定条件的数量。同时,文章介绍了在博弈论背景下如何计算得分期望,并通过DFS和ST表解决树形结构上的博弈问题。通过对经典算法的解析,展示了如何在实际问题中应用这些技巧。
摘要由CSDN通过智能技术生成

目录

A:Ares, Toilet Ares(期望,逆元)

D:OR(位运算,思维)

F:Robots(暴力,bitset)

J:Tree(st表,博弈,dfs)


A:Ares, Toilet Ares(期望,逆元)

题意

你原本的得分是a,你可以去k次厕所,每次去厕所有\frac{zi-yi}{zi}的概率拿到可以让你的得分加一的卡片的一小部分,这一小部分表示为xi​,保证\sum xi=ll就是完整卡片的长度,你只有在拿到全部长度的时候才能让你的得分加一,问你得分的期望是多?

solution:

得分期望:1*(每次的概率累乘)+a 即为答案。(注意xi=0时,不需要乘)

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod=4933;
int ksm(int a, int k)  // 求a^k mod p
{
    int res = 1 % mod;
    while (k)
    {
        if (k & 1) res = res * a % mod;
        a = a * a % mod;
        k >>= 1;
    }
    return res%mod;
}
signed main()
{
    int n,m,k,a,l;
    cin>>n>>m>>k>>a>>l;
    int ans=a;
    int res=1;
    for(int i=0;i<k;i++)
    {
        int x,y,z;
        cin>>x>>y>>z;
        if(x==0)
        continue;
        x=z-y;
        res=((res*x%mod)*ksm(z,mod-2)%mod)%mod;
    }
    cout<<(ans+res)%mod;
}

D:OR(位运算,思维)

题意:

给定b序列和c序列,定义b_{i}=a_{i}|a_{i-1},c_{i}=a_{i}+a_{i-1},求有多少序列a满足要求。

solution:

一个重要的公式:a+b=(a|b)+(a&b).可以求出a_{i}&a_{i-1}的序列。可以发现二进制下每一位取0或1都不会影响其他位,根据已有的a_{i}&a_{i-1}a_{i}|a_{i-1}的关系,可以推出每一位可以取0或1的可能性。枚举a_{1}的每一位为0或1的情况是否满足 a_{i}&a_{i-1}a_{i}|a_{i-1}的关系,分别计算模拟即可。

 

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int b[N];
int c[N];
int a[N];
signed main()
{
    int n;
    cin>>n;
    for(int i=2;i<=n;i++)
    {
        cin>>b[i];
    }
    for(int i=2;i<=n;i++)
    {
        cin>>c[i];
    }
    for(int i=2;i<=n;i++)
    {
        a[i]=c[i]-b[i];
    }
    int ans=1;
    for(int i=31;i>=0;i--)
    {
        int temp=0;//当前位满足的情况
        for(int j=0;j<=1;j++)
        {
            int st=j;//前一位
            bool ok=1;
            for(int k=2;k<=n;k++)
            {
                if(st==0)
                {
                    if((a[k]>>i&1)==0)//当前位可以0 1
                    {
                        if((b[k]>>i&1)==0)//当前位为0
                        {
                            st=0;
                            continue;
                        }
                        else if((b[k]>>i&1)==1)//当前位1
                        {
                            st=1;
                            continue;
                        }
                        ok=0;break;
                    }
                    else
                    {
                        ok=0;break;
                    }
                }
                else //1&
                {
                    if((a[k]>>i&1)==0)//当前位只能为0
                    {
                        if((b[k]>>i&1)==0)
                        {
                            ok=0;break;
                        }
                        st=0;
                    }
                    else//当前位1
                    {
                        if((b[k]>>i&1)==0)
                        {
                            ok=0;break;
                        }
                        st=1;
                    }
                }
            }
            temp+=ok;
        }
        ans*=temp;
    }
    cout<<ans;
}

F:Robots(暴力,bitset)

题意:

给你一个n*m的网格,0可以走,1不可以走,有三种类型机器人,第一种是只能向右移动,第二种是只能向下移动,第三种是只能向下或向右移动。q次询问,给出机器人类型,起点以及终点,输出是否能从起点走到终点。

solution:

暴力做法肯定tle。考虑离线做法,维护终点,记录哪些点可以到达该点。

用bitset来开一个bitset<N*N>f[N][N]表示(i,j)为终点能到达的情况,但是开一维bitset<N*N>f[N]的空间用滚动数组的思想来降低空间。

当前这个网格只能从左边的网格或上面的网格的状态转移下来.

如果mp[i][j]==0,说明这个点可以走,可以从上一个状态转移下来,f[j] |=f[j-1]

否则,另f[j]=0即可表示不能走

怎么滚动转移,借鉴一下别人的思路:

```

这就是滚动数组的延迟更新的特性,这能到达其上方的点,本身就已经在上一轮循环中储存在b[j]里了
举个例子,这是一张地图:
0 0 0
0 0 0
0 1 0
图中1代表障碍物,0代表空地
在遍历(1,1)时,b[1]的可达性矩阵是这样的
1 0 0
0 0 0
0 0 0
在遍历(1,2)时,由于b[2]|=b[1],其的可达性矩阵是这样的
1 1 0
0 0 0
0 0 0
在遍历(1,3)时,b[3]的可达性矩阵是这样的
1 1 1
0 0 0
0 0 0
在遍历(2,1)时,在未更新b[1]前,b[1]可达性矩阵是这样
1 0 0
0 0 0
0 0 0
在更新b[1]后,b[1]可达性矩阵是这样
1 0 0
1 0 0
0 0 0
在遍历(2,2)时,未更新b[2]前,b[2]可达性矩阵是这样
1 1 0
0 0 0
0 0 0
在进行b[2]|=b[1]操作后,b[2]成了这样
1 1 0
1 1 0
0 0 0
现在快进一波,在遍历(3,2)时,由于(3,2)有障碍物,其可达性矩阵要全部清0
此时的b[2]变成了
0 0 0
0 0 0
0 0 0
接下来遍历(3,3),在更新数据前b[3]是这个样子
1 1 1
1 1 1
0 0 0
在进行b[3]|=b[2]更新后,再加上它本身,变成这个样子:
1 1 1
1 1 1
0 0 1
由此,就实现了高效维护
```

#include <bits/stdc++.h>
// #define int long long
using namespace std;
const int N = 510;
char mp[N][N];
bitset<N * N> f[N];
struct node
{
    int t, x, y, id; //类型,坐标,问题标号
};
vector<node> v[N][N];
int ans[500005];
signed main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        scanf("%s", mp[i] + 1);
    }
    int q;
    cin >> q;
    for (int i = 1; i <= q; i++)
    {
        int t, x1, y1, x2, y2;
        scanf("%d%d%d%d%d", &t, &x1, &y1, &x2, &y2);
        v[x2][y2].push_back({t, x1, y1, i});
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            if (mp[i][j] == '0')
            {
                f[j] |= f[j - 1];//上个状态继承下来
                f[j][i * m + j] = 1;//更新一下自己这个点可以到达
            }
            else
            {
                f[j] = 0;
            }
            for (auto it : v[i][j])
            {
                if (it.t == 1)
                ans[it.id] = (it.y == j && f[j][it.x * m + it.y]);//必须在同一行,且能到达
                else if (it.t == 2)
                ans[it.id] = (it.x == i && f[j][it.x * m + it.y]);//必须在同一列,且能到达
                else
                ans[it.id]=f[j][it.x*m+it.y];
            }
        }
    }
    for(int i=1;i<=q;i++)
    {
        if(ans[i])
        cout<<"yes"<<endl;
        else
        cout<<"no"<<endl;
    }
}

J:Tree(st表,博弈,dfs)

题意:

给你一棵树,起始两个人在这棵树上不同位置s,t 上进行博弈,每个人轮流移动,移动后的上一个点和与这个点相连的边删去,每移动一次这个人加一分,当两个人都不能走的时候游戏结束,A先手。S_{a}为A的得分,S_{b}为B的得分。A想要S_{a}-S_{b}的值尽量大,B想要S_{b}-S_{a}的值尽量大(等价于 S_{a}-S_{b} 的值尽量小)。输出最后的 S_{a}-S_{b} 的值。

solution:

可以想两人在s~t这条路径上移动是有影响的,如果离开了这条链,就对另个人没有影响只要走最长链即可。

我们把s~t的这条链看作一条路径存在数组里(1~idx),A在数组的开头1,B在数组的末尾idx。

可以dfs预处理出从这条路径上每个点离开所能走到的最长路,假设为val[i]为从i这个点离开不经过s~t这条路所能到达的最长路。

模拟一下两个人的移动过程:

假设A走到的点的坐标为l,B走到的点的坐标为r。

A先手:

1.A可以从当前点离开走其他最长路,即A的分数为val[i]+l-1 (1~l的路径+最长路),B可以从[l+1,r]这个区间选择一个点离开然后走最长路,游戏结束。

2.A继续走s~t这条路径,即A走到l=l+1,轮到B,游戏继续

轮到B:

1.B可以从当前点离开走其他最长路,即B的分数为val[i]+idx-r (r~idx的路径+最长路),A可以从[l,r-1]这个区间选择一个点离开然后走最长路,游戏结束。

2.B继续走s~t这条路径,即B走到r=r-1,轮到A,游戏继续

一直递归下去。。。直到l==r停止。

轮到A,需要取max因为 A想要S_{a}-S_{b}的值尽量大,轮到B,需要取min因为B想要 S_{a}-S_{b} 的值尽量小。

A,B选择一个点离开然后走最长路需要查询区间最大值,用st表解决

#include<bits/stdc++.h>
// #define int long long
using namespace std;
const int N=5e5+10;
int n,s,t;
vector<int>edge[N];
int pre[N];
int idx;
int path[N];//记录路径s~t
int val[N];//s~t的结点最长路
int rot;
int lmx[N][20],rmx[N][20];//st表求区间最大值 
void dfs(int u,int fa)//记录路径s~t
{
    pre[u]=fa;
    if(u==t)
    return;
    for(auto v:edge[u])
    {
        if(v==fa)
        continue;
        dfs(v,u);
    }
}
int dfs1(int u,int fa)
{
    int dis=0;
    int ans=0;
    for(auto v:edge[u])
    {
        if(v==fa)
        continue;
        if(v==path[rot-1]||v==path[rot+1])
        continue;
        ans=max(dis+dfs1(v,u)+1,ans);
    }
    return ans;
}
int query(int l,int r,int flag)
{
    int k=log2(r-l+1);
    if(!flag) return max(lmx[l][k],lmx[r-(1<<k)+1][k]);
	return max(rmx[l][k],rmx[r-(1<<k)+1][k]);
}
int dfs2(int l,int r,int flag)//falg 0是A操作 1是B操作
{
    if(l==r)
    {
        if(flag==0)
        return 1e9;//不能走 返回正无穷
        else
        return -1e9;//同理
    }
    if(!flag)
    return max((val[l]+l-1)-(query(l+1,r,1)),dfs2(l+1,r,1));
    else
    return min((query(l,r-1,0)-(val[r]+idx-r)),dfs2(l,r-1,0));
}
signed main()
{
    cin>>n>>s>>t;
    for(int i=0;i<n-1;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        edge[a].push_back(b);
        edge[b].push_back(a);
    }
    idx=0;
    dfs(s,-1);
    int cur=t;
    while(cur!=-1)
    {
        path[++idx]=cur;
        cur=pre[cur];
    }
    reverse(path+1,path+1+idx);
    for(rot=1;rot<=idx;rot++)//求每个点能到的最长路
    {
        val[rot]=dfs1(path[rot],0);
    }
    for(int i=1;i<=idx;i++)
    {
        lmx[i][0]=val[i]+i-1;//A 从i点离开的最大得分
        rmx[i][0]=val[i]+idx-i;//B 从i点离开的最大得分
    }
    for(int j=1;(1<<j)<idx;j++)
	{
		for(int i=1;i+(1<<j)-1<=idx;i++)
		{
			lmx[i][j]=max(lmx[i][j-1],lmx[i+(1<<(j-1))][j-1]);
			rmx[i][j]=max(rmx[i][j-1],rmx[i+(1<<(j-1))][j-1]);
		}
	}
    int ans=dfs2(1,idx,0);

    cout<<ans;
}

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值