第4场题解:【题解】2022年第四场寒假集训营题解
I题 (dp)[01背包变形]
题解思路:
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是,向量v2是,向量v3是;
求距离一共有三个情况:
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的距离: ;
题解代码:
#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,求距离时是通过余弦定理: 加上 求的;
代码:
#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题 (找规律/数论)
看完题目后可以先枚举一下答案是怎么出来的;
假设序列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;
}