2021牛客暑期多校训练营

2021牛客暑期多校训练营_第1张图片

2021牛客暑期多校训练营1

A- Alice and Bob

/*
博弈问题
两堆石头,每人每次从其中一堆拿k(k>0)个,
同时从另一堆拿s*k个(s>=0),不能进行操作的即失败
*/
#include 
using namespace std;
const int maxn=5e3+10;
typedef long long ll;
bool a[maxn][maxn];
int main(){
	 //从终点状态(P)出发,由下往上推到其他的状态(N) 
	 //打表找出所有N,P状态 
	 //(0,0)为必败态P,往上能推到的都是必胜态,否则就是必败态	 
	 for(int i=0;i<=5000;i++){
        for(int j=0;j<=5000;j++){
            if(!a[i][j]){
                for(int k=1;k+i<=5000;k++){
                    for(int z=0;z*k+j<=5000;z++){
                        a[i+k][j+z*k]=1;
                    }
                }
                for(int k=1;k+j<=5000;k++){
                    for(int z=0;z*k+i<=5000;z++){
                        a[i+k*z][j+k]=1;
                    }
                }
            }
        }
    }
  	int t;
   	scanf("%d",&t);
   	while(t--){
   		int n,m;
   		scanf("%d%d",&n,&m);
   		puts(a[n][m] ? "Alice" : "Bob");
	   }
	return 0;
}

B-Ball Dropping

简单几何问题,赛时把问题想的略微有点复杂,导致公式推了很久,浪费了一些不必要的时间。另外还需注意精度问题!

/*
简化图形!
用最少的变量来求解 
三角形相似原理
*/ 
#include
using namespace std;
const int maxn=1e5+10;
typedef long long ll;
int main(){
	double r,a,b,h;
	scanf("%lf%lf%lf%lf",&r,&a,&b,&h);
	if(2*r<=b){
		cout<<"Drop\n";
		return 0;
	}
	double x,y,l,ans;
	x=b/(a-b)*h;
	l=sqrt((a/2)*(a/2)+(h+x)*(h+x));
	ans=2*r*l/a-x;
	cout<<"Stuck\n";
	printf("%.10lf\n",ans);
}

D- Determine the Photo Position

签到题

#include 
using namespace std;
const int maxn=5e3+10;
typedef long long ll;
string s[maxn];
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=0;i<n;i++){
		cin>>s[i];
	}
	string t;
	cin>>t;
	for(int i=0;i<t.length();i++){
		t[i]='0';
	}
	int ans=0;
	for(int i=0;i<n;i++){
		for(int j=0;j+m-1<s[i].length();j++){
			if(s[i].substr(j,m)==t){
				ans++;
			}
		}
	}
	cout<<ans<<endl;
	return 0;
}

F-Find 3-friendly Integers

一看数据范围如此之大,感觉应该找规律,打表之后发现,果然就是一道规律题(所有>=100的数都是符合条件的数)。不过仍需注意所给区间左右端点的位置分布情况。(均位于(1,100)?分别位于100的两边?均位于100的右边?)

#include 
#define ll long long
using namespace std;
const int mod = 998244353;
const int maxn = 1e6+10;
int num[101];
int check(string s){
	for(int i=0;i<s.length();i++){
		for(int j=1;j+i<=s.length();j++){
			int x=stoi(s.substr(i,j));
			if(x%3==0){
				return 1;
			}
		}
	}
	return 0;
}
int main(){
	int ans=0;
	for(int j=1;j<=100;j++){
		ans=0;
		for(int i=1;i<=j;i++){
			string s=to_string(i);
			if(check(s)) ans++;
		}
		num[j]=ans;
		//cout<
	} 
	int t;
	cin>>t;
	while(t--){
		ll n,m;
		cin>>n>>m;
		if(m<=100){
			cout<<num[m]-num[n-1]<<endl;
		}
		else if(m>100&&n<=100){
			cout<<num[100]-num[n-1]+m-100<<endl;
		}
		else cout<<m-n+1<<endl;
	}
	return 0;
} 

G-Game of Swapping Numbers

给定两个长度为n的数组A,B。对A数组恰好进行K次操作(交换任意两个元素的位置)使得A,B两数组位于相同为位置的元素差的绝对值之和最大。
即求:
∑ i = 1 n ∣ A i − B i ∣ \sum\limits_{i=1}^{n}|A_i-B_i| i=1nAiBi的最大值
2021牛客暑期多校训练营_第2张图片

∣ A i − B i ∣ |A_i-B_i| AiBi=max{ A i , B i A_i,B_i Ai,Bi}-min{ A i , B i A_i,B_i Ai,Bi}
1、因为最终结果本质上只与符号分配有关,与元素所在位置无关,为简化计算,我们将 A i , B i A_i,B_i Ai,Bi中的较大数存入A数组中,较小数存入B数组中,即使得 ∀ i ∈ [ 1 , n ] , B i ≤ A i { \forall}i{\in}[1,n],B_i{\leq}A_i i[1,n],BiAi
2、现在考虑,数组元素的交换,会对最终结果产生怎样的影响。
对于( A i , B i A_i,B_i Ai,Bi)( A j , B j A_j,B_j Aj,Bj),经过1的处理,我们可以明确:
A i > B i A_i>B_i Ai>Bi, A j > B j A_j>B_j Aj>Bj
故只需讨论 B i B_i Bi A j A_j Aj的大小关系

2021牛客暑期多校训练营_第3张图片

由上图可知:
只有当 A j < B i A_jAj<Bi时,交换元素会使最终结果有所增加(有效交换),且增量为:
d e l = 2 ( m i n ( A i , B i ) − m a x ( A j , B j ) ) del=2(min(A_i,B_i)-max(A_j,B_j)) del=2(min(Ai,Bi)max(Aj,Bj))
(其实有了步骤1的处理, d e l = 2 ( B i − A j ) del=2(B_i-A_j) del=2(BiAj))
故要使del最大,则 B i B_i Bi要更大, A j A_j Aj要更小,即进行交换操作之后,AB两数组对应位置的元素差值要尽可能大。
因此我们将数组A升序排列,数组B降序排列(相当于对数组进行了n次交换操作)
3、对下图结论的理解:
n=2时,k为奇数,则两元素必须交换位置;k为偶数,相当于不用交换位置(最终会换回来)
n>2时,若当前交换了m次,但最终结果已经达到最大( m ≤ m{\leq} mk),则后续交换操作做不做都无所谓(如果要继续进行交换操作使次数恰好达到k次,那么只需要交换那些对最终结果没有影响的元素,即进行无效交换)
2021牛客暑期多校训练营_第4张图片
4、遍历排序后的数组(已经交换好了),计算del,当 d e l ≤ 0 del{\leq}0 del0时,即可停止(结果已经达到最大,无需再次进行交换操作)。

/*牛客给的标程没有对A,B数组进行步骤1的处理,他特判了n=2的情况,处理之后,不特判也是正确的*/
#include 
#define INF 0x3f3f3f3f
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn = 5e5 + 10;
const int mod=1e9+10;
int a[maxn];
int b[maxn];
bool cmp(int a,int b){
	return a>b;
}
int main(){
	int n,k;
	cin>>n>>k;
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	ll ans=0;
	for(int i=0;i<n;i++){
		cin>>b[i];
		if(a[i]<b[i]) swap(a[i],b[i]);
		ans+=(a[i]-b[i]);
	}
	sort(a,a+n);
	sort(b,b+n,cmp);
	for(int i=0;i<k&&i<n;i++){
		ll tmp=2*(b[i]-a[i]);
		if(tmp>0) ans+=tmp;
		else break;
	}
	cout<<ans<<endl;
    return 0;
}

2021牛客暑期多校训练营2

C-Draw Grids

题目大意:
每人每次可以选两点,然后用线将两点连接起来,注意在连线的时候不能围成封闭图形,谁先连线围成封闭图形谁就输了。

规律题,因为不能连成封闭图形,故对于n个点的图,最多只能连n-1条边。

#include
using namespace std;
typedef long long ll;
const int N = 2e5+10;
int main(){
    int n,m;
    cin>>n>>m;
    int ans=n*m-1;
    if(ans%2==1)
        cout<<"YES"<<endl;
    else
        cout<<"NO"<<endl;
    return 0;
}

D-Er Ba Game

简单模拟题,不过有一个点要注意:题目规定 a 1 ≤ b 1 a_1{\leq}b_1 a1b1, a 2 ≤ b 2 a_2{\leq}b_2 a2b2
因此在输入数据时,需判断一下是否需要进行交换
还有就是是否所有情况都考虑到了
大模拟其实最考验思维逻辑是否清晰和缜密了。

#include
using namespace std;
const int maxn=1e5+10;
int main(){
	int t;
	cin>>t;
	while(t--){
		int a1,b1,a2,b2;
		cin>>a1>>b1>>a2>>b2;
		if(a1>b1) swap(a1,b1);
		if(a2>b2) swap(a2,b2); 
		if(a1==a2&&b1==b2){
			cout<<"tie\n";
		}
		else if(a1==2&&b1==8){
			cout<<"first\n";
		}
		else if(a2==2&&b2==8){
			cout<<"second\n";
		}
		else if(a1==b1&&a2!=b2){
			cout<<"first\n";
		}
		else if(a1!=b1&&a2==b2){
			cout<<"second\n";
		}
		else if(a1==b1&&a2==b2){
			if(a1>a2) cout<<"first\n";
			if(a1==a2) cout<<"tie\n";
			if(a1<a2) cout<<"second\n";
		}
		else if(a1!=b1&&a2!=b2){
			int c1=(a1+b1)%10;
			int c2=(a2+b2)%10;
			if(c1>c2) cout<<"first\n";
			if(c1<c2) cout<<"second\n";
			if(c1==c2){
				if(b1>b2) cout<<"first\n";
				if(b1==b2) cout<<"tie\n";
				if(b1<b2) cout<<"second\n";
			}
		}
		
	}
}

I-Penguins

其实也可以说是模拟吧,题目怎么说就怎么做

#include
using namespace std;
const int maxn=1e5+10;
typedef long long ll;
string a[25],b[25];//存地图
bool vis[25][25][25][25];//标记该位置是否被访问过(两只企鹅一起)
//dlru题目要求字典序最小,故按此顺序
int dir[][2]={{1,0},{0,-1},{0,1},{-1,0}};
char d[4]={'D','L','R','U'};
struct node{
	int x1,y1,x2,y2;//记录两只企鹅的坐标
	string road;//记录当前路径
};
bool check(int x,int y,string map[]){//检查是否越界
	if(x<0||x>19||y<0||y>19||map[x][y]=='#') return false;
	return true;
} 
string bfs(){
	queue<node>q;
	q.push({19,19,19,19,""});//两只企鹅均从(19,19)出发
	vis[19][19][19][19]=true;
	while(!q.empty()){
		node p=q.front();
		q.pop();
		if(p.x1==0&&p.y1==19&&p.x2==0&&p.y2==19){//到达终点
			return p.road;
		}
		for(int i=0;i<4;i++){//向四个方向搜索
			int dx1=p.x1+dir[i][0],dy1=p.y1+dir[i][1];
			int dx2=p.x2+dir[i][0],dy2=p.y2+dir[i][1];
			bool f1=check(dx1,dy1,a),f2=check(dx2,dy2,b);
			if(f1&&f2){//两只企鹅都能前进
				if(vis[dx1][dy1][dx2][dy2]) continue;
				vis[dx1][dy1][dx2][dy2]=true;
				q.push({dx1,dy1,dx2,dy2,p.road+d[i]});//更新路径
			}
			else if(f1){//第一只企鹅能够前进,第二只被阻挡
				if(vis[dx1][dy1][p.x2][p.y2]) continue;
				vis[dx1][dy1][p.x2][p.y2]=true;
				q.push({dx1,dy1,p.x2,p.y2,p.road+d[i]});
			}
			else if(f2){//第二只企鹅能够前进,第一只被阻挡
				if(vis[p.x1][p.y1][dx2][dy2]) continue;
				vis[p.x1][p.y1][dx2][dy2]=true;
				q.push({p.x1,p.y1,dx2,dy2,p.road+d[i]});
			}
		}
	}
	return "";//不能到达终点
}
int main(){
	for(int i=0;i<20;i++){
		cin>>a[i]>>b[i];
		reverse(b[i].begin(),b[i].end());//为便于处理,我们对b地图的镜像进行搜索
	}
	string path=bfs();
	cout<<path.length()<<endl<<path<<endl;
	int x1=19,y1=19;
	int x2=19,y2=19;
	a[19][19]='A';a[0][19]='A';
	b[19][19]='A';b[0][19]='A';
	for(int i=0;i<path.length();i++){//按所得路径,za
		int dx1=x1,dy1=y1,dx2=x2,dy2=y2;
		if(path[i]=='D'){
			dx1++;
			dx2++;
		}
		else if(path[i]=='L'){
			dy1--;
			dy2--;
		}
		else if(path[i]=='R'){
			dy1++;
			dy2++;
		}
		else if(path[i]=='U'){
			dx1--;
			dx2--;
		}
		if(check(dx1,dy1,a)){
			a[dx1][dy1]='A';
			x1=dx1;
			y1=dy1;
		}
		if(check(dx2,dy2,b)){
			b[dx2][dy2]='A';
			x2=dx2;
			y2=dy2;
		}
	}
	for(int i=0;i<20;i++){
		cout<<a[i]<<" ";
		reverse(b[i].begin(),b[i].end());
		cout<<b[i]<<endl;
	}
	return 0;
	
}

F-Girlfriend

题目大意:
给定四个点 A , B , C , D A,B,C,D A,B,C,D的坐标。另外两个动点 P 1 、 P 2 P_1、P_2 P1P2,其轨迹需分别满足 ∣ A P 1 ∣ ≥ k 1 ∣ B P 1 ∣ |AP_1|{\geq}k_1|BP_1| AP1k1BP1 ∣ C P 2 ∣ ≥ k 1 ∣ D P 2 ∣ |CP_2|{\geq}k_1|DP_2| CP2k1DP2
求两动点轨迹相交部分的体积。

其实动点轨迹就是一个球!(即使想不起来,那就推一推呀!难道就这样直接放弃吗?)
2021牛客暑期多校训练营_第5张图片

#include 
#define INF 0x3f3f3f3f
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn = 1e5 + 10;
const int mod=1e9+10;
const double pi=acos(-1);
void dio(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);
        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("%.3f\n",ans);
}
int main(){
	int t;
	cin>>t;
	double x[10],y[10],z[10];
	double k1,k2;
	while(t--){
		for(int i=0;i<4;i++){
			cin>>x[i]>>y[i]>>z[i];
		}
		cin>>k1>>k2;
		double exp,x1,y1,z1,d1,r1,x2,y2,z2,d2,r2;
		exp=k1*k1-1;
		x1=(k1*k1*x[1]-x[0])/exp;
		y1=(k1*k1*y[1]-y[0])/exp;
		z1=(k1*k1*z[1]-z[0])/exp;
		d1=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];
		d1/=exp;
		r1=sqrt(x1*x1+y1*y1+z1*z1-d1);
		//cout<
		exp=k2*k2-1;
		x2=(k2*k2*x[3]-x[2])/exp;
		y2=(k2*k2*y[3]-y[2])/exp;
		z2=(k2*k2*z[3]-z[2])/exp;
		d2=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];
		d2/=exp;
		r2=sqrt(x2*x2+y2*y2+z2*z2-d2);
		//cout<
		dio(x1,y1,z1,r1,x2,y2,z2,r2);
	}
    return 0;
}

K-Stack

题目大意:
使用数组a来构造单调栈,若a[i]大于栈顶元素直接入栈;若a[i]小于栈顶元素,则将栈顶元素弹出,直至a[i]大于栈顶元素。b[i]则记录当前栈中的元素个数。现给出部分b[i],构造一个符合条件的数组a,若不可能则输出-1。

思维+构造
总体思路:先明确数组b(栈中的元素个数),再根据数组b构造数组a

  • 数组b:
    对于没有给出的b[i],我们直接令其等于b[i-1]+1
    如果出现b[i]>b[i-1]+1的情况,则不可能构造出符合条件的数组a
    a[i]入栈对栈中会产生两类影响:

1、直接入栈,使栈中元素+1
2、从栈顶开始,比它大的都出栈,再入栈,使栈中元素减少
所以b[i]最多比b[i-1]大1。

  • 数组a:
    倒序遍历数组b,从数字1开始往栈中放入b[i]个数字,此时栈顶元素即为对应的a[i]值。

如果栈中元素个数小于当前b[i],则表示还需要往栈中压入元素,直到元素个数达到b[i]。
如果栈中元素个数等于当前b[i],则直接将栈顶元素出栈。

为什么倒序?
单独看数组b,根据上面的分析我们可以知道,对于b[i]来说,其后的b[i+1],b[i+2]…b[n]是有可能会比它小的(即a[i]在入栈时,栈中某些元素出栈,也就是说栈顶出现了比a[i]大的元素)
举个例子,如果b[1]=1;b[2]=2;b[3]=3;b[4]=4;
如果我们正序遍历b,如下图。那么此时a[1]=1,a[2]=2,a[3]=3,a[4]=4
似乎觉得没什么问题。
2021牛客暑期多校训练营_第6张图片
但如果b[5]=4;意味着对于a[5]来说前面必须要有比它大的数字出现,可显然目前a[5]只能等于5了。所以这样构造出来的数组a肯定是不对的。
之所以不能正序遍历数组b,也就是因为可能出现这种情况。
那为什么倒序遍历就不会出现这种情况呢?
对于b[1]=1;b[2]=2;b[3]=3;b[4]=4;b[5]=5;
我们首先看b[5],往栈中填入4个数字,然后将栈顶元素赋给a[5],并弹出栈顶元素。
接下来是b[4],此时栈中仅剩三个元素,所以还需压入一个元素。因此a[4]=5;
对于剩下的数,每次都直接赋值并出栈即可。
这样构造出来的数组a,显然是满足条件的。
(但总感觉我的思维还不是完全清晰,只能通过实例来理解了)
2021牛客暑期多校训练营_第7张图片

#include 
#define INF 0x3f3f3f3f
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn = 1e6+10;
const int mod=1e9+10;
int a[maxn],b[maxn];
int st[maxn];
int main(){
	int n,k;
	cin>>n>>k;
	while(k--){
		int p,x;
		cin>>p>>x;
		b[p]=x;
	}
	for(int i=1;i<=n;i++){
		if(b[i]==0) b[i]=b[i-1]+1;
	}
	int flag=0;
	for(int i=1;i<=n;i++){
		if(b[i]>b[i-1]+1){
			flag=1;
			break;
		}
	}
	if(flag){
		cout<<"-1\n";
		return 0;
	}
	int num=0,val=0;
	for(int i=n;i>=1;i--){
		while(b[i]>num){
			st[++num]=++val;
		}
		a[i]=st[num--];
	}
	cout<<a[1];
	for(int i=2;i<=n;i++) cout<<" "<<a[i];
    return 0;
}

2021牛客暑期多校训练营3

数论专场。。。开场即罚坐

J-Counting Triangles

题目大意:
有n个点的无向完全图,每条边被染成黑色或白色。现需找出图中由三条颜色相同的边构成的三角形的数量。

2021牛客暑期多校训练营_第8张图片
由上图可以知道,由图中三个点构成一个三角形,要么三条边颜色全都相同,要么只有一条边的颜色与其他两条不同。
我们可以知道,从n个顶点中任选三个点可以构成 C n 3 = n ( n − 1 ) ( n − 2 ) / 6 C_n^3=n(n-1)(n-2)/6 Cn3=n(n1)(n2)/6个三角形,再减去不符合要求的那些三角形,即可得到最终答案。
对于那些两边颜色不同的顶点,我们统计其数量cnt,那么不符合要求的三角形总数即为cnt/2。(这么理解:两个这样的点,会导致一个不符合条件的三角形)

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef long long ll;
typedef int64_t s64;
using namespace std;
const int maxn=1e6+5;
namespace GenHelper
{
    unsigned z1,z2,z3,z4,b,u;
    unsigned get()
    {
        b=((z1<<6)^z1)>>13;
        z1=((z1&4294967294U)<<18)^b;
        b=((z2<<2)^z2)>>27;
        z2=((z2&4294967288U)<<2)^b;
        b=((z3<<13)^z3)>>21;
        z3=((z3&4294967280U)<<7)^b;
        b=((z4<<3)^z4)>>12;
        z4=((z4&4294967168U)<<13)^b;
        return (z1^z2^z3^z4);
    }
    bool read() {
      while (!u) u = get();
      bool res = u & 1;
      u >>= 1; return res;
    }
    void srand(int x)
    {
        z1=x;
        z2=(~x)^0x233333333U;
        z3=x^0x1234598766U;
        z4=(~x)+51;
      	u = 0;
    }
}
using namespace GenHelper;
bool edge[8005][8005];
int x[8005];
int y[8005]; 
int main() {
  	ll n, seed;
  	cin >> n >> seed;
  	srand(seed);
  	for(int i = 0; i < n; i++){
  		for (int j = i + 1; j < n; j++){
  			edge[j][i] = edge[i][j] = read();
  			if(i==j) continue;
  			if(edge[i][j]==1){
  				x[i]++;
  				x[j]++;
			}
			else{
				y[i]++;
				y[j]++;
			}  
	  	}  
  	}
  	ll del=0;
  	for(int i=0;i<n;i++){
  		del+=x[i]*y[i];
 	}
 	del/=2;
 	ll ans=n*(n-1)*(n-2)/6;
 	ans-=del;
 	cout<<ans<<endl;    	
 	return 0;
}

2021牛客暑期多校训练营4

C-LCS

Now give you four integers a,b,c,n, you need to find three lowercase character strings s 1 s_1 s1, s 2 s_2 s2, s 3 s_3 s3 satisfy that ∣ s 1 ∣ = ∣ s 2 ∣ = ∣ s 3 ∣ = n |s_1|=|s_2|=|s_3|=n s1=s2=s3=n
and L C S ( s 1 , s 2 ) = a , L C S ( s 2 , s 3 ) = b , L C S ( s 1 , s 3 ) = c LCS(s_1,s_2)=a, LCS(s_2,s_3)=b, LCS(s_1,s_3)=c LCS(s1,s2)=a,LCS(s2,s3)=b,LCS(s1,s3)=c

0 ≤ a , b , c ≤ n 0{\leq}a,b,c{\leq}n 0a,b,cn 1 ≤ n ≤ 1000 1{\leq}n{\leq}1000 1n1000

目的:构造符合题目条件的三个长度为n的字符串
(其实就是根据约束条件一位一位的给字符串填上小写字母)
由于a,b,c的相对大小关系是不确定的,且在公共子序列范围内,两字符串每个位置的字母都必须相同,所以我们构造字符串时需要从LCS的最小值开始,(这样才能给自己留更多的余地去填剩下的字母呀)
(比赛的时候,又把问题想的过于复杂了,其实他总共就三个变量,三个字符串,就用最暴力的方法一位一位的安要求填字符串不就好了)
构造思想示例如下:
2021牛客暑期多校训练营_第9张图片
为什么最后分别补上d,e,f
最后操作是为了使字符串长度都能达到n,为避免此操作对字符串之间的LCS造成影响,破坏前面的构造,因此补位的时候使用了之前没有用到过的字母,且每个字符串都使用不同的字母,保证后续补位操作一定不会再产生公共子序列
对于不可能情况的判断:
根据a,b,c和n的值是能推出一个公式来判断所输入的值是否能成功构造出满足条件的字符串的。但有一种更直接的判断方法:完成所有构造步骤之后,判断一下是否存在字符串的长度大于n,若存在则输出“NO”

#include 
#define INF 0x3f3f3f3f
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn = 5e5 + 10;
const int mod=1e9+10;
vector<pair<int,int> >v;//first记录值,second记录下标
bool cmp(const pair<int,int>&p1,const pair<int,int>&p2){
	return p1.first<p2.first;//按值从小到大排序
}
int main(){
	int a[3],n;
	string s[3];
	for(int i=0;i<3;i++){
		int x;
		cin>>x;
		v.push_back(make_pair(x,i));
	}
	cin>>n;
	sort(v.begin(),v.end());
	int ind=v[0].second;
	//与ind相关的字符串即为s[ind],s[(ind+1)%3]
	for(int i=0;i<v[0].first;i++){
		s[ind]+='a';
		s[(ind+1)%3]+='a';
	}
//	cout<
//	cout<
	ind=v[1].second;
	int num=0;
	if(s[ind]==""){
		num=s[(ind+1)%3].size();
		for(int i=0;i<num;i++){
			s[ind]+='a';
		}
	}
	else{
		num=s[ind].size();
		for(int i=0;i<num;i++){
			s[(ind+1)%3]+='a';
		}
	}
	for(int i=0;i<v[1].first-num;i++){
		s[ind]+='b';
		s[(ind+1)%3]+='b';
	}
//	cout<
//	cout<
	ind=v[2].second;
	num=0;
	for(int i=0;i<min(s[ind].size(),s[(ind+1)%3].size());i++){
		if(s[ind][i]==s[(ind+1)%3][i]) num++;
	}
	num=v[2].first-num;
	for(int i=0;i<num;i++){
		s[ind]+='c';
		s[(ind+1)%3]+='c';
	}
//	cout<
//	cout<
//	cout<
	num=s[0].size();
	for(int i=0;i<n-num;i++){
		s[0]+='d';
	}
	num=s[1].size();
	for(int i=0;i<n-num;i++){
		s[1]+='e';
	}
	num=s[2].size();
	for(int i=0;i<n-num;i++){
		s[2]+='f';
	}
	if(s[0].size()>n||s[1].size()>n||s[2].size()>n) cout<<"NO\n";
	else{
		for(int i=0;i<3;i++){
			cout<<s[i]<<endl;
		}
	}
    return 0;
}

F-Just a joke

看到是博弈觉得肯定又得找规律,所以开始画图找规律。。。可是印象中比赛的时候越找越乱,后来才发现原来只和n+m的奇偶性有关。
每次可对无向图进行两种操作

1、选择一条边,将其删除
2、选择一个无环连通块,将其删除

操作1:边数减少1
操作2:顶点数减少k,边数减少(k-1)
可以发现,不论进行哪一种操作,对边数和点数总和的影响都是使其减少奇数个
因此,若最初n+m为偶数,则必须进行偶数次操作,即Bob获胜,否则Alice获胜。

#include 
#define INF 0x3f3f3f3f
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn = 5e5 + 10;
const int mod=1e9+10;
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=0;i<m;i++){//这题有点坑,不写这个,竟然也可以过,所以他搞这个输入就是为了干扰我们吧。。。
        int a,b;
        cin>>a>>b;
    }
	if((n+m)&1) cout<<"Alice\n";
	else cout<<"Bob\n";

    return 0;
}

I-Inverse Pair

inverse pair–逆序对

题目大意:
给定一个序列,可以对每个元素进行加1或加0操作,操作后需使该序列的逆序数最小,并输出此时序列的最小逆序数

回忆一下线性代数里面求逆序数的方法:对于当前元素,其后有多少个小于它的元素,该元素的逆序数就是几,每个元素的逆序数相加总和即为整个序列的逆序数。
因此要想减小序列的逆序数,则需尽量减小当前元素与其后比它小的元素的差距,即将其后比它小的元素加1,才有可能减少逆序数。
此题保证了原序列是由{1,2…n}构成的,其实就简单了很多
什么情况下对元素加1,会使逆序数减少呢?
如果x在x+1的后面,那我们对x进行加1操作,x+1进行加0 操作(即不变)就可以使逆序数减少啦。
操作完成后,再对序列计算一次逆序数即可。(逆序数板子

#include 
#define INF 0x3f3f3f3f
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn = 2e5 + 10;
const int mod=1e9+10;
ll a[maxn],vis[maxn],sig[maxn];
vector<ll>v;
ll merge(vector<ll> &a, ll begin, ll mid, ll end){ 
    ll nCount = 0; //前后两段之间逆序的个数 
    vector<ll >b; 
    ll j = begin, k = mid + 1; 
    ll n = end - begin + 1; 
    ll i = 0; 
    for (i = 0; i < n ; ++i) { 
        if (j > mid || k > end) break;
        if (a.at(j) <= a.at(k)){ 
            b.push_back(a.at(j)); 
            ++j; 
        } 
        else{ 
            b.push_back(a.at(k)); 
            nCount += mid - j + 1; 
            ++k; 
        } 
    } 
    while (j <= mid){ 
        b.push_back(a.at(j)); 
        ++j; 
    } 
    while (k <= end){ 
        b.push_back(a.at(k)); 
        ++k; 
    } 
    for (i = 0; i < n; ++i){ 
        a[begin + i] = b[i]; 
    } 
    return nCount; 
}
ll countReversed(vector<ll> &a, ll begin,ll end){  
    if (begin < end)  {  
        ll mid = (begin + end) / 2;  
        return (countReversed(a, begin, mid) +  
            countReversed(a, mid + 1, end) + merge(a, begin, mid, end));  
    }  
    else return 0;   
}  
int main(){
	int n;
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	for(int i=0;i<n;i++){
		if(vis[a[i]]) continue;
		int tmp=a[i]-1;
		if(!vis[tmp]&&!sig[tmp]){
			sig[tmp]=1;
			vis[a[i]]=1;
			vis[tmp]=1;
		}
	}
	for(int i=0;i<n;i++){
		if(sig[a[i]]==1){
			a[i]++;
		}
		v.push_back(a[i]);
	}
	cout<<countReversed(v,0,n-1)<<endl;
    return 0;
}

J-Average

2021牛客暑期多校训练营_第10张图片
综上只需分别对两数组求区间最大平均值,相加即可。
*注意精度

#include 
#define INF 0x3f3f3f3f
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn = 1e5 + 10;
const int mod=1e9+10;
ll sum[maxn];
bool check(ll x,ll *a,ll n,ll len){
    for (int i = 1; i <= n; i++){
        sum[i] = sum[i-1]+(a[i]-x);
    }
    ll y = 0x3f3f3f3f;
    for (int i = len; i <= n; i++){
        y = min(y, sum[i - len]);
        if (sum[i] - y >= 0) {
            return true;
        }
    }
    return false;
}
ll a[maxn],b[maxn];
int main(){
	ll n,m,x,y;
	cin>>n>>m>>x>>y;
	ll l = 0x3f3f3f3f, r = -0x3f3f3f3f;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		a[i]*=1000000;
		l = min(l, a[i]);
        r = max(r, a[i]);
	}
    while (l != r) {
        ll mid = (l + r + 1) / 2;
        if (check(mid,a,n,x)) l = mid;
        else r = mid - 1;
    }
    double ans1=l*1.0/1000000;
    //cout<
    l = 0x3f3f3f3f, r = -0x3f3f3f3f;
    for(int i=1;i<=m;i++){
    	cin>>b[i];
    	b[i]*=1000000;
    	l = min(l, b[i]);
        r = max(r, b[i]);
	}
	while (l != r) {
        ll mid = (l + r + 1) / 2;
        if (check(mid,b,m,y)) l = mid;
        else r = mid - 1;
    }
	double ans2=l*1.0/1000000;
	//cout<
	printf("%.10lf\n",ans1+ans2);
    return 0;
}

2021牛客暑期多校训练营5

H-Holding Two

题目大意:
构造一个大小为 n × m n\times m n×m的矩阵,其元素均为0或1。且该矩阵中不存在三个元素 A i 1 , j 1 , A i 2 , j 2 , A i 3 , j 3 A_{i1,j1},A_{i2,j2},A_{i3,j3} Ai1,j1,Ai2,j2,Ai3,j3满足
A i 1 , j 1 = A i 2 , j 2 = A i 3 , j 3 A_{i1,j1}=A_{i2,j2}=A_{i3,j3} Ai1,j1=Ai2,j2=Ai3,j3
其中 ∣ i 1 − i 2 ∣ = ∣ i 2 − i 3 ∣ ≤ 1 , ∣ j 1 − j 2 ∣ = ∣ j 2 − j 3 ∣ ≤ 1 |i_1-i_2|=|i_2-i_3|\leq1, |j_1-j_2|=|j_2-j_3|\leq1 i1i2=i2i31,j1j2=j2j31

同行、同列、斜边均不能出现三个相同的元素
2021牛客暑期多校训练营_第11张图片
2021牛客暑期多校训练营_第12张图片

图示位置也不能出现三个相同的元素
2021牛客暑期多校训练营_第13张图片
构造方法:两次010101…串,两次101010…串,不断重复即可

#include
using namespace std;
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
const int maxn=1e3+10;
const int mod=1e9+7;
typedef long long ll;
char s[1005];
char t[1005];
int main(){
	int n,m;
	cin>>n>>m;
	s[0]='0';
	t[0]='1';
	for(int i=1;i<m;i++){
		if(s[i-1]=='0') s[i]='1';
		else s[i]='0';
	}
	for(int i=1;i<m;i++){
		if(t[i-1]=='0')t[i]='1';
		else t[i]='0';
	}
	int cnt=0;
	for(int i=0;i<n;i++){
		cnt%=4;
		if(cnt==0||cnt==1){
			cout<<s<<endl;
			cnt++;
		}
		else if(cnt==2||cnt==3){
			cout<<t<<endl;
			cnt++;
		}	
	}	
    return 0;
}

K-King of Range

题目大意:
给定长度为n的数组,m个查询。每次查询给定一个k,
计算:最大值减去最小值大于k的子数组数量

发现:最大值减去最小值小于或等于num的子数组数量(x),是有模板的,所以改了改
总子数组的数量-x即为答案

//原Java模板
public class Test {
    public int getNum(int[] arr,int num){
        if(arr == null || arr.length == 0){
            return 0;
        }
        int res = 0;
        int i = 0;
        int j = 0;
        Deque<Integer> qmax = new LinkedList<>();
        Deque<Integer> qmin = new LinkedList<>();
        while(i < arr.length){
            while( j < arr.length){

                //维护窗口最大值
                while(!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[j]){
                    qmax.pollLast();
                }
                qmax.addLast(j);
                //维护窗口最小值
                while(!qmin.isEmpty() && arr[qmin.peekLast()] >= arr[j]){
                    qmin.pollLast();
                }
                qmin.addLast(j);
                if(arr[qmax.peekFirst()] - arr[qmin.peekFirst()] >num){
                     break;
                }
               j++;
            }
            res += j - i;
            if(qmax.peekFirst() == i){
                qmax.poll();
            }
            if(qmin.peekFirst() == i){
                qmin.poll();
            }
            i++;
        }
        return res;
    }
#include
using namespace std;
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
const int maxn=1e5+10;
const int mod=1e9+7;
typedef long long ll;
ll a[MAXN];	
ll n,m,k;
ll Sol(){
	IOS;
	ll res = 0;
	ll i = 0;
	ll j=0;
	deque<ll>qmax,qmin;//双端队列维护最大值和最小值的位置
	while(i<n){
		while(j<n){
			while(!qmax.empty() && a[qmax.back()] <= a[j]){
                    qmax.pop_back();
            }
            qmax.push_back(j);
            while(!qmin.empty() && a[qmin.back()] >= a[j]){
                    qmin.pop_back();
            }
            qmin.push_back(j);
            if(a[qmax.front()]-a[qmin.front()]>k) break;
			j++;
		}
		res+=j-i;
		if(qmax.front()==i){
			qmax.pop_front();
		}
		if(qmin.front()==i){
			qmin.pop_front();
		}
		i++;
	}
	return res;
}
int main() {
	cin >> n >> m;
	ll mmax = 0,mmin = 0x3f3f3f3f;
	for(int i = 0;i < n;i++){
		cin >> a[i];
		mmax = max(mmax,a[i]);
		mmin = min(mmin,a[i]);
	}
	while(m--){
		ll sum = 0;
		cin >> k;
		if(mmax - mmin <= k){
			cout << "0" <<endl;
			continue;
		}
		ll ans=n*(n+1)/2;
		sum=Sol();
		cout << ans-sum <<endl;

	}
 	return 0;
} 

2021牛客暑期多校训练营6

I-Intervals on the Ring

#include 
#define INF 0x3f3f3f3f
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e5+10;
const int mod=998244353;
int a[1010] = {0};
vector<pair<int,int> >v;
int main(){
	int t;
	cin >> t;
	while(t--){
		int n,m;
		cin >> n >> m;
		memset(a,0,sizeof(a));
		v.clear();
		while(m--){
			int l , r;
			cin >> l >> r;
			if(l <= r){
    			for(int i  = l;i <= r;i++){
    				a[i] = 1;
    			}
    		}
    		else{
    			for(int i  = l;i <= n;i++){
    				a[i] = 1;
    			}
    			for(int i  = 1;i <= r;i++){
    				a[i] = 1;
    			}
    		 }
    	}
    	for(int i=1;i<=n;i++){
    		if(a[i]){
    			int j=i;
    			while(j<=n&&a[j]==a[i]){
    				j++;
				}
				//cout<
				v.push_back(make_pair(i,j-1));
				if(i==n) break;
				i=j-1;
			}
		}
//		cout<
//		for(auto i:v){
//			cout<
//		}
		if(v.size()==1&&v[0].first==1&&v[0].second==n){
			cout<<"1"<<endl;
			cout<<"1"<<" "<<n<<endl;
		} 
		else if(v[0].first==1&&v[v.size()-1].second==n){
			cout<<v.size()-1<<endl;
			for(int i=1;i<v.size();i++){
				cout<<v[i].first<<" "<<v[i-1].second<<endl;
			}
		}
		else{
			cout<<v.size()<<endl;
			for(int i=1;i<v.size();i++){
				cout<<v[i].first<<" "<<v[i-1].second<<endl;
			}
			cout<<v[0].first<<" "<<v[v.size()-1].second<<endl;
		}
    }
    	
	return 0;
}

F-Hamburger Steak

2021牛客暑期多校训练营7

I-xay loves or

题目大意:
给定x和s,求满足x or y=s的正整数y的数量

按位或:有1出1,全0为0;
将x和s都转化为二进制:
1—>1有两种方法:1|1=1,1|0=1
0—>1有一种方法:0|1=1
1—>0:不可能!一旦出现此情况,直接输出0
注意:当x==s时,x|0=x,要减去一种情况(y=0)

#include
#define PI acos(-1);
#define mp make_pair 
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
typedef long long ll;
using namespace std;
const int maxn=1e6+10;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
int gcd(int a, int b) { return b ? gcd(b, a%b) : a; }
int lcm(int a, int b) { return a / gcd(a, b)*b; }
string solve(int x){
	string ans;
	while(x){
		ans+=x%2+'0';
		x>>=1;
	}
	reverse(ans.begin(),ans.end());
	return ans;
}
int main(){
   int x,y;
   cin>>x>>y;
   string s=solve(x);
   string t=solve(y);
   int l1=s.length();
   int l2=t.length();
   int k=abs(l1-l2);
   if(l1<l2){
   		for(int i=0;i<k;i++){
   			s="0"+s;
		}
   }
   else{
   		for(int i=0;i<k;i++){
   			t="0"+t;
		}
   }
   ll ans=1;
   for(int i=s.size()-1;i>=0;i--){
   		if(s[i]=='1'&&t[i]=='0'){
   			ans=0;
   			break;
		}
		else if(s[i]=='1'&&t[i]=='1') ans*=2;		
   }
   if(x==y) ans--;
   cout<<ans<<endl;
   
}

H-xay loves count

给定一个长度为n的数组,计算满足 a i × a j = a k a_i\times a_j=a_k ai×aj=ak的三元组(i,j,k)的数量。

减少未知数,暴力,注意开longlong
代码一:

#include
#define PI acos(-1);
#define mp make_pair 
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
typedef long long ll;
using namespace std;
const int maxn=1e6+10;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
int gcd(int a, int b) { return b ? gcd(b, a%b) : a; }
int lcm(int a, int b) { return a / gcd(a, b)*b; }
int a[maxn];
int vis[maxn];
int main(){
	IOS
	int n;
	cin>>n;
	int mmax=0;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		vis[a[i]]++;
		mmax=max(mmax,a[i]);
	}
	ll ans=0;
	for(int i=1;i<=mmax;i++){
		for(int j=1;1ll*i*j<=mmax&&j<=mmax;j++){
			if(vis[i]&&vis[j]&&vis[i*j]){
				ans+=1ll*vis[i]*vis[j]*vis[i*j];
			}
		}
	}
	cout<<ans<<endl;
}

代码二:

#include
#define PI acos(-1);
#define mp make_pair 
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
typedef long long ll;
using namespace std;
const int maxn=1e6+10;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
int gcd(int a, int b) { return b ? gcd(b, a%b) : a; }
int lcm(int a, int b) { return a / gcd(a, b)*b; }
int a[maxn];
int vis[maxn];
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		vis[a[i]]++;
	}
	ll ans=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=sqrt(a[i]);j++){
			if(a[i]%j==0&&vis[a[i]/j]&&vis[j]){
				ll num=vis[a[i]/j]*vis[j];
				if(a[i]/j!=j) num*=2;
				ans+=num;
			}
		}
	}
	cout<<ans<<endl;
}

2021牛客暑期多校训练营8

A-Ares, Toilet Ares

题目大意:一定能通过的题目数为a,有k次获得提示的机会,每次提示代码的长度为x,依靠提示代码通过题目的可能性为(1-y/z)。求最终能通过的题目数量。

注意:当x=0时,一定不能通过题目,所以不予考虑。
考察逆元、取模运算

#include
#define PI acos(-1);
#define mp make_pair 
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
typedef long long ll;
using namespace std;
const int maxn=1e6+10;
const int mod=4933;
const int INF=0x3f3f3f3f;
ll qsm(ll x,ll y){
	ll ans=1;
	while(y){
		if(y&1) ans=ans*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return ans;
}
ll inv(ll a,ll b){//逆元
	return (a*qsm(b,mod-2))%mod;
}
int main(){
	ll n,m,k,a,l;
	scanf("%lld%lld%lld%lld%lld",&n,&m,&k,&a,&l);
	ll ans=1;
	while(k--){
		ll x,y,z;
		scanf("%lld%lld%lld",&x,&y,&z);
		if(x>0){
			y=z-y;
			ans=(ans%mod*inv(y,z)%mod)%mod;
		}
	}
	ans=(ans%mod+a%mod)%mod;
	printf("%lld\n",ans);
} 

D-OR

题目大意:
给出两个长度为n-1的序列, b = ( b 2 , b 3 , . . . , b n ) , c = ( c 2 , c 3 , . . . , c n ) b=(b_2,b_3,...,b_n),c=(c_2,c_3,...,c_n) b=(b2,b3,...,bn),c=(c2,c3,...,cn)
序列 a = ( a 1 , a 2 , . . . , a n ) a=(a_1,a_2,...,a_n) a=(a1,a2,...,an)满足:
∀ ( 2 ≤ i ≤ n ) , b i = a i − 1 ∣ a i , c i = a i − 1 + a i \forall (2\leq i\leq n),b_i=a_{i-1}|a_i,c_i=a_{i-1}+a_i (2in),bi=ai1ai,ci=ai1+ai
求满足条件的序列a的个数

重要性质:a+b=a|b+a&b
a i − 1 + a i = a i − 1 ∣ a i + a i − 1 & a i a_{i-1}+a_i=a_{i-1}|a_i+a_{i-1}\&a_i ai1+ai=ai1ai+ai1&ai
=> c i = b i + a i − 1 & a i c_i=b_i+a_{i-1}\&a_i ci=bi+ai1&ai
=> a i − 1 & a i = c i − b i a_{i-1}\&a_i=c_i-b_i ai1&ai=cibi
d i = a i − 1 & a i = c i − b i d_i=a_{i-1}\&a_i=c_i-b_i di=ai1&ai=cibi
也就意味着, a i − 1 ∣ a i a_{i-1}|a_i ai1ai a i − 1 & a i a_{i-1}\&a_i ai1&ai的值均是已知的,因此可以通过枚举 a 1 a_1 a1的值来确定整个序列其他元素的值,进而计算满足条件的序列的数量。
或运算、与运算均为按位运算符,我们也枚举每一位可能出现的情况(是0,还是1,或者两者均可)
假设x为 b i b_i bi转化为二进制后的第j位(即 a i − 1 ∣ a i a_{i-1}|a_i ai1ai的第j位)
假设y为 d i d_i di转化为二进制后的第j位(即 a i − 1 & a i a_{i-1}\&a_i ai1&ai的第j位)
a i − 1 a_{i-1} ai1 a i a_i ai的第j位可能会出现以下四种情况:

1、x=0,y=0
只可能是:0|0=0,0&0=0
2、x=1,y=1
只可能是1|1=1,1&1=1
3、x=1,y=0
有两种可能:
0|1=1,0&1=0
1|0=1,1&0=0
4、x=0,y=1
不可能

#include
#define pb push_back
#define mpi make_pair
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#define pi acos(-1)
using namespace std;
const int maxn=1e6+10;
const int mod=1e9+7;
typedef long long ll;
inline ll lowbit(ll x){ return x&(-x);}
int a[maxn];
int b[maxn];
int main(){
	int n;
	cin>>n;
	for(int i=2;i<=n;i++) cin>>a[i];
	for(int i=2;i<=n;i++){
		int x;
		cin>>x;
		b[i]=x-a[i];
	}
	ll ans=1;
	for(int i=0;i<=31;i++){
		int pre0=1,pre1=1;
		for(int j=2;j<=n;j++){
			int now0=0,now1=0;
			int x=(a[j]>>i)&1;//取a[j]转化为二进制后的第i位(从最低位开始) 
			int y=(b[j]>>i)&1;
			if(!x&&!y) now0=pre0;//上一位选0,这一位也选0
			if(x&&y) now1=pre1;//上一位选1,这一位也选1
			if(x&&!y){
				now0=pre1;//上一位选1,这一位需要选0
				now1=pre0;//上一位选0,这一位需要选1
			}
			//if(!x&&y) now0=0,now1=1;
			pre0=now0;
			pre1=now1;
		}
		ans*=(pre0+pre1);
	}
	cout<<ans<<endl;
	return 0;
}

E-Rise of Shadows

判断某数是否既是质数又是闰年数

闰年:可被4整除,但不能被100整除,或能被400整除。不可能为质数,所以不存在这样的数。

#include
#define pb push_back
#define mpi make_pair
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e6+10;
const int mod=1e9+7;
typedef long long ll;
inline ll lowbit(ll x){ return x&(-x);}
int main(){
    int t;
    cin>>t;
    while(t--){
        int a;
        cin>>a;
        puts("no");//puts()函数用来向标准输出设备屏幕输出字符串并换行。
    }
	return 0;
}

F-Robot

题目大意:
给出一张n*m的地图,0代表空地,可以通过;1代表障碍物,不可通过。
有三种类型的机器人:
1、只能向下移动,从(x,y)->(x+1,y)
2、只能向右移动,从(x,y)->(x,y+1)
3、既可以向下移动,又可以向右移动
给出q个询问,问机器人能否从某起点到达某终点。
1 ≤ n , m ≤ 500 1\leq n,m \leq 500 1n,m500
1 ≤ q ≤ 5 × 1 0 5 1\leq q\leq 5\times 10^5 1q5×105

比赛的时候,在线处理,T了。(给了5s,肯定有猫腻。。。)
看题解才知道原来要离线处理,还要用bitset优化,以此来优化时间。
对于第1、2种机器人,通过预处理前缀和即可O(1)判断能否到达。
对于第三种机器人则需离线,优化。
维护终点,判断哪些起点可以到达该终点

#include
#define pb push_back
#define mpi make_pair
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#define pi acos(-1)
using namespace std;
const int maxn=1e6+10;
const int mod=1e9+7;
typedef long long ll;
inline ll lowbit(ll x){ return x&(-x);}
bitset<510>vis[510];//vis[i][j]代表某起点(x1,j)能否到达某点(x2,i)
char mp[510][510];
int sr[510][510];//行的前缀和
int sc[510][510];//列的前缀和
int ans[maxn];//记录答案
struct node{
	int id,y1,x2,y2;
};
vector<node>v[510],vec[510];//存储询问信息
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
    	for(int j=1;j<=m;j++){
    		scanf(" %c",&mp[i][j]);
    		sr[i][j]=sr[i][j-1]+(mp[i][j]=='1');
    		sc[i][j]=sc[i-1][j]+(mp[i][j]=='1'); 
		}    	
    }
    int q;
    scanf("%d",&q);
    for(int i=0;i<q;i++){
    	int t,x1,y1,x2,y2;
    	scanf("%d%d%d%d%d",&t,&x1,&y1,&x2,&y2);
		if(t==1){
		//只有起点和终点在同一列,且中间没有障碍物时,可到达。
			if(y1==y2&&x1<=x2&&sc[x2][y2]-sc[x1-1][y1]==0) ans[i]=1;
		}
		else if(t==2){
		//只有起点和终点在同一行,且中间没有障碍物时,可到达。
			if(x1==x2&&y1<=y2&&sr[x2][y2]-sr[x1][y1-1]==0) ans[i]=1;
		}
		else{
			if(x1<=x2&&y1<=y2){
			//先存储查询信息
				v[x1].push_back(node{i,y1,x2,y2});
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(auto it:v[i]){
			vec[it.x2].pb(it);
		}
		//把所有能到达点(x2,j)的点进行标记
		for(int j=1;j<=m;j++){//初始化
			vis[j].reset();
		}
		for(int j=1;j<=m;j++){//遍历列(向右走),看点(i,j)能否到达(x2,j)
			if(mp[i][j]=='1') vis[j].reset();//一旦遇到障碍,则说明无法到达
			else{
				vis[j][j]=1;//说明点(i,j)能够到达(x2,j)
				//拷贝操作
				vis[j]|=vis[j-1];//如果点(i,j)能够到达(x2,j),那点(i,j-1)也能够到达(再往右走一步)
			}
		}
		for(int j=i;j<=n;j++){//遍历行(向下走)
			for(int k=1;k<=m;k++){
			//走到 (j,k) 的方案,要合并上能走到 (j,k-1) 的方案 
				vis[k]|=vis[k-1];
				//若 (j,k) 存在障碍物,则无方案 
				if(mp[j][k]=='1') vis[k].reset();
			}
			for(auto it:vec[j]){
				ans[it.id]=vis[it.y2][it.y1];
			}
			vec[j].clear();
		}
	}
	for(int i=0;i<q;i++){
		if(ans[i]) puts("yes");
		else puts("no");
	}
	return 0;
}

K-Yet Another Problem About Pi

题目大意:
在网格中画一根长度为pi的线,最多经过的网格数量

#include
#define pb push_back
#define mpi make_pair
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#define pi acos(-1)
using namespace std;
const int maxn=1e6+10;
const int mod=1e9+7;
typedef long long ll;
inline ll lowbit(ll x){ return x&(-x);}
int main(){
	int t;
	cin>>t;
	while(t--){
		double n,m;
		cin>>n>>m;
		double a=min(m,n);//直线
		double b=sqrt(m*m+n*n);//对角线
		ll ans=0;
		//ax+by<=pi
		for(int i=0;i<=5;i++){//暴力枚举
			if(pi-a*i>=0){
				ans=max(ans,2ll*i+3ll*(ll)floor(((pi-a*i)/b)));				
			}
		} 
		for(int i=0;i<=5;i++){
			if(pi-b*i>=0){
				ans=max(ans,3ll*i+2ll*(ll)floor(((pi-b*i)/a)));				
			}
		} 
		//至少4个格子
		cout<<ans+4<<endl;
	}
	return 0;
}

2021牛客暑期多校训练营9

H-Happy Number

题目大意:将只由2、3、6三个数字构成的数字称为快乐数字。求出第n个快乐数字是什么。

我们知道,在十进制中,第n个数字就是n;二进制中,第n个数字就是n对应的二进制;三进制中,第n个数字就是n对应的三进制…
由于数字只由2、3、6三个数字构成,我们可以将其看成三进制数。但是与真正的三进制数又有所不同。
方法一:
一位的快乐数有3个,两位的有 3 2 3^2 32个…n位的有 3 n 3^n 3n
我们可以先看第n个快乐数是几位的(假设是x位),然后算出它是x位快乐数中的第几个(假设第y个),那么将y转化为三进制,若位数不足x,则在高位补0,即为最终答案。

#include
#define pb push_back
#define mpi make_pair
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e6+10;
const int mod=1e9+7;
typedef long long ll;
inline ll lowbit(ll x){ return x&(-x);}
vector<ll>v;
int a[]={2,3,6};
int main(){
	ll sum=0;
	v.pb(0);
	for(int i=1;i<=20;i++){//预处理,算出从1位快乐数开始到i位快乐数共有多少个
		sum+=pow(3,i);
		v.pb(sum);
	}
	int n;
	cin>>n;
	int len;
	for(int i=0;i<v.size();i++){
		if(v[i]>=n){
			len=i;//n是len位快乐数
			break;
		}
	}
	int k=n-v[len-1]-1;//n是len位快乐数的第几个
	string s="";
	while(k){//转三进制
		s+=to_string(k%3);
		k/=3;
	}
	reverse(s.begin(),s.end());//不要忘记翻转
	while(s.size()<len) s="0"+s;//位数不足,高位补0
	for(int i=0;i<s.length();i++){//输出结果
		cout<<a[s[i]-'0'];
	}
	cout<<endl;
	return 0;
}

方法二:
正常三进制:0,1,2,10,11,12,20,21,22,30,31,32,100,101,102…
快乐数:2,3,6,22,23,26,32,33,36,62,63,66,222,223,226…
若将快乐数看成三进制数,2视为0,3视为1,6视为2,可以发现,快乐数比正常三进制数要多,两位的快乐数比正常三进制数多3个,三位的快乐数比正常三进制数多9个……n位的快乐数比正常三进制数多 3 ( n − 1 ) 3^{(n-1)} 3(n1)个。
如果直接将n转化为三进制,则需进行相应修改:转为三进制数后,若第i位为0,则向高位借一位(相当于拿来补上比快乐数少了的 3 ( i − 1 ) 3^{(i-1)} 3(i1)个)

#include
#define pb push_back
#define mpi make_pair
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e6+10;
const int mod=1e9+7;
typedef long long ll;
inline ll lowbit(ll x){ return x&(-x);}
int a[]={0,2,3,6};
int s[maxn];
int main(){
	int n;
	cin>>n;
	int cnt=0;
	while(n){
		s[cnt++]=n%3;
		n/=3;
	}
	int tmp=0;
	for(int i=0;i<cnt;i++){
		if(tmp){ //若之前被借位
			if(s[i]==0) s[i]=2;//tmp不能归零,需保存借位到下一个不是0的位
			else{
				s[i]-=tmp;
				tmp=0;
			}
		}
		if(s[i]==0&&i!=cnt-1){//若0不是最高位 
			s[i]=3;//0向高位借位:加3 
			tmp=1;
		}	
	} 
	int k=cnt-1;
	while(s[k]==0) k--;//去除前缀0 
	for(int i=k;i>=0;i--){
		cout<<a[s[i]];
	}
	cout<<endl;
	return 0;
}

简易写法

#include
#define pb push_back
#define mpi make_pair
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e6+10;
const int mod=1e9+7;
typedef long long ll;
inline ll lowbit(ll x){ return x&(-x);}
int a[]={6,2,3};//注意此时转化顺序!!!!
int s[maxn];
int main(){
	int n;
	cin>>n;
	int cnt=0;
	while(n){
		s[cnt++]=n%3;
		int k=n%3;
		n/=3;
		if(k==0){//相当于只修改了被借位的数字的值,而借位的数字并没有修改
			n--;
		} 
	}
	for(int i=cnt-1;i>=0;i--){
		cout<<a[s[i]];
	}
	cout<<endl;
	return 0;
}

2021牛客暑期多校训练营10

H-War of Inazuma (Easy Version)

题目大意:
n维超立方体,有 2 n 2^n 2n个顶点,每个顶点的标号为[0,2^n-1]内的数字。若两个顶点的标号转化为二进制后有且仅有一位不同,则视为这两个顶点相邻。
一个顶点要么归属于R军,要么归属于S军,其实就相当于给每个顶点涂色(黑色1或白色0)。
现需满足条件:对于任一顶点,与它相邻且颜色相同的顶点数量不能超过 ⌈ n ⌉ \lceil \sqrt n \rceil n ,求给顶点涂色的方案。

本质上,n维超立方体就是一个二分图,二进制中1的个数为偶数的顶点涂上一种颜色,个数为奇数的顶点涂上另一种颜色即可。

#include
#include
#include
#include
#define PI acos(-1);
#define mpi make_pair 
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
typedef long long ll;
using namespace std;
const int maxn=1e6+10;
const int mod=19260817;
const ll INF=0x3f3f3f3f3f3f3f3f;
int cal(int x){//计算二进制中1的个数
	int cnt=0;
	while(x>0){  
     cnt++;  
     x&=(x-1);  
 	} 
 	return cnt;
}
int main(){
	int n;
	cin>>n;
	for(int i=0;i<1<<n;i++){
		int num=cal(i);
		if(num&1) cout<<"1";
		else cout<<"0";
	}
	cout<<endl;
	return 0;
} 

F-Train Wreck

题目大意:
给定一个出入栈序列,每个出入栈的节点都会被涂上颜色。求出一种涂色方案,使得任何一个时刻,节点入栈后,栈中序列都是唯一(不会出现跟自己一模一样的栈)

大体思路:先确定入栈顺序,再给节点涂色(优先使用颜色数多的那种颜色涂色)
在这里插入图片描述

2021牛客暑期多校训练营_第14张图片

#include
#define pb push_back
#define mpi make_pair
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=1e6+10;
const int mod=1e9+7;
typedef long long ll;
inline ll lowbit(ll x){ return x&(-x);}
vector<int>vec[maxn];//记录每一层的节点序号
int cnt[maxn];//统计颜色个数
priority_queue<pair<int,int> >q;//记录每种颜色的个数及其对应颜色 
int ans[maxn];//记录涂色方案 
int main(){
	int n;
	cin>>n;
	int lev=0;//层数 
	int idx=1;//节点序号 
	string s;
	cin>>s;
	for(int i=1;i<=n;i++){
		int color;
		cin>>color;
		cnt[color]++;
	}
	for(int i=1;i<=n;i++){
		if(cnt[i]) q.push(mpi(cnt[i],i));
	}
	for(int i=0;i<2*n;i++){
		if(s[i]=='('){//入栈 
			vec[lev++].pb(idx++);	
		}
		else{//出栈,出栈时要把该层所有节点涂上不一样的颜色 
			if(vec[lev].size()==0){//如果该层没有节点,直接返回上一层 
				lev--;
				continue;
			}
			if(q.size()<vec[lev].size()){//颜色种类数小于该层节点数:不足以给每个节点涂上不同的颜色 
				puts("NO\n");
				return 0; 
			} 
			//为该层每个节点涂色 
			vector<pair<int,int> >tmp;//暂存剩余颜色的数量以及相应颜色 
			pair<int,int>u;
			for(int i=0;i<vec[lev].size();i++){
				u=q.top();
				q.pop();
				ans[vec[lev][i]]=u.se;//给节点涂色 
				u.fi--;//该颜色数量减一
				if(u.fi){//如果颜色还有剩余,那就先暂时保存在tmp中,等该层节点全部涂色完成后,再更新优先队列 
					tmp.pb(u);
				}
			}
			if(tmp.size()){//颜色有剩余,则更新优先队列 
				for(auto i:tmp){
					q.push(i);
				}
			}
			vec[lev].clear();//该层涂色完成后,将所有节点清除,然后返回上一层 
			lev--;
		}
	} 
	//处理第0层(题目所给括号序列是可以完全匹配的,可以发现上面涂色时,lev不可能等于0,所以最后要单独给第0层涂色)
	//如果剩余颜色种类不足,直接输NO
	if(q.size()<vec[0].size()){
		puts("NO\n");
		return 0;
	} 
	pair<int,int>u;
	for(int i=0;i<vec[0].size();i++){
			u=q.top();
			q.pop();
			ans[vec[0][i]]=u.se;
			u.fi--;
	}
	puts("YES");
	for(int i=1;i<=n;i++){
		printf("%d",ans[i]);
		if(i==n) printf("\n");
		else printf(" ");
	}
	return 0;
}

你可能感兴趣的:(acm竞赛题解,c++)