ZJNU 2022-02-26 个人排位赛1 部分题解
2022/02/27 UPD:
- 更正 \(C\) 题公比少了平方
- 开头补充
INF
的数值 - \(I\) 题补充特判
写在前面:
文中使用到的宏定义等参照此处,下文不再重复出现
#define closeSync ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define multiCase int T;cin>>T;for(int t=1;t<=T;t++)
#define rep(i,a,b) for(auto i=(a);i<=(b);i++)
#define repp(i,a,b) for(auto i=(a);i<(b);i++)
#define per(i,a,b) for(auto i=(a);i>=(b);i--)
#define perr(i,a,b) for(auto i=(a);i>(b);i--)
#define REP(i,a,b,s) for(auto i=(a);i<=(b);i+=s)
#define REPP(i,a,b,s) for(auto i=(a);i<(b);i+=s)
#define PER(i,a,b,s) for(auto i=(a);i>=(b);i-=s)
#define PERR(i,a,b,s) for(auto i=(a);i>(b);i-=s)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
typedef long long ll;
typedef pair pii;
const int INF=0x3f3f3f3f;
A - An (Almost) Perfect Match
没读,题解好像是记忆化搜索
B - Good Coin Denomination
给定已按升序排序的至多 \(10\) 个数字 \(\{a\}\),问是否满足 \(2a_i\le a_{i+1}\) ?
解
直接判断
void solve()
{
int n;
cin>>n;
int a[n];
cout<<"Denominations:";
for(int i=0;i>a[i];
cout<<' '<
C - Cameron’s Crazy Circles
给定直角三角形的两直角边 \(a,b\ (a\lt b)\)
定义其第三条边为 \(c\),并绘制出其内接三角形 \(C_1\)
再沿着较长的直角边 \(b\) 继续绘制与 \(b,c\) 及 \(C_1\) 相切的内圆 \(C_2\)
如此循环下去,绘制出无限个圆 \(C_i\)
问所有绘制出的圆的面积与该三角形的面积之比,即 \(\frac{\sum C_i}{S_{\triangle}}\)
解
首先求出最大的内接圆的半径 \(R\),由于是直角三角形,所以公式直接得出 \(R=\frac{a+b-c}2\)
(不知道这条公式的就先求两条角平分线,得出交点后来算 \(R\) 吧,见代码)
根据勾股定理,算得所有可以绘制出的圆的直径和 \(totLen=\sqrt{R^2+(b-R)^2}+R\)
转为另一种方式看 \(C_1,C_2\) 两个圆,绘制出两相似等腰三角形,见下图蓝线
可得小三角形与大三角形的边长比即 \(ratio = \frac{totLen-2R}{totLen} = 1-\frac{2R}{totLen}\)
由圆形面积公式 \(S_1=\pi R^2\) ,可知面积与边长呈平方关系
圆 \(C_2\) 的半径 \(R_2=R\times ratio\),圆 \(C_2\) 的面积即 \(S_2=\pi R_2^2=\pi R^2ratio^2=ratio^2S_1\)
无限绘制圆形,比例不变,得 \(S_{i+1}=ratio^2S_i\)
这是一个首项为 \(S_1=\pi R^2\),公比为 \(ratio^2\) 的等比数列
\(\sum S_i = \frac {S_1(1-ratio^{\infin})}{1-ratio^2} = \frac{S_1}{1-ratio^2}\)
答案即 \(\frac{2\sum S}{a\times b}\)
void solve()
{
double a,b;
cin>>a>>b;
Point p1=Point(0,0);
Point p2=Point(0,a);
Point p3=Point(b,0);
Vector v21=p1-p2;
Vector v23=p3-p2;
Vector v2f=Rotate(v21,Angle(v21,v23)/2); // 旋转一半求角平分向量,板子太长不放了
Line la=Line(p1,Point(1,1));
Line lb=Line(p2,p2+v2f);
Point cross=la.intersectPoint(lb); // 求出内接圆圆心
double R=cross.x; // R=(a+b-c)/2 ,上面可以都不用写
double S=PI*R*R;
double totLen=sqrt(R*R+(b-R)*(b-R))+R;
double rt=1-R*2/totLen;
double totS=S/(1-rt*rt);
cout<
D - Matrix Transformation
给定一个 \(n\times m\) 的矩形,每次可以选择任意两个相邻位置,令该两位置值同时 \(+1\) 或 \(-1\)
问是否存在一种方式能够使得矩形内每个位置的值均为 \(0\)
解
按照每个位置的 \(x+y\) 的奇偶性进行分组
由于每次选择的是两个相邻的位置,因此 \(x+y\) 的奇偶性一定不同
又因为两个位置的值同时操作,且改变量相同,因此本题就变成了判断两组数值之和是否相同
int a[111][111];
void solve()
{
int n,m;
cin>>n>>m;
int s1=0,s2=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
cin>>a[i][j];
for(int j=i%2+1;j<=m;j+=2)
s1+=a[i][j];
for(int j=2-i%2;j<=m;j+=2)
s2+=a[i][j];
}
if(s1==s2)cout<<"YES\n";
else cout<<"NO\n";
}
signed main()
{
closeSync;
multiCase
{
cout<<"Case #"<
E - Cut the Cake!
按相邻边的顺序给定一个多边形蛋糕,被 \(y=Y\) 的一条平行于 \(x\) 轴的直线切成上下两部分
保证蛋糕一定会被切割到,且不会切割到蛋糕的任意顶点上
问两部分蛋糕的周长分别是多长
解
定义两个多边形容器 \(poly1/poly2\) 存两部分蛋糕的每个点
从任意某条边开始,枚举所有边,并判断每条边是否会被 \(y=Y\) 切割
如果不会被切割,则将上一个点归为当前的多边形容器内
如果边 \(p_ip_{i+1}\) 被切割,交点为 \(pc\),则将 \(pc\) 放在当前多边形容器内,接下来切换为另一个容器来收集点(一旦产生交点即表示跨越了 \(y=Y\) 这条线,前一个点与后一个点应当归属于不同的多边形容器)
如上图,按照顺序加入多边形的点如右侧所示(交点 \(pc_i\) 应当同时存在两个多边形中)
得到的两个新多边形,可以直接通过两两相邻点距离之和求出周长
Point p[10];
vector poly[2];
void solve()
{
for(int t=0;t<2;t++)
poly[t].clear();
int n,y;
cin>>n>>y;
Line ly=Line(Point(0,y),Point(1,y));
for(int i=0;i>p[i].x>>p[i].y;
int cur=0;
for(int i=0;iy)!=(p[(i+1)%n].y>y)) // 判断i与(i+1)%n两点是否位于y=Y这条线两侧
{
Point pp=Line(p[i],p[(i+1)%n]).intersectPoint(ly); // 获得交点
poly[cur].pb(pp);
cur^=1; // 切换另一个容器继续收集点
poly[cur].pb(pp);
}
}
double S[2]={0,0};
rep(t,0,1)
repp(i,0,poly[t].size()) // 遍历两个多边形容器内每条边,直接求距离和即可
S[t]+=Distance(poly[t][i],poly[t][(i+1)%poly[t].size()]);
if(S[0]>S[1])
swap(S[0],S[1]);
cout<
F - Camp Out
每天 \(24\) 小时被分为 \(6\) 个时间段 \(0\sim 4,4\sim 8,\cdots,20\sim 24\) (左闭右开)
你需要安排 \(10\) 个人,在一个星期 \(7\) 天 \(168\) 个小时内搭帐篷,且需满足以下限制:
- 每个时段至少有 \(3\) 个人搭帐篷
- 只有整个时间段内 \(4\) 小时都有空的人才可以安排该时间段给他
- 每个人至多被安排 \(80\) 个小时
现在给定每个人没有空的时间段,问是否存在一种合法安排方案?
解
标准二分图权值匹配题,下面考虑用最大流解(KM也可以)
首先找出二分图两侧点的代表,明显一侧指人,一侧指时间段
源点向每个人连边,流量为 \(20\),表示每个人至多被安排 \(20\) 个时间段
每个时间段向汇点连边,流量为 \(3\),这里贪心让每个时间段只安排 \(3\) 人就行,最后判断网络是否能够流满即可
对于每个人 \(i\),只要时间段 \(j\) 有空,就从 \(i\) 向 \(j\) 连一条流量为 \(1\) 的边,表示该人可以安排时间段 \(j\)
最后判断最大流是否等于 \(168/4*3=126\) 即可
int bz[55];
void solve()
{
init(54);
rep(i,1,10)
addedge(53,i,20);
rep(i,11,52)
addedge(i,54,3);
rep(i,1,10)
{
mst(bz,0);
int n;
cin>>n;
rep(j,1,n)
{
int d,s,e;
cin>>d>>s>>e;
s=s/4+(d-1)*6+11;
e=(e-1)/4+(d-1)*6+11;
rep(k,s,e)
bz[k]=1; //表示该时间段没空
}
rep(k,11,52)
if(!bz[k])
addedge(i,k,1); //有空的连边
}
int f=dinic(53,54);
//cout<
G - Sorry About That, Chief!
判断给定数是否是素数,如果不是,问与最近的素数差值是多少
解
素数筛,每个询问暴力即可
bool vis[22222];
void init()
{
rep(i,2,20000)
if(!vis[i])
REP(j,i*i,20000,i)
vis[j]=true;
}
void solve()
{
int n;
cin>>n;
cout<<"Input value: "<
H - Knight Moves – Gold Edition
I - Knight Moves – Black Edition
问 \(n\times n\) 的国际象棋棋盘上,骑士从 \((r_1,c_1)\) 走到 \((r_2,c_2)\) 的最少步数
EZ解
bfs
int d[25][25];
const int ddx[8]={-2,-2,-1,-1,1,1,2,2};
const int ddy[8]={-1,1,-2,2,-2,2,-1,1};
void solve()
{
int n,r1,c1,r2,c2;
cin>>n>>r1>>c1>>r2>>c2;
mst(d,INF);
d[r1][c1]=0;
queue q;
q.push(pii(r1,c1));
while(!q.empty())
{
pii p=q.front();
q.pop();
repp(i,0,8)
{
int px=ddx[i]+p.first;
int py=ddy[i]+p.second;
if(px>0&&py>0&&px<=n&&py<=n&&d[px][py]>d[p.first][p.second]+1)
{
d[px][py]=d[p.first][p.second]+1;
q.push(pii(px,py));
}
}
}
cout<
HD解
大范围贪心 + 小范围暴力bfs
为了方便,根据对称轴翻转,使得 \((r_1,c_1)\) 出现在 \((r_2,c_2)\) 的左下角
其后再根据点 \((r_1,c_1)\) 位于那一块区域内进行分类讨论
如果点位于上图区域 \(1\) 内,贪心可知刚开始每次都走 \((+2,+1)\) 最优,直到 \(c1=c2\) 时,再反复 \((+2,-1),(+2,+1)\) 向右走,如下图所示:
如果点位于上图区域 \(4\) 内,贪心可知刚开始每次都走 \((+1,+2)\) 最优,直到 \(r1=r2\) 时,再反复 \((-1,+2),(+1,+2)\) 向上走,如下图所示:
如果位于上图区域 \(2\) 内,可以发现先走 \((+1,+2)\),直到与 \(y=\frac12x\) 这条直线接触时再一直走 \((+2,+1)\) 最优,但这样的走法可能比较难处理?
这种走法实际上与先接触 \(y=x\) 这条直线,再反复 \((+1,+2),(+2,+1)\) 所需要的步数是相同的,也好写OvO
区域 \(3\) 内也是相同,先靠近 \(y=x\) 再向右上走,如下图所示:
当然,以上也只是大范围贪心,小范围内这种走法可能会出问题
因此,最终快要接近终点的时候,限制一下不要直接取到最近的位置,稍微留出个 \(20\times 20\) 大小的位置来跑 EZ 版本的代码即可
int d[35][35];
const int ddx[8]={-2,-2,-1,-1,1,1,2,2};
const int ddy[8]={-1,1,-2,2,-2,2,-1,1};
int solve2(int n,int r1,int c1,int r2,int c2)
{
mst(d,INF);
d[r1][c1]=0;
queue q;
q.push(pii(r1,c1));
while(!q.empty())
{
pii p=q.front();
q.pop();
repp(i,0,8)
{
int px=ddx[i]+p.first;
int py=ddy[i]+p.second;
if(px>0&&py>0&&px<=n&&py<=n&&d[px][py]>d[p.first][p.second]+1)
{
d[px][py]=d[p.first][p.second]+1;
q.push(pii(px,py));
}
}
}
return d[r2][c2];
}
void solve()
{
ll n,r1,c1,r2,c2;
cin>>n>>r1>>c1>>r2>>c2;
// 如果范围较小,直接调用EZ Ver.的解法
if(n<=20)
{
cout<r2)
{
r1=n-r1+1;
r2=n-r2+1;
}
if(c1>c2)
{
c1=n-c1+1;
c2=n-c2+1;
}
ll ans=0;
if(r2-r1>c2-c1) //如果x方向的增量比y方向的增量多,位于上图区域3,4
{
if(r1+(c2-c1)*2<=r2) //上图区域4
{
// 先 (+1,+2) 走到 c1=c2 的位置
ll d=c2-c1;
ans+=d;
r1+=d*2;
c1=c2;
// 再反复向上,每两步可以使 y+4
d=(r2-r1)/4-1; // 注意这里的 -1,为后面的暴力留出空间,下同
if(d>0)
{
ans+=d*2;
r1+=d*4;
}
}
else //上图区域3
{
ll d=(r2-r1)-(c2-c1);
ans+=d;
r1+=d*2;
c1+=d;
d=min(r2-r1,c2-c1)/3-1;
if(d>0)
{
ans+=d*2;
r1+=d*3;
c1+=d*3;
}
}
}
else
{
if(c1+(r2-r1)*2<=c2) //上图区域1
{
ll d=r2-r1;
ans+=d;
c1+=d*2;
r1=r2;
d=(c2-c1)/4-1;
if(d>0)
{
ans+=d*2;
c1+=d*4;
}
}
else //上图区域2
{
ll d=(c2-c1)-(r2-r1);
ans+=d;
c1+=d*2;
r1+=d;
d=min(r2-r1,c2-c1)/3-1;
if(d>0)
{
ans+=d*2;
r1+=d*3;
c1+=d*3;
}
}
}
// 最后差的一点点就暴力跑,特判边界
if(r1<=4&&c1<=4) // 左下边界
ans+=solve2(20,r1,c1,r2,c2);
else if(r2>=n-3&&c2>=n-3) // 右上边界
ans+=solve2(20,20-(n-r1),20-(n-c1),20-(n-r2),20-(n-c2));
else
ans+=solve2(20,10,10,10+(r2-r1),10+(c2-c1));
cout<
J - A Prickly Problem – Gold Edition
K - A Prickly Problem – Black Edition
给定一张仙人掌图(两两简单环最多只有一个公共点)
可以去除一些边使其变成一棵树,问生成树数量
解1
求生成树的数量,裸题
矩阵树定理,求无向连通图生成树计数,套高斯消元板子即可
没代码QAQ
解2
由于给定的图是仙人掌,明显答案就是每个简单环内的边数之乘积(由于生成树不存在环,因此每个简单环都可以任意选择一条边去掉)
考虑点双连通分量,求出每条边所在的 bcc 编号,再统计出每个 bcc 包含的边数,求乘积即可
没代码\(^2\)QAQ
解3
原理同解2,但可以直接根据 dfs 搜索出每个环内的边数
从任意一个点开始搜索,为每个搜到的点分配深度 \(depth\)
如果某个点此前没有被搜到,则朝下继续搜索
如果已经被搜索到,则此时的搜索深度 \(d\) 与原先为该点分配的深度 \(depth\) 的差值即环内边数
最后向上传递解即可
int n,m;
vector G[50050];
int dep[50050];
bool vis[50050];
int dfs(int u,int fa,int d)
{
if(vis[u])
return max(d-dep[u],1);
dep[u]=d;
vis[u]=true;
int r=1;
for(int &v:G[u])
{
if(v==fa)
continue;
r=r*dfs(v,u,d+1)%mod;
}
return r;
}
void solve()
{
cin>>n>>m;
rep(i,1,n)
{
G[i].clear();
dep[i]=0;
vis[i]=false;
}
rep(i,1,m)
{
int u,v;
cin>>u>>v;
G[u].pb(v);
G[v].pb(u);
}
cout<