2022寒假集训4 I题(dp)、A题(模拟)、J题(数论)、D题(计算几何)、G题(数论/找规律)

第4场题解:【题解】2022年第四场寒假集训营题解 

I题 (dp)[01背包变形]

I-爆炸的符卡洋洋洒洒

题解思路:

dp[i][j]中i是第几件,j是包中魔法消耗,a是当前件魔法消耗(体积),b是魔法威力(价值);

已知魔法消耗只有在k的倍数的时候才可以发动,所以j的范围时【0,k-1  】;

所以当背包中的魔法消耗为[j-a%k+k]%k时,加上当前物品后体积变成现在的j;

要注意如果 背包里是空的 或者背包里有 魔法消耗和魔法威力 才可以放物品,目的是排除掉背包里 有魔法消耗但无魔法威力 的情况

(虽然上面的话像等于没说一样,ummm不太好表述) 

为了排除掉背包里 有魔法消耗但无魔法威力 的不合法情况,有两种方法:

法1:先把所有dp值(魔法威力)都变成-1e16以确保取不到,除了dp[0][0]为0,这样所有可能的方法路径就都是从 背包为空 的合法状态开始了;(如题解所示)

法2:dp(魔法威力)初始为0,加上条件判断:如果j-a%k+k为0(代表背包为空)或者 dp[i-1][(j-a+k)%k](代表背包有魔法价值)才可以进行状态转移;(如第二份代码)

注意⚠:记得f[i][j]=f[i][j-1]!!!

题解代码: 

#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll a[1010][2],dp[1010][1010];
int main(){
    int n,k,i,j;
    cin>>n>>k;
    for(i=1;i<=n;i++)cin>>a[i][0]>>a[i][1];
    for(i=0;i<=n;i++)for(j=0;j<=k;j++)dp[i][j]=-1e16;//初始化尽可能小!代表取不到。
    dp[0][0]=0;//前0件物品,显然取到的最大威力为0。此时消耗魔力模p等于0
    for(i=1;i<=n;i++){
        for(j=0;j<k;j++){
            dp[i][j]=dp[i-1][j];//这一部分是不取第i个符卡。
            dp[i][j]=max(dp[i][j],dp[i-1][(j-a[i][0]%k+k)%k]+a[i][1]);//状态转移方程
        }
    }
    if(dp[n][0]<=0)cout<<-1;
    else cout<<dp[n][0];
}

代码:

#include<bits/stdc++.h>
using namespace std;
long long f[1100][1005],n,k;
int main()
{
    cin>>n>>k;
    for(int i=1;i<=n;i++){
    long long a,b;
        cin>>a>>b;
        a=a%k;
        for(int j=0;j<k;j++){
            f[i][j]=f[i-1][j];
            if(f[i-1][(j-a+k)%k]||(j-a+k)%k==0) 
                f[i][j]=max(f[i-1][j],f[i-1][(j-a+k)%k]+b);
        }
    }
    if(f[n][0]==0)cout<<-1;
    else cout<<f[n][0];
}

 A题(模拟)

找规律找到最后发现temp满足k的情况下,ans+=l(左端点)

遇到P的时候截断string;

【srds我比赛的时候知道思路但是代码没写出来orz】

#include<bits/stdc++.h>
using namespace std;
long long sum=0;string s;int n,k;
void g(string t){
    int temp=0;int j=0;//j相当于左端点
    for(int i=0;i<t.size();i++){//i相当于右端点
        if(t[i]=='R')temp++;//temp代表这一段有多少个R
        while(j<=i&&temp==k){if(t[j++]=='R')temp--;}
        sum+=j;
    }
}
int main(){
    cin>>n>>k;cin>>s;
    string t;
    for(int i=0;i<n;i++){
        if(s[i]=='P'){g(t);t="";}
        else t=t+s[i];
    }
    g(t);
    cout<<sum;
    return 0;
}

同学的代码:

#include<bits/stdc++.h>
using namespace std;
int main(){
    char s[200005];
    long long n,k,p=0,q=0,ans=0,cnt=0,j=0;
    vector<long long> pos,posp;
    cin>>n>>k;
    posp.push_back(0);pos.push_back(0);
    scanf("%s",s+1);
    for(int i=1;i<=n;i++){
        if(s[i]=='R'){pos.push_back(i);}
        else if(s[i]=='P'){pos.push_back(i);posp.push_back(i);}
    }
    posp.push_back(n+1);pos.push_back(n+1);
    for(int i=0;i<pos.size();i++)
        if(pos[i]==posp[j]){p=i+1;q=i+1;j++;}
        else
            if(q-p+1<k){q++;}
            else{
                ans+=(pos[p]-pos[p-1] )*(posp[j]-pos[q]);
                p++;q++;
            }
    cout<<ans;
}

J题 (数论)

素数分解,枚举素数;【虽然比赛的时候也是这么想的,但是不知道怎么确定在l和r区间之间】

从题解可以看出

1. 枚举出所有 2 ~ r/2 之间的质数【如果这个质数大于r/2,那么它不可能是[l,r]之间的数的因数】

2.判断每个质数是几次方

         Q:如何判断j是[l,r]之间的数的因数?

          A:r-(r%j)是小于r的 j的倍数 ,如果如果 r-(r%j)>=l 成立,那么x是[l,r]之间的数的因数;

于是就通过上面的方法不断判断j的次方是不是[l,r]区间的数的因数,如果不是,代表超出,break;

3.把  每一次质数的次方(在区间内的) 都乘以ans;

4.判断输出;

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1000000007;
ll ans=1,l,r;
int main(){
	cin>>l>>r;
	for(int i=2;2*i<=r;i++){
        bool f=0;
        for(int j=2;j*j<=i;j++){if(i%j==0){f=1;break;}}
        if(!f){
            int j=i;
            while(r-(r%j)>=l)j*=i;
            ans=ans*(j/i)%mod;
        }
    }
	if(ans==1)cout<<"-1";
	else cout<<ans;
	return 0;
}

D题(计算几何)

比赛的时候套模板做的,把边看成向量;

设P是不动点,A至B是移动路径,向量v1是\overrightarrow{AB},向量v2是\overrightarrow{AP},向量v3是\overrightarrow{PB}

求距离一共有三个情况:

1.如图所示,向量v1和v2的夹角是钝角,内积(x1x2+y1y2)小于0,距离是PA,即向量v2的模;

2.如图所示,向量v1和v3的夹角是锐角,内积(x1x2+y1y2)大于0,距离是PB,即向量v3的模;

3.如图所示,此时点P到AB线段的距离 是 以向量v1和v2构成的四边形面积 除以 向量v1的模

向量v1和v2构成的四边形面积 是 向量v1和v2的叉积,即x1y2-x2y1;

 

代码: 

#include<bits/stdc++.h>
using namespace std;
const double eps=1e-10;                                                              
struct Point{double x,y;Point(double x=0,double y=0):x(x),y(y){}};
typedef Point Vector;
Vector operator + (Vector A,Vector B){return Vector(A.x+B.x,A.y+B.y);}
Vector operator - (Vector A,Vector B){return Vector(A.x-B.x,A.y-B.y);}
int dcmp(double x){if(fabs(x)<eps)return 0;return (x>0)?1:-1;}
bool operator == (const Vector A,const Vector B){
    return dcmp(A.x-B.x)==0 && dcmp(A.y-B.y)==0;
}
double Dot(Vector A,Vector B){return A.x*B.x+A.y*B.y;}
double Length(Vector A){return sqrt(Dot(A,A));}        
double Cross(Vector A,Vector B){return A.x*B.y-B.x*A.y;}
double Distance(Point P,Point A,Point B){
    if(A==B)return Length(P-A);
    Vector v1=B-A,v2=P-A,v3=P-B;
    if(dcmp(Dot(v1,v2))<0){return Length(v2);}
    else if(dcmp(Dot(v1,v3))>0){return Length(v3);}
    else {return fabs(Cross(v1,v2))/Length(v1);}           
}
int main()
{
    int n;cin>>n;long long x0,y0,x,y;double dis=0;
    cin>>x0>>y0>>x>>y;
    Point a(x0,y0);Point b(x,y);dis=Distance(b,a,a);
    for(int i=1;i<=n;i++){
        long long xx,yy;cin>>xx>>yy;
        Point c(xx,yy);c=c+a;
        dis=min(dis,Distance(b,a,c));
        a=c;
    }
    cout<<setprecision(10)<<dis;
    return 0;
}

题解是从多边形的角度做的,通过勾股定理来判断钝角(情况1和情况2);

通过三角形海伦公式(如下)来求情况3的距离:  2*S_{ABC}/AB  ;

S=\sqrt{p*(p-a)*(p-b)*(p-c)},p=(a+b+c)/2

题解代码:

#include<bits/stdc++.h>
using namespace std;
struct point{double x,y;point(double x,double y):x(x),y(y){}};
double dis(point a,point b){
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
double ar(point A,point B,point C){ //三点三角形面积
    double a=dis(B,C),b=dis(A,C),c=dis(A,B);double p=(a+b+c)/2;
    return sqrt(p*(p-a)*(p-b)*(p-c));
}
int jud(point A,point B,point C){  //判断角ABC是钝角
    double a=dis(B,C),b=dis(A,C),c=dis(A,B);
    return b*b>a*a+c*c;
}
double f(point a,point b,point c){  //c点到ab线段的最小距离
    double d1=dis(a,c),d2=dis(b,c);
    if(jud(c,a,b)||jud(c,b,a))return min(d1,d2);
    double s=ar(a,b,c);double d3=2*s/dis(a,b);return d3;
}
int main(){
    int n,i,j,k;double x,y,x0,y0;
    cin>>n;cin>>x0>>y0>>x>>y;
    point purple(x,y);point red(x0,y0);double res=1e16;
    for(i=0;i<n;i++){
        double xt,yt;cin>>xt>>yt;
        point temp(red.x+xt,red.y+yt);
        res=min(res,f(red,temp,purple));
        red=temp;
    }
    printf("%.8f",res);
}

同时,在提交记录区的最短代码中找到一个写起来更简单的方法;

通过勾股定理分情况1、2、3,求距离时是通过余弦定理: \frac{c^2+a^2-b^2}{2c}=acosB     加上\sqrt{a^2+(acosB)^2}  求的;

代码:

#include<bits/stdc++.h> 
using namespace std;
int main(){
	int n;double x0,y0,x,y,x1,y1,a,b,c,d,s,t;
	scanf("%d%lf%lf%lf%lf",&n,&x0,&y0,&x,&y);
	s=sqrt(pow(x-x0,2)+pow(y-y0,2));
	while(n--){
		scanf("%lf%lf",&x1,&y1);x1+=x0;y1+=y0;
		a=pow(x-x0,2)+pow(y-y0,2);
		b=pow(x-x1,2)+pow(y-y1,2);
		c=pow(x1-x0,2)+pow(y1-y0,2);
		if(a+c>b&&c+b>a){
			t=(a-b+c)/sqrt(c)/2;
            d=sqrt(a-t*t);
		}
		else d=min(sqrt(a),sqrt(b));
		if(d<s)s=d;
		x0=x1;y0=y1;
	}
	printf("%.8f",s);
	return 0;
}

G题 (找规律/数论)

G-子序列权值乘积

看完题目后可以先枚举一下答案是怎么出来的;

假设序列a是:2 3 4 5,枚举子序列,括号里的是序列中的最小值和最大值:
    2(22) 23(23) 24(24) 25(25) 234(24) 245(25) 235(25) 2345(25)
    3(33) 34(34) 35(35) 345(35)
    4(44) 45(45) 
    5(55)

从上面的枚举,统计以下每个数作为最小值和最大值出现的次数,如下所示,括号中左边是作为最小值出现的次数,右边是作为最大值出现的次数:

2:(8,1)
3:(4,2)
4:(2,4)
5:(1,8)

好像找到规律了,那么打乱顺序的话规律会改变吗?

假设序列a是:4 2 9 5,枚举子序列,括号里的是序列中的最小值和最大值:
    4(44) 42(24) 49(49) 45(45) 429(29) 425(25) 495(49) 4295(29)
    2(22) 29(29) 25(25) 295(29)
    9(99) 95(59)
    5(55)

从上面的枚举,统计以下每个数作为最小值和最大值出现的次数,如下所示,括号中左边是作为最小值出现的次数,右边是作为最大值出现的次数:
2:(8,1)
4:(4,2)
5:(2,4)
9:(1,8)

找到规律了,之后来“数学论证”一下:

以第2个例子为例:

共有4个数字,n=4;

2是最小的数,包含这个数字的序列有2^(n-1)个,所以2作为最小的数出现次数是2^(n-1)次;

4是第二小的数,在没有2的a序列中是最小的,有4无2 的序列有2^(n-2)个;

以此类推……

代码:【不用快速幂的版本】

#include<bits/stdc++.h>
using namespace std;
long long a[200005],mod=1000000007;
int main(){
    int n;
	cin>>n;
	for(int i=0;i<n;i++) {cin>>a[i];}
	sort(a,a+n);
    long long p=1,q=1;
    for(int i=0;i<n;i++){ 
        p=p*p%mod;q=q*q%mod;
        p=p*a[i]%mod;q=q*a[n-i-1]%mod;
    }
	cout<<p*q%mod;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值