2021牛客暑期多校训练营2

本文介绍了几道数学和算法问题的解题思路,包括数组的最长上升子序列构造、三维空间中球体相交体积计算、镜像行走路径问题以及区间划分优化。解题方法涉及动态规划、预处理、单调队列优化等技术。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

K
题目大意:给出N和M,有M个输入,每次输入两个数p,x,表示有一个数组b,从a[1]开始到a[p],这p个数的最长上升子序列为x,即b[p] = x。输出符合b数组的a数组,不存在就输出01。
解题思路:显然对于每一个 b[i],必须满足 b[i]≤i,那么对于未给出数值的 b[i],可以考虑先给它们填充上数值,不妨令 b[i] = b[i - 1] + 1,这样构造能够满足b[i]≤i的条件,随后只需要从后往前构造即可。填充完b数组,我们借助栈来从后往前构造a数组即可。

#include <bits/stdc++.h>

using namespace std;

const int N = 1e6 + 10;

int b[N], a[N];

int main()
{
    int n, m;
    cin >> n >> m;
    while(m--)
    {
        int d,dd;
        cin >> d >> dd;
        b[d] = dd;
    }
    for(int i = 1; i <= n; i++)
    {
        if(!b[i])
            b[i] = b[i-1]+1;
        if(b[i] > b[i-1]+1)
        {
            puts("-1");
            return 0;
        }
    }

    int cnt = 0;
    stack<int> s;
    for(int i = n; i > 0; i--)
    {
        while(b[i] > (int)s.size()) 
            {s.push(++cnt);}
        a[i] = s.top();
        s.pop();
    }
    for(int i = 1; i <= n; i++)
        cout << a[i] << ' ';
    cout << "\n";
    return 0;
}

F
题目大意:给出四个点A(x0,y0,z0),B(x1,y1,z1),C(x2,y2,z2),D(x3,y3,z3)和k1,k2,要求算出满足下列式子的两图形的相交的体积
在这里插入图片描述
以A B P1为例,设P1(x,y,z),依据题目限制条件先写出关系式:
在这里插入图片描述
化简后
在这里插入图片描述
容易得出这是一个球的方程,然后只需算出两球的相交体积即可。

#include <bits/stdc++.h>
#define INF 99999999
#define LINF LLONG_MAX 
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int MAX_N=1;
const double pi = acos(-1);

int T;
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);
        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(){
	cin>>T;
	while(T--){
		for(int i=0;i<4;i++)
			cin>>x[i]>>y[i]>>z[i];
		cin>>k1>>k2;
		//计算球1的球心坐标和半径 
		double xishu1=k1*k1-1;
		double c1x,c1y,c1z,c1r,t;
		c1x=(k1*k1*x[1]-x[0])/xishu1;
		c1y=(k1*k1*y[1]-y[0])/xishu1;
		c1z=(k1*k1*z[1]-z[0])/xishu1;
		t=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];
		t/=xishu1;
		c1r=sqrt(c1x*c1x+c1y*c1y+c1z*c1z-t);
		//计算球2的球心坐标和半径 
		double xishu2=k2*k2-1;
		double c2x,c2y,c2z,c2r;
		c2x=(k2*k2*x[3]-x[2])/xishu2;
		c2y=(k2*k2*y[3]-y[2])/xishu2;
		c2z=(k2*k2*z[3]-z[2])/xishu2;
		t=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];
		t/=xishu2;
		c2r=sqrt(c2x*c2x+c2y*c2y+c2z*c2z-t);
		//计算两个球相交部分体积 
		solve(c1x,c1y,c1z,c1r,c2x,c2y,c2z,c2r);
	}
	return 0;
}

I
题目大意:给2个20*20的地图,有两个人同时出发,并且以镜像的方式走路,要求从【20,20】 【20,1】 同时走到【1,20】【1,1】。
解题思路:bfs

#include <bits/stdc++.h>

using namespace std;

string ans;
int flag;
char c1[30][30], c2[30][30];
int vis[25][25][25][25];
int now[25][25][25][25][5];
string p = "DLRU";
int tx1[] = {1,0,0,-1}, ty1[] = {0,-1,1,0}, tx2[] = {1,0,0,-1}, ty2[] = {0,1,-1,0};
struct no
{
    int x1,y1,x2,y2;
};

queue<no> q;

int check(int a, int b)
{
    if((a+b)<1 || (a+b)>20)
        return a;
    return a+b;
}

void put()
{
    cout << vis[1][20][1][1]-1 << endl;
    reverse(ans.begin(), ans.end());
    cout << ans << endl;
    c1[20][20]='A';
    c2[20][1]='A';
    for(int i = 1; i <= 20; i++)
    {
        printf("%s ",c1[i]+1);
        printf("%s\n",c2[i]+1);
    }
}

int main()
{
    for(int i = 1; i <= 20; i++)
    {
        scanf("%s",c1[i]+1);
        scanf("%s",c2[i]+1);
    }
    vis[20][20][20][1]=1;
    q.push({20,20,20,1});
    while(!q.empty())
    {
        auto t = q.front(); 
        q.pop();
        for(int i = 0; i < 4; i++)
        {
            int xx1 = check(t.x1 , tx1[i]);
            int yy1 = check(t.y1 , ty1[i]);
            int xx2 = check(t.x2 , tx2[i]);
            int yy2 = check(t.y2 , ty2[i]);
            if(c1[xx1][yy1] == '#')
            {
                xx1 -= tx1[i];
                yy1 -= ty1[i];
            }
            if(c2[xx2][yy2] == '#')
            {
                xx2 -= tx2[i];
                yy2 -= ty2[i];
            }
            if(vis[xx1][yy1][xx2][yy2])
                continue;
            q.push({xx1,yy1,xx2,yy2});
            vis[xx1][yy1][xx2][yy2] = vis[t.x1][t.y1][t.x2][t.y2]+1;
            now[xx1][yy1][xx2][yy2][0] = i;
            now[xx1][yy1][xx2][yy2][1] = t.x1;
            now[xx1][yy1][xx2][yy2][2] = t.y1;
            now[xx1][yy1][xx2][yy2][3] = t.x2;
            now[xx1][yy1][xx2][yy2][4] = t.y2;
            if(xx1 == 1 && yy1 == 20 && xx2 == 1 && yy2 == 1)
            {
                int a = xx1, b = yy1, c = xx2, d = yy2;
                while(a != 20 || b!= 20 || c != 20 || d != 1)
                {
                    c1[a][b]=c2[c][d]='A';
                    ans += 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;
                }
                put();
                flag = 1;
                break;
            }
        }
    }
    if(!flag)
        puts("-1");
    return 0;
}

G
对于处理若干区间的题,通常需要进行预处理,去掉完全包含或被包含的区间。
如果存在区间A,能够完全包含区间B,那么A的分组情况有两种:要么自己单独成为一组,贡献为该区间的长度,要么和它的任一子区间成为一组,贡献为0。
否则,假设区间A和区间C一组,且A与C没有包含关系,那么将A与B分到一组会更优。
接下来我们将区间分成两类Si和Ti,Si是能够完全包含某一区间的区间,那么Ti中的区间就没有包含关系了。
我们对Si按长度降序排序,Ti按右端点坐标升序排序,可以证明,最优的划分方案,一定是将Ti分成若干组,且组内下标连续,再把Si中最长的若干个区间单独成组。
根据这个思路不难想到动态规划,设F[i][j]表示,将T中前i个区间分成j组,能够达到的最大贡献,若不合法则设为−inf。
状态转移方程:
F[i][j]=max(F[k−1][j−1]+rk−li+1)

需要保证k≤i且rk≥li。
这里表示将Tk~Ti分成一组的情况,枚举i,j,k,时间复杂度为O(n3)。
改写一下转移方程,进行分离变量:
F[i][j]+li−1=max(F[k−1][j−1]+rk)

方程右边是一个只和k有关的值,可以进行单调队列优化Dp。
即对于每一个j,维护一个单调队列Q[j],队列中每个元素储存F[k−1][j−1]和rk,保证rk单增,F[k−1][j−1]+rk单减。
每次转移前弹队首,将Qj−1中rk<li的扔掉,再直接利用第一个元素转移,并将转移后的信息放入Qj队尾。
这样我们得到了F[i][j]的值,然后枚举一下Si中有几个单独成组,和F[i][j]合并计算答案即可。
时间复杂度可以优化至O(n2)

J
解题思路:枚举每个质因数的贡献,需要用到欧拉降幂。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int inf = 0x3f3f3f3f;
const int maxn = 1e7 + 10;
const int mod = 1e9;

bool is_prime[maxn];
ll prime[maxn], phi[maxn];
int n, k;
ll p;
ll a[40000 + 5];
ll c[40000 + 5][30 + 5];
ll num[80000 + 5];

void get_phi()
{
	int i, j, k;
	k = 0;
	for (i = 2; i < maxn; i++)
	{
		if (is_prime[i] == false)
		{
			prime[k++] = i;
			phi[i] = i - 1;
		}
		for (j = 0; j < k && i * prime[j] < maxn; j++)
		{
			is_prime[i * prime[j]] = true;
			if (i % prime[j] == 0)
			{
				phi[i * prime[j]] = phi[i] * prime[j];
				break;
			}
			else
			{
				phi[i * prime[j]] = phi[i] * (prime[j] - 1);
			}
		}
	}
}

ll ksm(ll aa, ll b, ll mod)
{ // 快速幂
	__int128 res = 1, a = aa;
	while (b)
	{
		if (b & 1)
			res = (res * a) % mod;
		a = (a * a) % mod;
		b >>= 1;
	}
	return res % mod;
}

void solve()
{
	ll maxx = 0;
	cin >> n >> k >> p;
	for (int i = 1; i <= 8e4; i++)
		num[i] = 0;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		num[a[i]]++;
		maxx = max(maxx, a[i]);
	}
	c[0][0] = 1;
	for (int i = 1; i <= n; i++)
	{
		c[i][0] = 1;
		for (int j = 1; j <= min(i, k); j++)
		{
			c[i][j] = c[i - 1][j - 1] + c[i - 1][j];
			if (c[i][j] >= phi[p])
			{
				c[i][j] = c[i][j] % phi[p] + phi[p];
			}
		}
	}
	ll ans = 1;
	for(ll i = 0; prime[i] <= maxx; i++)
	{
		for(ll j = prime[i]; j <= maxx; j *= prime[i])
		{
			ll t = 0;
			for(ll z = j; z <= maxx; z += j)
				t += num[z];
			if(t < k)
				break;
			ans = ((__int128)ans*ksm(prime[i], c[t][k], p)%p)%p;
		}
	}
	cout << ans << endl;
}

int main()
{
	ios::sync_with_stdio(false);
	get_phi();
	int T;
	for (cin >> T; T--;)
	{
		solve();
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值