2020.11.05 CSP-S2020模拟赛 解题报告
T1:射线(ray)
原题
应该可以算作这道题的原题:【BZOJ2187】fraction。
代码:\(O(Tlogn)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define swap(x,y) (x^=y^=x^=y)
#define int long long
using namespace std;
int a,b,c,d;
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define pc(c) (C==E&&(clear(),0),*C++=c)
#define D isdigit(c=tc())
int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
public:
I FastIO() {A=B=FI,E=(C=FO)+FS;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
Tp I void write(Ty x,char y) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);pc(y);}
I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
I void Get(CI a,CI b,CI c,CI d,int& x,int& y)//类欧几里得算法
{
if(a/b+1<=(c-1)/d) return (void)(x=a/b+1,y=1);if(!a) return (void)(x=1,y=d/c+1);
a<b?(Get(d,c,b,a,x,y),swap(x,y)):(Get(a%b,b,c-(a/b)*d,d,x,y),x+=y*(a/b));
}
signed main()
{
freopen("ray.in","r",stdin),freopen("ray.out","w",stdout);
RI Tt,x,y;F.read(Tt);W(Tt--)
{
F.read(a),F.read(b),F.read(c),F.read(d),1LL*a*d>1LL*b*c&&(swap(a,c),swap(b,d));//强制a/b<c/d
if(!a) {F.write(1,' '),F.write(d/c+1,'\n');continue;}//特判第一条直线与y轴重合
if(!d) {F.write(a/b+1,' '),F.write(1,'\n');continue;}//特判第二条直线与x轴重合
Get(a,b,c,d,x,y),F.write(x,' '),F.write(y,'\n');
}return F.clear(),0;
}
T2:图(graph)
找规律
模拟赛的时候真就硬找规律把这道题给想出来了。。。(但显然因为一些细节,我是赛后才改出来的。。。)
考虑站在一个点\(x\)上不动的时候,我们从\(x\)走到\(y\)再走回\(x\),相当于同时改变了\(x\)和\(y\)的选择状态,且并不影响其他点。
那么只要改变\(x,y\)的选择状态,再改变\(x,z\)的选择状态,就变成可以改变任意两点的选择状态。
也就是说,在不动的时候,我们可以在不改变选择点数奇偶性的前提下任选。
而一旦走了一步,相当于选择点数奇偶性就改变了。
发现这题刚好有一个很优秀的性质——二分图,也就是说我们很容易判断最终选择点数的奇偶性。
那么只要预先求出选若干奇数能得到的最大异或和以及若干偶数能得到的最大异或和就可以了。
线性基
众所周知线性基能求选若干个数的异或和,却并不能直接解决我们的问题。
我们给每个元素异或上一个极大值\(2^{30}\)。
发现为了取到这个极大值,我们肯定会取奇数个元素。
而如果要得到偶数,直接以\(2^{30}\)为初始答案做即可。
代码:\(O(nlogn)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 500000
#define L 30
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,m,a[N+5],c[N+5],ans[2],ee,lnk[N+5];struct edge{int to,nxt;}e[2*N+5];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define pc(c) (C==E&&(clear(),0),*C++=c)
#define D isdigit(c=tc())
int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
public:
I FastIO() {A=B=FI,E=(C=FO)+FS;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
Tp I void writeln(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);pc('\n');}
I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
struct LinearBasis//线性基
{
int v[L+5];I void A(RI x) {for(RI i=L;~i;--i) if(x>>i&1) {if(!v[i]) {v[i]=x;break;}x^=v[i];}}
I int Q(RI x) {for(RI i=L;~i;--i) x<(x^v[i])&&(x^=v[i]);return x;}
}B;
I void Col(CI x)//二分图黑白染色,以判断两点是否在同一侧
{
for(RI i=lnk[x];i;i=e[i].nxt) !~c[e[i].to]&&(c[e[i].to]=c[x]^1,Col(e[i].to),0);
}
int main()
{
freopen("graph.in","r",stdin),freopen("graph.out","w",stdout);
RI Qt,i,x,y;for(F.read(n),F.read(m),F.read(Qt),i=1;i<=n;++i) F.read(a[i]),c[i]=-1;
for(i=1;i<=m;++i) F.read(x),F.read(y),add(x,y),add(y,x);c[1]=0,Col(1);
for(i=1;i<=n;++i) B.A(a[i]^(1<<L));ans[0]=B.Q(1<<L)^(1<<L),ans[1]=B.Q(0)^(1<<L);//分别求出选偶数个数的答案和奇数个数的答案
W(Qt--) F.read(x),F.read(y),F.writeln(ans[c[x]^c[y]^1]);return F.clear(),0;//根据选择点数的奇偶性输出答案
}
T3:找钱(deal)
简单转化
根据它给出的要求,\(Y-X\)(售货员找的钱)必须小于小L使用的任意一张钱。
显然它的一个必要不充分条件,就是售货员找的任意一张钱必须小于小L使用的任意一张钱。
所以,售货员用的钱必然是前一段,小L用的钱必然是后一段,我们完全可以分开来\(DP\)。
动态规划
我们设\(f_{i,j}\)表示售货员使用第\(i\)种及之前的钱得到总额为\(j\)元的方案数,相似地设\(g_{i,j}\)表示小L使用第\(i\)种及之后的钱得到的总额为\(j\)元的方案数。
然后发现这样做方案肯定会算重,实际上我们需要修改一下\(f\)的定义:用\(F\)表示原先的\(f\),同时新的\(f\)加上一个必须要使用第\(i\)种钱的限制。(注意没必要也不应同时修改\(g\)的定义,因为两人选的钱之间可以有空缺)
具体转移时发现这是一个多重背包,好像会\(\texttt{TLE}\)。
但众所周知多重背包有一些奇奇怪怪的优化,比如这题我们可以按照\(j\)模\(a_i\)的余数分类,显然一个\(j\)只能从模\(a_i\)同余的位置转移得到,而且恰好只能从前\(b_i/c_i\)个位置转移。
最后统计答案时也要稍微注意一下,具体实现可以看代码。
代码:\(O(nm)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1000
#define V 20000
#define X 1000000007
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,m,a[N+5],b[N+5],c[N+5],F[N+5][V+5],f[N+5][V+5],g[N+5][V+5],q[V+5],s[V+5];
int main()
{
freopen("deal.in","r",stdin),freopen("deal.out","w",stdout);
RI i,j,k;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%d%d%d",a+i,b+i,c+i);
for(F[0][0]=f[0][0]=i=1;i<=n;++i)//DP求解f
{
if(!c[i]) {for(j=0;j<=V;++j) F[i][j]=F[i-1][j];continue;}//没有这种钱不转移
for(j=0;j^a[i];++j) q[j]=j,s[j]=F[i][j]=F[i-1][j];//总额较少不可能转移
for(k=0,j=a[i];j<=V;++j,++k==a[i]&&(k=0))//k记录j模a[i]的余数
j-q[k]>1LL*a[i]*c[i]&&(Inc(s[k],X-F[i-1][q[k]]),q[k]+=a[i]),f[i][j]=s[k],//删去超出限制的贡献
Inc(s[k],F[i-1][j]),F[i][j]=s[k];
}
for(g[n+1][0]=1,i=n;i>=1;--i)//DP求解g,同上F的转移
{
if(!b[i]) {for(j=0;j<=V;++j) g[i][j]=g[i+1][j];continue;}
for(j=0;j^a[i];++j) q[j]=j,s[j]=g[i][j]=g[i+1][j];
for(k=0,j=a[i];j<=V;++j,++k==a[i]&&(k=0)) Inc(s[k],g[i+1][j]),
j-q[k]>1LL*a[i]*b[i]&&(Inc(s[k],X-g[i+1][q[k]]),q[k]+=a[i]),g[i][j]=s[k];
}
RI t=0;for(i=0;i<=n;++i) for(k=i+1,j=0;j<=10000;++j)//枚举售货员用的最后一种货币
{W(k<=n&&a[k]<=j) ++k;if(k>n) break;t=(1LL*f[i][j]*g[k][j+m]+t)%X;}//实现不够优秀,只好写一个双指针维护统计答案
return printf("%d\n",t),0;
}
T4:几何(geo)
题意转化
考虑条件为:
移个项就变成了:
发现对于一个\(i\),\(x_i,y_i-x_i^2\)都是定值。
所以题目就变成了问有多少直线,经过至少两点,且它的上方没有点。
计算几何
怎么说呢,这道题连身为计算几何渣渣的我都会做。。。
\(y=0\)的部分分已经给了我们提示,其实就是求出上凸壳,然后只能选相邻两点连直线。
即,答案就是上凸壳的点数减\(1\)。
代码:\(O(nlogn)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200000
#define LL long long
using namespace std;
int n;struct P {LL x,y;I bool operator < (Con P& o) Con {return x^o.x?x<o.x:y>o.y;}}p[N+5],S[N+5];
int main()
{
freopen("geo.in","r",stdin),freopen("geo.out","w",stdout);
RI i,x,y;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d%d",&x,&y),p[i].x=x,p[i].y=y-1LL*x*x;//坐标转化
RI T=0;for(sort(p+1,p+n+1),i=1;i<=n;++i) if(i==1||p[i].x^p[i-1].x)//按横坐标排序,显然横坐标相同只去纵坐标大的点
{
W(T>1&&1LL*(S[T].y-S[T-1].y)*(p[i].x-S[T].x)<=1LL*(p[i].y-S[T].y)*(S[T].x-S[T-1].x)) --T;//单调栈维护上凸壳
S[++T]=p[i];
}return printf("%d\n",T-1),0;//答案为上凸壳点数-1
}