【牛客练习赛41】 A.B.C.D.E

A.翻硬币问题

题意

n个硬币朝向正面,Alice每次选m枚反过来,bob可以在某次Alice操作之后反转一枚硬币,问最终Alice是否能赢。m为偶数。

做法

只要第一轮Alice赢不了,Bob就可以控制比赛的输赢。

代码

#include<stdio.h>
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n,m;
        scanf("%d%d",&n,&m);
        if(n==m) puts("Yes");
        else puts("No");
    }
    return 0;
}


B.666RPG

题意

有一个数字初值为0,n回合操作,每回合操作有两种,第一种操作将分数加上 a i a_i ,第二种操作是将分数乘上-1.问有多少种操作方式在第n回合之后数字变为-666而且中间每一个回合之后分数都不是666。

1 N 300 1 \leq N \leq 300
666 a i 666 -666 \leq a_i \leq 666
做法

由于n个回合最多的改变量为666*n,所以数字如果在 [ 666 300 , 666 300 ] [-666*300,666*300] 这个范围之外,就是无效的。这样状态数就确定了。设dp[i][j]为第i个回合之后数字为j的方案数,很明显第i轮只能从第i–1轮转移过来,所以可以把第一维滚动掉,就做完了。由于数组不能存负下标,我们把所有数加上一个BASE就可以了。

代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
const int Mod=100000007;
ll dp[2][400000];
int a[maxn];
const int BASE = 199800;
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    dp[0][BASE]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=2*BASE;j++)
        {
            if(j==BASE+666) continue;
            if(j>=a[i]) dp[1][j]=(dp[0][j-a[i]]+dp[0][BASE*2-j])%Mod;
            else dp[1][j]=dp[0][BASE*2-j];
        }
        memcpy(dp[0],dp[1],sizeof(dp[0]));
        memset(dp[1],0,sizeof(dp[1]));
    }
    printf("%lld\n",dp[0][BASE-666]);
    return 0;
}



C.抓捕盗窃犯

题意

已知盗窃犯分布于 N N 个地点,以及第 i i 个地点初始有 a i a_i 名盗窃犯。特别的是,对于每一个地点
u u ,都有一个固定的地点 v v –当前如果某个盗窃犯位于地点 u u ,在下一个时刻他会移动到地点 v v
你需要通过初始时在某些点设置哨卡来捉住他们。现在你可以在 M M 个地点设置哨卡,如果在某个地点设置哨卡,你可以抓获在任一时刻经过该地点的盗窃犯。也就是说,哨卡存在的时间是无限长,但哨卡不能移动。

做法

把每个地点看作一个点,那么每个点一定有且仅有一条有向出边。
每个点出度只有1,如果某些点组成了一个有向环,这个环上所有点不会有额外的出边,即这个环一定是一个简单环。
也易证每个点最终都会走向一个环。
结论:单独看待每个联通块,每个连通块一定有且只有一个环,只要在这个环上任何一个点建立哨卡,就能抓到这个联通块中的所有。可以使用把边都看成无向,dfs找出所有连通块并求每个连通块上所有点的数量之和,从大到小排序,取前m大即可。
时间复杂度: O ( n l o g n ) O(nlogn)
代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
int val[maxn],to[maxn],vis[maxn];
ll cnt,sum[maxn];
vector<int> G[maxn];
bool cmp(ll a,ll b) {return a>b;}
void dfs(int x)
{
    vis[x]=1;
    cnt+=val[x];
    for(int i=0;i<G[x].size();i++)
        if(!vis[G[x][i]]) dfs(G[x][i]);
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&val[i]);
    for(int i=1;i<=n;i++) scanf("%d",&to[i]);
    for(int i=1;i<=n;i++)
    {
        G[to[i]].push_back(i);
        G[i].push_back(to[i]);
    }
    int tt=0;
    for(int i=1;i<=n;i++)
    {
        if(!vis[i])
        {
            cnt=0;
            dfs(i);
            sum[++tt]=cnt;
        }
    }
    ll ans=0;
    sort(sum+1,sum+1+tt,cmp);
    for(int i=1;i<=min(tt,m);i++) ans+=sum[i];
    printf("%lld\n",ans);
    return 0;
}


D.最小相似度

题意

定义两个位数相等的二进制串的相似度 S I M ( A , B ) = 二进制串中 A B 中0的个数 SIM\left( A,B \right) =\text{二进制串中}A\oplus B\text{中0的个数}
给定 N N 个长度为 M M 的二进制串。
现在的问题是找出一个额外的长度为 M M 的二进制字符串
使得 max { S I M ( S 1 , T ) , S I M ( S 2 , T ) . . . S I M ( S N , T ) } \max \left\{ SIM\left( S_1,T \right) ,SIM\left( S_{2,}T \right) ...SIM\left( S_N,T \right) \right\} 最小。
因为满足条件的可能不止一个,不需要输出串,只需要输出这个最小值即可。

1 N 3 1 0 5 1 \leq N \leq 3*10^5
1 M 20 1 \leq M \leq 20
做法

由于M只有20,所以我们知道状态数一共只有2^20,我们如果能算出每个状态和给定n个字符串相似度的最大值,这道题就解决了。首先我们知道所有n个串最初的dp值为m,通过bfs,把每个串丢进队列一次,每个串改变每一位得到新的字符串,更新那些没出现过的串的dp值再丢进队列,就可以保证当前步数为最大相似度。而且保证每个串只进一次队列.复杂度就是 O ( 20 2 20 ) O(20*2^{20})

代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<queue>
#include<string.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 3e5+5;
char str[maxn][25];
int dp[1<<21];
queue<int>q;
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%s",str[i]);
    memset(dp,INF,sizeof(dp));
    for(int i=1;i<=n;i++)
    {
        int sta=0;
        for(int j=0;j<m;j++) if(str[i][j]=='1') sta=(sta|(1<<j));
        dp[sta]=m;
        q.push(sta);
    }
    int ans=INF;
    while(!q.empty())
    {
        int tp=q.front();
        q.pop();
        ans=min(ans,dp[tp]);
        for(int j=0;j<m;j++)
        {
            int tmp=(tp^(1<<j));
            if(dp[tmp]==INF)
            {
                dp[tmp]=dp[tp]-1;
                q.push(tmp);
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}



E.球的体积并

题意

求两个球的体积并

做法

用球缺公式+计算几何模板即可解决。
球缺公式为: V = π h 2 ( 3 r h ) 3 V=\frac{\pi h^2\left( 3r-h \right)}{3}
其中 r r 是球的半径, h h 是球缺的高。
代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
const int maxn = 1e5+5;
typedef double db;
const db PI = acos(-1);
const db eps = 1e-10;
int sgn(double x)
{
    if(fabs(x)<eps) return 0;
    if(x<0) return -1;
    else return 1;
}
typedef struct point
{
	double x,y,z;
	point() { } point(double a, double b,double c) { x = a; y = b; z = c; }
	point operator -(const point &b)const { return point(x - b.x, y - b.y,z-b.z); }
	point operator +(const point &b)const { return point(x + b.x, y + b.y,z+b.z); }
	 //数乘计算
	  point operator *(const double &k)const {  return point(x * k, y * k,z*k); }
	  point operator /(const double &k)const {  return point(x / k, y / k,z/k); }
	  double operator *(const point &b)const {  return x*b.x + y*b.y+z*b.z; }
}point;
double dist(point p1, point p2) { return sqrt((p1 - p2)*(p1 - p2)); }
struct sphere
{
     double r;
     point centre;
}sc[maxn];
void SphereInterVS(sphere a, sphere b,double &v)
{
    double d = dist(a.centre, b.centre);
    if(sgn(d-a.r-b.r)>=0)//相离
    {
        v=0.0;
        return ;
    }
    else if(sgn(d-fabs(a.r-b.r))<=0)//内含
    {
        if(sgn(a.r-b.r)<=0) v=4.0*PI*a.r*a.r*a.r/3.0;
        else v=4.0*PI*b.r*b.r*b.r/3.0;
        return ;
    }
    //球心距
    double t = (d*d + a.r*a.r - b.r*b.r) / (2.0 * d);
    //h1=h2,球冠的高
    double h = sqrt((a.r*a.r) - (t*t)) * 2;
    double angle_a = 2 * acos((a.r*a.r + d*d - b.r*b.r) / (2.0 * a.r*d)); //余弦公式计算r1对应圆心角,弧度
    double angle_b = 2 * acos((b.r*b.r + d*d - a.r*a.r) / (2.0 * b.r*d)); //余弦公式计算r2对应圆心角,弧度
    double l1 = ((a.r*a.r - b.r*b.r) / d + d) / 2;
    double l2 = d - l1;
    double x1 = a.r - l1, x2 = b.r - l2;//分别为两个球缺的高度
    double v1 = PI*x1*x1*(a.r - x1 / 3);//相交部分r1圆所对应的球缺部分体积
    double v2 = PI*x2*x2*(b.r - x2 / 3);//相交部分r2圆所对应的球缺部分体积
    v = v1 + v2;//相交部分体积
    return ;
 }
int main()
{
    double ans=0;
    double x,y,z;
    scanf("%lf%lf%lf%lf",&x,&y,&z,&sc[1].r);
    point p =point(x,y,z);
    sc[1].centre=p;
    scanf("%lf%lf%lf%lf",&x,&y,&z,&sc[2].r);
    point p2 =point(x,y,z);
    sc[2].centre=p2;
    ans=ans+4.0/3*PI*sc[1].r*sc[1].r*sc[1].r;
    ans=ans+4.0/3*PI*sc[2].r*sc[2].r*sc[2].r;
    double v=0.0;
    SphereInterVS(sc[1],sc[2],v);
    ans=ans-v;
    printf("%.10f\n",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_38891827/article/details/88077133