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回合操作,每回合操作有两种,第一种操作将分数加上 ,第二种操作是将分数乘上-1.问有多少种操作方式在第n回合之后数字变为-666而且中间每一个回合之后分数都不是666。
做法
由于n个回合最多的改变量为666*n,所以数字如果在 这个范围之外,就是无效的。这样状态数就确定了。设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.抓捕盗窃犯
题意
已知盗窃犯分布于
个地点,以及第
个地点初始有
名盗窃犯。特别的是,对于每一个地点
,都有一个固定的地点
–当前如果某个盗窃犯位于地点
,在下一个时刻他会移动到地点
。
你需要通过初始时在某些点设置哨卡来捉住他们。现在你可以在
个地点设置哨卡,如果在某个地点设置哨卡,你可以抓获在任一时刻经过该地点的盗窃犯。也就是说,哨卡存在的时间是无限长,但哨卡不能移动。
做法
把每个地点看作一个点,那么每个点一定有且仅有一条有向出边。
每个点出度只有1,如果某些点组成了一个有向环,这个环上所有点不会有额外的出边,即这个环一定是一个简单环。
也易证每个点最终都会走向一个环。
结论:单独看待每个联通块,每个连通块一定有且只有一个环,只要在这个环上任何一个点建立哨卡,就能抓到这个联通块中的所有。可以使用把边都看成无向,dfs找出所有连通块并求每个连通块上所有点的数量之和,从大到小排序,取前m大即可。
时间复杂度:
代码
#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.最小相似度
题意
定义两个位数相等的二进制串的相似度
给定
个长度为
的二进制串。
现在的问题是找出一个额外的长度为
的二进制字符串
使得
最小。
因为满足条件的可能不止一个,不需要输出串,只需要输出这个最小值即可。
做法
由于M只有20,所以我们知道状态数一共只有2^20,我们如果能算出每个状态和给定n个字符串相似度的最大值,这道题就解决了。首先我们知道所有n个串最初的dp值为m,通过bfs,把每个串丢进队列一次,每个串改变每一位得到新的字符串,更新那些没出现过的串的dp值再丢进队列,就可以保证当前步数为最大相似度。而且保证每个串只进一次队列.复杂度就是
代码
#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.球的体积并
题意
求两个球的体积并
做法
用球缺公式+计算几何模板即可解决。
球缺公式为:
其中
是球的半径,
是球缺的高。
代码
#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;
}