Noip模拟87 2021.11.1


T1 集合均值

打了一个比较稳的\(70pts\),本地测极限数据跑了\(0.2s\),然后\(Waitingcoders\)上面\(T\)成了\(35\),究极疑惑

其实和正解就差在了一个线性复杂度的求逆元,因为我的复杂度是\(O(nmlog(nm))\)的。。。

那么考场上推了推发现个规律

就是说他的所有情况不要按照每种情况一一计算贡献,要按照所有情况的每一列计算贡献,

这样你会发现每一列的答案是公差为\(p[1]\)的等差数列,其中\(p[1]\)即等差数列的第一项,他等于\(sum(nm)\times (nm-1)!\)

然后这不是答案,而是你计算每一列的总和,然后你再除以一个\(A\)集合的大小就算出平均数了,然后最后再除以总方案数\(nm!\)就是答案

mos
#include
#define int long long
using namespace std;
const int NN=2e7+5,mod=998244353;
namespace AE86{
	auto read=[](){
		int x=0,f=1;char ch=getchar();
		while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
		while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
		return x*f;
	};
	auto write=[](int x,char opt='\n'){
		char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
		do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
		for(short i=len-1;i>=0;--i)putchar(ch[i]);putchar(opt);
	};
	inline int qmo(int a,int b,int ans=1){
		int c=mod;for(;b;b>>=1,a=a*a%c)if(b&1)ans=ans*a%c;
		return ans;
	}
}using namespace AE86;
int n,m,a[100005],ans,T,tmp;
int h[NN],v[NN];
inline void pre(){
	h[0]=h[1]=1; v[0]=v[1]=1;
	for(int i=2;i<=m*n+1;++i)h[i]=h[i-1]*i%mod;
	tmp=qmo(h[n*m],mod-2); v[n*m+1]=qmo(h[n*m+1],mod-2);
	for(int i=n*m;i>=2;--i) v[i]=v[i+1]*(i+1)%mod;
}
namespace WSN{
	inline short main(){
		freopen("mos.in","r",stdin);
		freopen("mos.out","w",stdout);
		n=read(); m=read(); pre();
		for(int i=1;i<=n;++i) a[i]=read(),T+=a[i];
		T%=mod; T=T*m%mod;
		T=T*h[n*m-1]%mod;
		for(int i=1;i<=n*m;++i)
			ans=(ans+T*i%mod*v[i+1]%mod*h[i]%mod)%mod;
		ans=ans*tmp%mod;
		write(ans);
		return 0;
	}
}
signed main(){return WSN::main();}

T2 聚烷撑乙二醇

考场上读错题了以为第一个部分分所有的\(l=r\)并且等于同一个数,然后就直接输出了第一项,然后爆蛋了

然而他不相等啊!!那么,直接输出最大值就好了

考虑正解,不难发现在一段区间随机选择一个数的贡献是\(\frac{l+r}{2}\)

那么我们记每一个生成器的贡献为\(d_i\),然后设\(f_i\)表示从第\(i\)个开始游戏的最大期望

他这种设法有些模糊,我觉得就是表示一个答案,他这么说的就好像是从哪里开始游戏是随便选择的一样

不难发现最后的答案\(f_n\)就是\(d_n\),那么可以倒着转移,分三种情况转移

\(f_{n-1}= \begin{cases} d_{n-1} &\mathrm{ if }L_{n-1}>f_n\\ f_n &\mathrm{ if }R_{n-1}

如果\(f_{i+1}\)没有落在当前考虑的\([L_i,R_i]\)区间上,直接选择较优的转移即可

如果在\([L_i,R_i]\)区间上,考虑第\(i\)个生成器生成的数\(X\)\(f_{i+1}\)的大小关系合并转移即可

pag
#include
#define int long long
using namespace std;
const int NN=1e6+5;
namespace AE86{
	auto read=[](){
		int x=0,f=1;char ch=getchar();
		while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
		while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
		return x*f;
	};
	auto write=[](int x,char opt='\n'){
		char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
		do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
		for(short i=len-1;i>=0;--i){putchar(ch[i]);}putchar(opt);
	};
}using namespace AE86;
typedef long double D;
int n,tmp;
D f[NN],d[NN];
struct node{D l,r;}s[NN];
namespace WSN{
	inline short main(){
		freopen("pag.in","r",stdin);
		freopen("pag.out","w",stdout);
		n=read();
		for(int i=1;i<=n;i++){
			s[i].l=read(),s[i].r=read();
			d[i]=(s[i].l+s[i].r)/2;
			if(s[i].l==s[i].r) ++tmp;
		}
		if(tmp==n){
			int ans=0;
			for(int i=1;i<=n;i++)ans=max(ans,(int)s[i].l);
			printf("%.5lf\n",1.0*ans);
			return 0;
		}
		f[n]=d[n];
		for(int i=n-1;i;i--){
			if(f[i+1]s[i].r){
				f[i]=f[i+1];
			}else{
				f[i]=(s[i].r-f[i+1])/(s[i].r-s[i].l)*(f[i+1]+s[i].r)/2;
				f[i]+=(f[i+1]-s[i].l)/(s[i].r-s[i].l)*f[i+1];
			}
		}
		printf("%.5Lf\n",f[1]);
		return 0;
	}
}
signed main(){return WSN::main();}

T3 技术情报局

比较明显是要建出大根堆笛卡尔树的,不过在\(T1\)花时间太多而且还想做\(T4\)于是就直接打了一个部分分和一个线段树走了

预计得分\(40pts\),实际得分\(20\),因为卡了\(O(n)\)的常,没事,正常正常。。。。。

考虑建出笛卡尔树后如何合并答案,想到原来做过的一道题

没错还是他,我好像已经两次在别的博客里面拿出这套题的链接了

这道题的维护方式和他一样,那么我们只需要记录四个值就可以合并区间答案了,时间复杂度\(O(n)\)

tio
#include
#define int long long
using namespace std;
const int NN=1e7+5;
namespace AE86{
	auto read=[](){
		int x=0,f=1;char ch=getchar();
		while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
		while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
		return x*f;
	};
	auto write=[](int x,char opt='\n'){
		char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
		do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
		for(short i=len-1;i>=0;--i){putchar(ch[i]);}putchar(opt);
	};
	auto max_=[](int a,int b){return a>b?a:b;};
	auto min_=[](int a,int b){return a sca;
namespace GenHelper {
	unsigned z1, z2, z3, z4, b;
	unsigned rand_() {
	b = ((z1 << 6) ^ z1) >> 13;
	z1 = ((z1 & 4294967294U) << 18) ^ b;
	b = ((z2 << 2) ^ z2) >> 27;
	z2 = ((z2 & 4294967288U) << 2) ^ b;
	b = ((z3 << 13) ^ z3) >> 21;
	z3 = ((z3 & 4294967280U) << 7) ^ b;
	b = ((z4 << 3) ^ z4) >> 12;
	z4 = ((z4 & 4294967168U) << 13) ^ b;
	return (z1 ^ z2 ^ z3 ^ z4);
	}
}
vector get (int n, unsigned s, int l, int r) {
	vector a;
	using namespace GenHelper;
	z1 = s;
	z2 = unsigned((~s) ^ 0x233333333U);
	z3 = unsigned(s ^ 0x1234598766U);
	z4 = (~s) + 51;
	for (int i = 1; i <= n; i ++) {
		int x = rand_() & 32767;
		int y = rand_() & 32767;
		a.push_back(l + (x * 32768 + y) % (r - l + 1));
	}
	return a;
}
inline int qmo(int a,int b,int ans=1){
	int c=mod;for(;b;b>>=1,a=a*a%c)if(b&1)ans=ans*a%c;
	return ans;
}
namespace TASK{
	int pw[NN];
	inline void task(){
		int ans=0;pw[0]=1;for(int i=1;i<=n;++i)pw[i]=w[i]*pw[i-1]%mod;
		for(int i=1;i<=n;++i)ans=(ans+pw[i]*w[1]%mod*(n-i+1)%mod)%mod;
		write(ans);
	}
}using namespace TASK;
namespace carisian_tree{
	int son[NN][2],siz[NN],stk[NN],top;
	inline void build(int *a){
		for(int i=1;i<=n;i++){
			while(top&&a[stk[top]]

T4 肯德基

莫比乌斯大白板

考场上推杜教筛的部分分用时:\(\leq 1h\)

考场上推正解用时:\(\leq 15min\)

大雾

然而考试的时候不会解决等差数列的\(/2\)要特判的问题导致没有调出来正解代码,于是。。。

然后就gg。。。。。。

非常郁闷,啊啊啊啊啊!!!!

问题实际上是求范围在\([1,n]\)内的没有平方质因子的数的总和

考虑容斥

我们枚举平方因子是哪个,最后用\(sum(n)\)减去有平方因子的数的和就是答案

那么答案就是

\(\begin{matrix} & sum(n)-\sum\limits_{d=2}^{\sqrt{n}}\sum\limits_{i=1}^{\frac{n}{d^2}}id^2 \\ & =sum(n)-\sum\limits_{d=2}^{\sqrt{n}}d^2\sum\limits_{i=1}^{\frac{n}{d^2}}i \\ & =sum(n)-\sum\limits_{d=2}^{\sqrt{n}}d^2sum(\frac{n}{d^2}) \end{matrix}\)

但其实他是不对的!!!

为什么?因为会算重。

在哪里?在一个数有多个质因子的地方,于是我们加上容斥系数\(\mu\),他就对了,即

\(=sum(n)-\sum\limits_{d=2}^{\sqrt{n}}\mu(d)d^2sum(\frac{n}{d^2})\)

然后发现\(sum(n)\)可以直接放进大循环里算,那么答案就是

\(ans=\sum\limits_{d=1}^{\sqrt{n}}\mu(d)d^2sum(\frac{n}{d^2})\)

然后直接筛出\(\mu(d)d^2\)的前缀和数论分块即可

可能是昨天学了一天的杜教筛和莫比乌斯反演,这题确实比较白板

\(\huge{另外,如果有路过的大佬会杜教筛的部分分,请疯狂在评论区D我}\)

kfc
#include
#define int unsigned long long
using namespace std;
const int NN=10000001;
namespace AE86{
	auto read=[](){
		int x=0,f=1;char ch=getchar();
		while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
		while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
		return x*f;
	};
	auto write=[](int x,char opt='\n'){
		char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
		do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
		for(short i=len-1;i>=0;--i)putchar(ch[i]);putchar(opt);
	};
}using namespace AE86;
int T,n;
signed prime[NN],len;
signed mu[NN];
bool vis[NN];
int F[NN],S[NN];
inline void getprime(){
	mu[1]=1; F[1]=1; S[1]=1;
	for(int i=2;i