A:Arithmetic Progression
B:Cannon
C:Draw Grids
题意:给你一个nm的区域,共有nm个点,两个人依次选两个点(a,b),(c,d)连线,需满足|a-c|+|b-d|=1且不能构成封闭图形,判断谁赢。
思路:根据绝对值等式可以得到每人每次只能选相邻的两个点,所以我们只要判断一下总共点数的奇偶性即可
代码:
#include <iostream>
using namespace std;
int main(){
int n,m; cin>>n>>m;
if((n*m)&1) cout<<"NO"<<endl;
else cout<<"YES"<<endl;
}D:Er Ba Game
思路:按题目意思模拟一下即可。
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int N = 5e3 + 10;
int main() {
int t; cin >> t;
while(t--) {
int a1,b1,a2,b2; cin >> a1 >> b1 >> a2 >> b2;
int a=(a1+b1)%10,b=(a2+b2)%10;
if(a1>b1) swap(a1,b1);
if(a2>b2) swap(a2,b2);
if(a1==a2&&b1==b2) cout << "tie" << endl;
else if(a1==2&&b1==8) cout << "first" << endl;
else if(a2==2&&b2==8) cout << "second" << endl;
else if(a1==b1&&a2!=b2) cout << "first" << endl;
else if(a1!=b1&&a2==b2) cout << "second" << endl;
else if(a1==b1&&a2==b2) {
if(a1>a2) cout << "first" << endl;
else if(a2>a1) cout << "second" << endl;
}
else if(a1!=b1&&a2!=b2) {
if(a>b) cout <<"first" << endl;
else if(b==a)
{
if(b1>b2) cout << "first" << endl;
else if(b2>b1) cout << "second" << endl;
}
else if(b>a) cout << "second" << endl;
}
}
return 0;
}E:Gas Station
F:Girlfriend
题意:已知四个点A,B,C,D坐标和k1,k2。再告诉你还有点p1和p2且满足∣AP1∣≥k1 *∣BP1∣,∣CP2∣≥k2 *∣DP2∣,求它们在空间上的相交部分的体积。
思路:以第一个式子为例,设A(x0,y0,z0),B(x1,y1,z1),p(x,y,z),代入不等式展开化简可得:()
(
)
(
)
(
)
(
)
又因为球的一般公式为:,球心坐标为
,半径为:
。
因为展开式中x、y、z的系数都相同,所以它的形状是个球。 所以这道题就转换成求两个球是否相交并输出相交的体积,一般方程里他们的系数都是1,所以我们要将化简的式子除以
,总的来说无非就内含、内切、相交三种情况。内含和内切就直接输出里面球的体积即可,如果相交的话又涉及到一个新的知识点——球冠。所谓球冠:球面被平面所截得的一部分叫做球冠。截得的圆叫做球冠的底,垂直于截面的直径被截得的一段叫做球冠的高。
球冠的体积公式为:。
如图是两球相交的一个平面图。圆心之间的距离我们用d表示,由余弦定理可得的值,
。代入上面求体积公式即可。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N=1;
const double pi = acos(-1);
double x[5],y[5],z[5];
double k1,k2;
void solve(double x1,double y1,double z1,double r1,double x2,double y2,double z2,double r2)
{
double ans=0;
//球心距离
double dis=sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)+(z1-z2)*(z1-z2));
//相离或相切
if(dis>=r1+r2){
ans=0;
}
//内含或内切
else if (dis+r1<=r2){
ans=(4.00/3.00)*pi*r1*r1*r1;
}
else if(dis+r2<=r1){
ans=(4.00/3.00)*pi*r2*r2*r2;
}
//相交
else{
double cal=(r1*r1+dis*dis-r2*r2)/(2.00*dis*r1);
//计算cos值
double h=r1*(1-cal);
ans+=(1.00/3.00)*pi*(3.00*r1-h)*h*h;
cal=(r2*r2+dis*dis-r1*r1)/(2.00*dis*r2);
h=r2*(1.00-cal);
ans+=(1.00/3.00)*pi*(3.00*r2-h)*h*h;
}
printf("%.3lf\n",ans);
}
int main(){
int t; cin >> t;
while(t--){
for(int i=0;i<4;i++)
cin>>x[i]>>y[i]>>z[i];
cin>>k1>>k2;
double tmp1=k1*k1-1;
double c1x,c1y,c1z,c1r,tmp;
c1x=(k1*k1*x[1]-x[0])/tmp1;
c1y=(k1*k1*y[1]-y[0])/tmp1;
c1z=(k1*k1*z[1]-z[0])/tmp1;
tmp=k1*k1*((x[1]*x[1])+(y[1]*y[1])+(z[1]*z[1]))-x[0]*x[0]-y[0]*y[0]-z[0]*z[0];
tmp/=tmp1;
c1r=sqrt(c1x*c1x+c1y*c1y+c1z*c1z-tmp);
double tmp2=k2*k2-1;
double c2x,c2y,c2z,c2r;
c2x=(k2*k2*x[3]-x[2])/tmp2;
c2y=(k2*k2*y[3]-y[2])/tmp2;
c2z=(k2*k2*z[3]-z[2])/tmp2;
tmp=k2*k2*((x[3]*x[3])+(y[3]*y[3])+(z[3]*z[3]))-x[2]*x[2]-y[2]*y[2]-z[2]*z[2];
tmp/=tmp2;
c2r=sqrt(c2x*c2x+c2y*c2y+c2z*c2z-tmp);
solve(c1x,c1y,c1z,c1r,c2x,c2y,c2z,c2r);
}
return 0;
}
G:League of Legens
题意:给你n个区间,将它们分为k组,保证每组的线段都相交,问k组相交部分和最大是多少。
思路:如果一个大区间包含一个小区间,那么把大区间拿出来才会是最优解,因为你添加区间相当于是添加了一个限制,所以我们先进行把所有包含小区间的大区间筛选出来,然后对剩下的小区间进行讨论。
dp[i][j]代表分了i组用了前j个区间的最优答案。对于dp[i][j],我们枚举最后一组的人数k,然后从转移,加上这一组的游戏时间取最大值。状态转移方程如下:
所以将剩下的区间按照左右端点排序,不难得出左右端点均是单调不降的。但是这些区间可能没有交集,所以我们可以用单调队列去模拟,没有交集就弹出head,直到有交集为止,然后在该点进行转移,即经典的单调队列优化,每次转移记得更新tail。 最后统计答案的时候判断一下是否需要加上大区间的贡献即可。
单调队列压入的是j-k的可能值,所以最后一组的成员应该是从j-k+1到j,所以在计算最后一组的时间时需要+1来得到最后一组的游戏结束时间。例如:tot=5,此时i=3,那么j应该从3开始枚举,因为人数要大于等于组数,所以dp[3][3]只能从dp[2][2]转移,继续往后,i=3,j=4,此时dp[3][4]可以从dp[2][2]和dp[2][3]转移,但是dp[2][2]在i=3,j=3时已经考虑过压入单调队列,也就是说j-1前可转移的dp在之间的j循环里就已经全部考虑过了,所以dp[i-1][j-1]是唯一一个需要更新到单调队列中的dp元素。
代码:
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int N = 1e4+5;
struct node{
int l,r;
}a[N],b[N];
int c[5005],q[5005],dp[5005][5005];
bool cmp(node x,node y) {
if(x.l!=y.l) return x.l<y.l;
return x.r>y.r;
}
int main()
{
memset(dp,-1,sizeof(dp));
int n,k; cin >> n >> k;
for(int i=1;i<=n;i++) {
cin >> a[i].l >> a[i].r;
}
sort(a+1,a+1+n,cmp);
int maxn=INF;
int cnt=0,tot=0;
for(int i=n;i>=1;i--) {
if(a[i].r>=maxn) {
c[++cnt]=a[i].r-a[i].l;
}
else {
maxn=a[i].r,b[++tot]=a[i];
}
}
reverse(b+1,b+tot+1);
sort(c+1,c+1+cnt,greater<int>());
dp[0][0]=0;
for(int i=1;i<=k;i++) {
int head=1,tail=0;
for(int j=i;j<=tot;j++) {
//j=i是因为人数应该要大于等于组数
if(dp[i-1][j-1]>-1) {//防止从非法解处转移
while(head<=tail&&dp[i-1][q[tail]]+b[q[tail]+1].r<=dp[i-1][j-1]+b[j].r) tail--;//维护单调队列最大值
q[++tail]=j-1;
}
while(head<=tail&&b[q[head]+1].r<=b[j].l) head++;//弹出队首直至两个区间有交集
if(head<=tail) dp[i][j]=dp[i-1][q[head]]+b[q[head]+1].r-b[j].l;
}
}
int tmp=0,ans=0;
for(int i=0;i<=min(k,n);i++) {
tmp+=c[i];
if(dp[k-i][tot]>-1) ans=max(ans,dp[k-i][tot]+tmp);
}
cout << ans << endl;
return 0;
}H:Olefin
I:Penguins
交给队里最不会写搜的我来写这道题也就我队友能干出来这种事,又是被支配的一天。
题意:给你两个20*20的图,'#'代表墙,'.'代表空格,每个图有一个企鹅一开始分别位于(20,20),(20,1),它们分别想走到(1,20),(1,1),他们同时出发且走法为镜像,即左边企鹅往左走,那么右边的企鹅就会往右走。当两只企鹅都到达各自目的地时结束。如果有多种走法输出最小步数最小且移动的字典序要最小(D<L<R<U)的那种,并将走过的路径改为A。
思路:bfs,每次移动后记录下移动后的点是从哪个点走过来的。假如左边的企鹅此时它的左边已经是墙或者是边界,但右边的企鹅需要往右走(镜像原则,左边的企鹅要往左),那么左边的企鹅将会因为路不通而待在原地。
代码:
#include <bits/stdc++.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int N = 5e3 + 10;
const int eps = 1e-3;
bool flag;
char s1[25][25],s2[25][25];
string p="DLRU";
int vis[25][25][25][25];//统计走到该位置用了多少步
string dir;//记录走的方向
int cnt;
int dir1[4][2]={1,0,0,-1,0,1,-1,0};//左边企鹅,方向对应string p,保证字典序最小
int dir2[4][2]={1,0,0,1,0,-1,-1,0};//哟便企鹅
struct node{
int x1,y1,x2,y2;
};
int now[25][25][25][25][5];//记录当前位置从哪个位置走来
void print()
{
cout << vis[1][20][1][1]-1<< endl;
for(int i=dir.length()-1;i>=0;i--) cout << dir[i];
cout << endl;
s1[20][20]='A'; s2[20][1]='A'; //记得最好要将出发点更新
for(int i=1;i<=20;i++) {
printf("%s ",s1[i]+1);
printf("%s\n",s2[i]+1);
}
}
int main()
{
for(int i=1;i<=20;i++) {
scanf("%s",s1[i]+1);
scanf("%s",s2[i]+1);
}
queue<node> q;
q.push({20,20,20,1});
vis[20][20][20][1]=1;
while(!q.empty()){
node tmp=q.front();
q.pop();
for(int i=0;i<4;i++){
int dx1=tmp.x1+dir1[i][0];
int dy1=tmp.y1+dir1[i][1];
int dx2=tmp.x2+dir2[i][0];
int dy2=tmp.y2+dir2[i][1];
if(dx1<1||dx1>20){
dx1-=dir1[i][0];
}
if(dy1<1||dy1>20){
dy1-=dir1[i][1];
}
if(dx2<1||dx2>20){
dx2-=dir2[i][0];
}
if(dy2<1||dy2>20){
dy2-=dir2[i][1];
}
if(s1[dx1][dy1]=='#'){
dx1-=dir1[i][0];
dy1-=dir1[i][1];
}
if(s2[dx2][dy2]=='#'){
dx2-=dir2[i][0];
dy2-=dir2[i][1];
}
if(vis[dx1][dy1][dx2][dy2])
{
continue;
}
q.push({dx1,dy1,dx2,dy2});
vis[dx1][dy1][dx2][dy2]=vis[tmp.x1][tmp.y1][tmp.x2][tmp.y2]+1;
now[dx1][dy1][dx2][dy2][0]=i;//i对应string p的下标,每个pi代表着一个方向
now[dx1][dy1][dx2][dy2][1]=tmp.x1;//记录从哪个点走来
now[dx1][dy1][dx2][dy2][2]=tmp.y1;
now[dx1][dy1][dx2][dy2][3]=tmp.x2;
now[dx1][dy1][dx2][dy2][4]=tmp.y2;
if(dx1==1&&dy1==20&&dx2==1&&dy2==1){//两只企鹅都到目的地时更新地图 ,
int a=1,b=20,c=1,d=1; // 将走过的路径改为'A'
while(a!=20||b!=20||c!=20||d!=1) {//相当于倒着将最短路径走一边,直到返回出发点
s1[a][b]='A';//更新地图
s2[c][d]='A';
dir+=p[now[a][b][c][d][0]];
int a1=now[a][b][c][d][1];
int b1=now[a][b][c][d][2];
int c1=now[a][b][c][d][3];
int d1=now[a][b][c][d][4];
a=a1,b=b1,c=c1,d=d1;
}
print();
flag=1;
break;
}
}
}
if(!flag) cout << -1 << endl;
return 0;
}J:Product of GCDs
K:Stack
题意:含有n个数的序列a,现在将a中元素入栈,每次入栈将栈顶大于ai的弹出,bi代表栈的大小。现在告诉你部分bi,求出满足题意的序列a。
思路:
先记录下每个位置的栈中元素个数,然后从1-n遍历一遍,如果该位置元素个数为0,则该位置元素个数为上一个位置元素个数+1,如果该位置个数大于上一个上一个位置元素个数,不符合题意直接输出-1。否则,我们从后往前遍历,把1,2,3,4,5······按递增顺序放入栈里,因为单调栈元素中的数量代表的是递增数字的数量,当加入数字后的个数等于栈中元素数量的时候,就直接把数字当做该点的值即可。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
int a[N],st[N],b[N];
int flag;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n,k; cin >> n >> k;
for(int i=1;i<=k;i++) {
int x; cin >> x >>b[x];
}
for(int i=1;i<=n;i++) {
if(b[i]==0) {
b[i]=b[i-1]+1;
}
else
{
if(b[i]>b[i-1]+1) {
cout << -1 << endl;
return 0;
}
}
//cout << "ceshi: " << a[i] <<" ";
}
//cout << endl;
//for(int i=1;i<=n;i++) cout << a[i] << " "; cout << endl;
int cnt=0,top=0;
for(int i=n;i>=1;i--) {
while(b[i]>top)
{
top++;
cnt++;
st[top]=cnt;
}
a[i]=st[top];
top--;
}
for(int i=1;i<=n;i++) cout << a[i] << " ";
cout << endl;
return 0;
} L:WeChat Walk

京公网安备 11010502036488号