PKUSC2018部分题解

153 篇文章 0 订阅
134 篇文章 0 订阅

.
LOJ的std+数据+当时讲题的记忆硬是刚了五道题(D1t3咕咕咕)
稍微写一下题解

LOJ6432 「PKUSC2018」真实排名

一道比较简单的题,不过需要注意很多细节
考虑排名不变的两种情况
1.自己分数 x x x不变,那么所有分数在 [ [ x 2 ] , x ) [[\frac{x}{2}],x) [[2x],x)中的其他人的分数也不能变,否则必然有在自己后面的人到自己前面
2.自己分数 x x x翻倍,那么所有分数在 [ x , 2 x ) [x,2x) [x,2x)中的人分数必须翻倍,其他人则可以随意
预处理一下组合数,让后二分找范围就可以了
需要特判0

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 100010
#define M 998244353
#define LL long long
using namespace std;
int n,m,v[N],w[N]; LL js[N],inv[N];
inline LL pow(LL x,LL k,LL s=1){
	for(;k;x=x*x%M,k>>=1) k&1?s=s*x%M:0; return s;
}
inline LL C(int n,int m){
	if(m<0) return 0;
	if(n<m) return 0;
	return js[n]*inv[m]%M*inv[n-m]%M;
}
inline int rank(int x){
	return lower_bound(w+1,w+1+n,x)-w;
}
inline int count(int l,int r){
	if(l>r) return 0;
	int x=rank(l),y=rank(r+1);
	while(x&&w[x]>=l) --x;
	return y-x-1;
}
int main(){
	freopen("1.in","r",stdin);
	freopen("1.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%d",v+i);
	memcpy(w,v,sizeof w); sort(w+1,w+1+n);
	for(int i=*js=1;i<=n;++i) js[i]=js[i-1]*i%M;
	inv[n]=pow(js[n],M-2);
	for(int i=n;i;--i) inv[i-1]=inv[i]*i%M;
	for(int i=1;i<=n;++i){
		int r=count(v[i]+1>>1,v[i]-1)+1;
		int l=count(v[i],(v[i]<<1)-1);
		if(!v[i]) ++l;
		printf("%lld\n",(C(n-r,m)+C(n-l,m-l))%M);
	}
}

.
LOJ6433 「PKUSC2018」最大前缀和

很好的DP题,当时只水了一个 O ( 3 n ) O(3^n) O(3n)的状压
考虑最大(非空)前缀和的性质,假设这个位置是 x x x,很容易发现
1. ∑ i = x + 1 r a i &lt; 0 \sum_{i=x+1}^ra_i&lt;0 i=x+1rai<0 这里 r ≤ n r\le n rn
2. ∑ i = l x a i &gt; 0 \sum_{i=l}^xa_i &gt; 0 i=lxai>0 这里 1 &lt; l ≤ x 1&lt;l\le x 1<lx
为什么l要大于1,因为答案要求是非空,也就是说即使 ∑ i = 1 x &lt; 0 \sum_{i=1}^x&lt;0 i=1x<0也不可以取到0
让后,我们就可以根据这两条限制来做状压DP
F i F_i Fi表示取用集合i的元素,能组成多少种排列满足条件2(任何一个后缀和大于0)
G i G_i Gi表示取用集合i的元素,能组成多少种排列满足条件1(任何一个前缀和小于0)
预处理 S i S_i Si表示集合i的元素之和
考虑转移,对于 F F F,枚举下一个加入的元素即可,需要保证当前状态和大于0才能转移
对于 G G G,枚举最后加入的元素是哪一个转移,需要保证当前状态和小于0才能转移
答案是 ∑ i ∈ S F i ∗ S i ∗ G S   x o r   i \sum_{i\in S}F_i*S_i*G_{S\ xor \ i} iSFiSiGS xor i

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 20
#define M 998244353
#define LL long long
using namespace std;
int n; LL s[1<<N],f[1<<N],g[1<<N],A;
inline void ad(LL& x,LL y){ x=(x+y)%M; }
int main(){
	scanf("%d",&n);
	for(int i=0;i<n;++i){
		scanf("%lld",s+(1<<i));
		f[1<<i]=1;
	}
	int m=(1<<n)-1;
	for(int i=1;i<=m;++i)
		s[i]=s[i&i-1]+s[i&-i];
	for(int i=1;i<=m;++i) if(s[i]>0)
		for(int j=m^i;j;j&=j-1) ad(f[i|j&-j],f[i]);
	*g=1;
	for(int i=1;i<=m;++i) if(s[i]<=0)
		for(int j=i;j;j&=j-1) ad(g[i],g[i^j&-j]);
	for(int i=1;i<=m;++i) ad(A,f[i]*s[i]%M*g[i^m]%M);
	printf("%lld\n",(A+M)%M);
}

LOJ6434咕咕咕

LOJ6435 「PKUSC2018」星际穿越

居然是倍增?还有这种操作?!
一个非常牛逼的解法,首先稍加观察就可以发现,往后走的次数不多于1
而且询问总是在起点的前面
那么就可以考虑利用建树让后倍增的做法来求
首先,我们记 F i F_i Fi表示从i的后面任何一个节点除法,一步能走到的最前面的那个节点
这也算是在树上的父亲节点,处理方法就是对 L i L_i Li做一次后缀最小值即可
让后,预处理出 S i S_i Si表示从节点i出发,到1~i-1的所有点的最短路线中,不计往后走的那一步的路程之和
关于为什么不计往后走的那一步,稍后再解释
那么显然 S i = S F i + i − 1 , S 1 = 0 S_i=S_{F_i}+i-1,S_1=0 Si=SFi+i1,S1=0
接下来考虑如何求答案
考虑差分,我们将 [ l , r ] [l,r] [l,r]拆成两个区间答案的差
让后考虑计算节点 x x x [ 1 , r ] [1,r] [1,r]的距离之和
首先,如果 L x &lt; r L_x&lt;r Lx<r就说明,从 x x x出发可以一步到达r,那么所有在 L x L_x Lx之前的节点,除了第一步,剩下要走的步数之和,就正好是 S L x S_{L_x} SLx
为什么?考虑从 L x L_x Lx出发走到一个节点i的路线,要么就是一直向前,要么就是往后走了一步,而如果,从 L x L_x Lx可以往后一步走到,那么从 x x x出发,也肯定可以一步走到
所以,可以得到答案为 S L x + r S_{L_x}+r SLx+r
如果 r &lt; L x r&lt;L_x r<Lx,那么就考虑走 F x F_x Fx,我们通过倍增计算出至少要走几步才能走到一个比 r r r要前的位置,设走了 t t t步,走到了 y y y y &lt; = r y&lt;=r y<=r
那么答案就是 ( t + 1 ) ∗ r + S y (t+1)*r+S_y (t+1)r+Sy
首先,由 x x x F x F_x Fx相当于往后走一步,再往前走一步,需要2步,但是为什么不是 2 t 2t 2t而是 t + 1 t+1 t+1?因为向后走的步数,只需要最多1步,往后走过一次,如果已经到达过x之后的节点,再从x走到 F x F_x Fx就只需要1步,这样就可以完美解决本题了

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 300010
#define LL long long
using namespace std;
int n,m,f[18][N],l[N]; LL S[N];
inline int gcd(int x,int y){
	while(y^=x^=y^=x%=y); return x;
}
inline LL gf(int x,int y){
	if(!x) return 0;
	int A=1; y=l[y];
	if(x<y){
		++A;
		for(int i=17;~i;--i)
			if(x<f[i][y]) y=f[i][y],A+=(1<<i);
		y=f[0][y];
	}
	return A*x+S[y];
}
int main(){
	scanf("%d",&n); f[0][n+1]=n;
	for(int i=2;i<=n;++i) scanf("%d",l+i);
	for(int i=n;i>1;--i) f[0][i]=min(l[i],f[0][i+1]);
	for(int i=1;i<18;++i)
		for(int j=1;j<=n;++j)
			f[i][j]=f[i-1][f[i-1][j]];
	for(int i=2;i<=n;++i) S[i]=S[f[0][i]]+i-1;
	scanf("%d",&m);
	for(int l,r,x;m--;){
		scanf("%d%d%d",&l,&r,&x);
		LL A=gf(r,x)-gf(l-1,x); l=r-l+1; r=gcd(A%l,l);
		printf("%lld/%d\n",A/r,l/r);
	}
}

.
LOJ6436 「PKUSC2018」神仙的游戏

口算什么的不存在的
以后见到01字符串匹配类似的,都可以往FFT这方面去考虑
我们设 A i = [ s i = 1 ] , B i = [ s n − i − 1 = 0 ] A_i=[s_i=1],B_i=[s_{n-i-1}=0] Ai=[si=1],Bi=[sni1=0]
那么求一个A和B的卷积 C k = ∑ i = 0 k A i ∗ B k − i C_k=\sum_{i=0}^kA_i*B_{k-i} Ck=i=0kAiBki
发现,当 C k = 0 C_k=0 Ck=0时,k可能为一个border
为什么是可能?参考样例,简单来说,同一个?不能变成两个不同的数字
于是利用另外一个性质,如果 x x x为周期,那么 k x kx kx为周期,k为正整数
枚举倍数判断一下就好了

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 1100000
#define M 998244353
#define LL long long 
using namespace std;
char s[N]; int vis[N];
int n,m,W[N],iW[N],A[N],B[N];
inline LL pow(LL x,LL k,LL s=1){
	for(;k;x=x*x%M,k>>=1) k&1?s=s*x%M:0; return s;
}
inline void init(){
	for(int j=1;j<N;j<<=1){
		W[j]=pow(3,(M-1)/j);
		iW[j]=pow(W[j],M-2);
	}
}
inline void NTT(int* A,int n,int g){
	static int b[N]; memcpy(b,A,n<<2);
	for(int i=0,j=0;i<n;++i){
		if(i>j) swap(b[i],b[j]);
		for(int l=n>>1;(j^=l)<l;l>>=1);
	}
	for(int k=1;k<n;k<<=1){
		LL w=g?W[k<<1]:iW[k<<1],u,v,z=1;
		for(int j=k;j<n;++j&k?z=z*w%M:z=1,j|=k){
			u=b[j^k]; v=z*b[j]%M;
			b[j^k]=(u+v)%M; b[j]=(u-v+M)%M;
		}
	}
	if(!g){
		g=pow(n,M-2);
		for(int i=0;i<n;++i) A[i]=(LL)b[i]*g%M;
	} else memcpy(A,b,n<<2);
}
int main(){
	scanf("%s",s);
	init(); n=strlen(s);
	for(int i=0;i<n;++i){
		A[i]=s[i]=='0';
		B[i]=s[n-i-1]=='1';
	}
	for(m=1;m<n+n;m<<=1);
	NTT(A,m,1);
	NTT(B,m,1);
	for(int i=0;i<m;++i) A[i]=(LL)A[i]*B[i]%M;
	NTT(A,m,0);
	for(int i=0;i<m;++i) vis[abs(n-i-1)]+=A[i];
	LL A=0;
	for(int i=1;i<n;++i){
		bool f=0;
		for(int j=n-i;j<=n;j+=n-i)
			if(vis[j]){ f=1; break; }
		if(!f) A^=(LL)i*i;
	}
	A^=(LL)n*n;
	printf("%lld\n",A);
}

.
LOJ6437 「PKUSC2018」PKUSC

被精度卡哇!了
参考 jefflyy \text{jefflyy} jefflyy的博客,我们可以发现一个显然的解法
每个敌人画圆,和多边形求交,弧长之和除以2π即可
因为我的做法,导致精度出现巨大问题(全程都在用方程算而不是角度,就连弧中点也是拿复数开方)
最后不得不对着数据调了一天

#include<math.h>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define eps 1e-6
#define db double
#define N 1010
#define pi 3.14159265358979323846
#define pi2 6.283185307179586476925286766559
using namespace std;
inline int sgn(db x){
	return fabs(x)<eps?0:(x>0?1:-1);
}
struct vec{ db x,y; };
typedef vec point;
inline bool operator==(vec a,vec b){ return a.x==b.x&&a.y==b.y; }
inline bool operator!=(vec a,vec b){ return sgn(a.x-b.x)||sgn(a.y-b.y); }
inline bool cx(point a,point b){ return a.x<b.x; }
inline bool ca(point a,point b){
	if(a.y*b.y<0) return sgn(a.y)<sgn(b.y);
	return sgn(a.y)*(a.x-b.x)>0;
}
inline db dot(vec a,vec b){ return a.x*b.x+a.y*b.y; }
inline db crs(vec a,vec b){ return a.x*b.y-a.y*b.x; }
inline db l2(vec a){ return dot(a,a); }

inline vec operator+(vec a,vec b){ return (vec){a.x+b.x,a.y+b.y}; }
inline vec operator-(vec a,vec b){ return (vec){a.x-b.x,a.y-b.y}; }
inline vec operator*(vec a,db  x){ return (vec){a.x*x,a.y*x}; }
inline vec operator/(vec a,db  x){ return (vec){a.x/x,a.y/x}; }
inline vec operator*(vec a,vec b){ return (vec){a.x*b.x-a.y*b.y,a.y*b.x+a.x*b.y}; }
inline vec sqrt(vec x,vec f){
	db a=x.x,b=x.y;
	b=b*b/4;
	//u-v=a,uv=b
	db c=sqrt(a*a+4*b);
	db u=(c+a)/2,v=(c-a)/2;
	u=sqrt(u); v=sqrt(v);
	if(sgn(u*v)!=sgn(x.y)) v=-v;
	vec y=(vec){u,v};
	if(crs(f,y)<0) y=(vec){-u,-v};
	return y; 
}
inline db ang(vec x){ return atan2(x.y,x.x); }
inline int cross(point a,point A,point b,point B){
	return sgn(crs(A-a,b-a))*sgn(crs(A-a,B-a))<0
		&& sgn(crs(B-b,a-b))*sgn(crs(B-b,A-b))<0;
}
inline int on(point x,point a,point b){
	if(a.x>b.x) swap(a,b);
	return (a.x<x.x||fabs(a.x-x.x)<1e-2)
		&& (x.x<b.x||fabs(x.x-b.x)<1e-2);//1e-5会WA,点积判断会WA
}
point p[N],w[N],s[N];
int n,m,c;
inline void intersection(point t,point a,point b){
	db x,y;
	db A=(a-b).y,B=-(a-b).x,C=A*a.x+B*a.y,r2=dot(t,t);
	if(!sgn(A)){
		y=C/B;
		if(sgn(r2-y*y)<0) return;
		x=sqrt(r2-y*y);
		if(on((vec){x,y},a,b)) w[++c]=(vec){x,y};
		x=-x;
		if(on((vec){x,y},a,b)) w[++c]=(vec){x,y};
	} else if(!sgn(B)){
		x=C/A;
		if(sgn(r2-x*x)<0) return;
		y=sqrt(r2-x*x);
		if(on((vec){x,y},a,b)) w[++c]=(vec){x,y};
		y=-y;
		if(on((vec){x,y},a,b)) w[++c]=(vec){x,y};
	} else {
		db dA=A*A+B*B,dB=-2*A*C,dC=C*C-r2*B*B;
		db dlt=dB*dB-4*dA*dC;
		if(dlt<0) return;
		x=(-dB+sqrt(dlt))/(dA*2.);
		y=(C-A*x)/B;
		if(on((vec){x,y},a,b)) w[++c]=(vec){x,y};
		x=(-dB-sqrt(dlt))/(dA*2.);
		y=(C-A*x)/B;
		if(on((vec){x,y},a,b)) w[++c]=(vec){x,y};
	}
}
inline int in(point t){
	int A=0;
	point o=(vec){1.3893e6,3.38756e6};
	for(int i=1;i<=m;++i)
		if(cross(o,t,s[i],s[i+1])) ++A;
	return A&1;
}
inline db ra(db x){ if(x<-pi) return x+pi2; return x; }
int main(){
//	freopen("1.in","r",stdin);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%lf%lf",&p[i].x,&p[i].y);
	for(int i=1;i<=m;++i) scanf("%lf%lf",&s[i].x,&s[i].y);
	s[m+1]=s[1]; db A=0;
	for(int i=1;i<=n;++i){
		c=0;
		for(int j=1;j<=m;++j)
			intersection(p[i],s[j],s[j+1]);
//		for(int j=1;j<=c;++j) printf("%lf\n",ang(w[j])-ang(p[i]));
		if(c<2){
			if(in(p[i])) A+=1.;
		} else {
			sort(w+1,w+1+c,ca);
			c=unique(w+1,w+1+c)-w-1;
			w[c+1]=w[1];
			for(int j=1;j<c;++j)
				if(in(sqrt(w[j]*w[j+1],w[j]))){
					db t=ang(w[j+1])-ang(w[j]);
					if(t<-1e-2) t+=pi2;//这里WA得我也是醉了
					A+=t/pi2;
				}
			if(in(sqrt(w[c]*w[c+1],w[c]))){
				db t=ang(w[c+1])-ang(w[c]);
				if(t<0) t+=pi2;
				A+=t/pi2;
			}
		}
	}
	printf("%.5lf\n",A);
}

大概就是这些,应该不会再更新了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值