2021.10.29考试总结[冲刺NOIP模拟18]

这题面属实乐子

给我考成败犬了

T1 莓良心

我打扮成你喜欢的样子来看你了,广,不,da,darling...

不难看出\(w\)值与它的贡献次数无关。考虑每个方案它必定会有\(1\)的贡献与跟其他元素结合产生的额外贡献,有

\[ans=(\sum_{i=1}^nw_i)\times\begin{Bmatrix}n\\k\end{Bmatrix}+(\sum_{u!=v}(w_u+w_v))\begin{Bmatrix}n-1\\k\end{Bmatrix} \]

\[ans=(\sum_{i=1}^nw_i)\times(\begin{Bmatrix}n\\k\end{Bmatrix}+(n-1)\begin{Bmatrix}n-1\\k\end{Bmatrix}) \]

其中\(\begin{Bmatrix}n\\k\end{Bmatrix}\)表示第二类斯特林数,将\(n\)个元素划分为\(k\)个非空集合的方案数。

\[\begin{Bmatrix}n\\k\end{Bmatrix}=\frac{1}{k!}\sum_{i=0}^k(-1)^i\binom{k}{i}(k-i)^n \]

线性筛出所有\(k-i\)\(n\)次方和\(n-1\)次方。

\(code:\)

T1
#include<bits/stdc++.h>
#define int long long
using namespace std;

namespace IO{
	typedef long long LL;
	int read(){
		LL 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;
	}
	void write(int x,char sp){
		char ch[20]; int len=0;
		if(x<0) x=-x,putchar('-');
		do{ ch[len++]=x%10+'0'; x/=10; }while(x);
		for(int i=len-1;~i;i--) putchar(ch[i]); putchar(sp);
	}
	void ckmin(int& x,int y){ x=x<y?x:y; }
	void ckmax(int& x,int y){ x=x>y?x:y; }
} using namespace IO;

const int NN=1000010,mod=998244353;
int n,k,cnt,sum,tmp1,tmp2,f[NN],g[NN],pri[NN];
bool vis[NN];

namespace Combination{
	int inv[NN],fac[NN];
	int C(int x,int y){ return x<0||y<0||x<y?0:fac[x]*inv[y]%mod*inv[x-y]%mod; }
	int qpow(int a,int b){
		int res=1;
		for(;b;b>>=1){
			if(b&1) res=res*a%mod;
			a=a*a%mod;
		}
		return res;
	}
	void init(){
		fac[0]=inv[0]=1;
		for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
		inv[n]=qpow(fac[n],mod-2);
		for(int i=n-1;i;i--) inv[i]=inv[i+1]*(i+1)%mod;
	}
} using namespace Combination;

void get(){
	f[1]=1; g[1]=1;
	for(int i=2;i<=n;i++){
		if(!vis[i]) pri[++cnt]=i,f[i]=qpow(i,n),g[i]=qpow(i,n-1);
		for(int j=1;j<=cnt&&pri[j]*i<=n;i++){
			vis[pri[j]*i]=1;
			f[pri[j]*i]=f[pri[j]]*f[i]%mod;
			g[pri[j]*i]=g[pri[j]]*g[i]%mod;
			if(i%pri[j]==0) break;
		}
	}
}
signed main(){
	freopen("ichigo.in","r",stdin);
	freopen("ichigo.out","w",stdout);
	n=read(); k=read(); init(); get();
	for(int i=1;i<=n;i++) (sum+=read())%=mod;
	for(int i=0;i<=k;i++){
		(tmp1+=((i&1)?-1:1)*C(k,i)*f[k-i]%mod+mod)%=mod;
		(tmp2+=((i&1)?-1:1)*C(k,i)*g[k-i]%mod+mod)%=mod;
	}
	(tmp1*=qpow(fac[k],mod-2))%=mod;
	(tmp2*=qpow(fac[k],mod-2))%=mod;
	sum=sum*(tmp1+(n-1)*tmp2%mod)%mod;
	write(sum,'\n');
	return 0;
}

T2 尽梨了

我成为你心中的第一位了吗?

先考虑\(a_i>0\)的情况。

考虑临项交换,对于\(i<j\),计算排队多出的时间,i在\(j\)之前更优,则

\[(a_i\times t+b_i+1)\times a_j<(a_j\times t+b_j+1)\times a_i \]

\[(b_i+1)\times a_j<(b_j+1)\times a_i \]

按这种关系排序,则最终答案肯定由排序后的子序列产生。设状态\(f_{i,j}\)为考虑到第\(i\)位,选了\(j\)个元素的最短时间。那么有

\[f_{i,j}=min(f_{i-1,j},f_{i-1,j-1}+a_i\times(f_{i-1,j-1}+1)+b_i+1) \]

因为\(a_i>0\),所以每选一次时间至少翻倍,第二位是\(\log\)级的,复杂度没问题。

\(DP\)之后考虑\(a_i=0\)的元素,从小到大选就行了。

\(code:\)

T2
#include<bits/stdc++.h>
#define int long long
using namespace std;

namespace IO{
	typedef long long LL;
	int read(){
		LL 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;
	}
	void write(int x,char sp){
		char ch[20]; int len=0;
		if(x<0) x=-x,putchar('-');
		do{ ch[len++]=x%10+'0'; x/=10; }while(x);
		for(int i=len-1;~i;i--) putchar(ch[i]); putchar(sp);
	}
	void ckmin(int& x,int y){ x=x<y?x:y; }
	void ckmax(int& x,int y){ x=x>y?x:y; }
} using namespace IO;

const int NN=200010;
int n,t,ext,ans,tim,dlt[NN],f[NN][50];
vector<int>zro;
struct shop{
	int a,b;
	bool operator<(const shop& x)const{
		return (b+1)*x.a<(x.b+1)*a;
	}
}s[NN];

signed main(){
	freopen("eriri.in","r",stdin);
	freopen("eriri.out","w",stdout);
	n=read(); t=read();
	for(int a,b,i=1;i<=n;i++){
		a=read(); b=read();
		if(!a) zro.push_back(b);
		else s[++ext]=(shop){a,b};
	}
	sort(s+1,s+ext+1); sort(zro.begin(),zro.end());
	memset(f,0x3f,sizeof(f)); f[0][0]=0;
	for(int i=1;i<=ext;i++)
		for(int j=0;j<50;j++){
			f[i][j]=f[i-1][j];
			if(j&&f[i-1][j-1]<=t)
				ckmin(f[i][j],f[i-1][j-1]+1+s[i].b+(1+f[i-1][j-1])*s[i].a);
		}
	for(int i=0;i<50;i++) if(f[ext][i]<=t){
		int k=f[ext][i],res=i;
		for(int j:zro){
			k+=j+1;
			if(k>t) break;
			++res;
		}
		ckmax(ans,res);
	}
	write(ans,'\n');
	return 0;
}

T3 团不过

眼泪没能流出来。因为已经哭过很多次了。

容斥。

\(p_i\)表示\(i\)堆石子的总方案数,有

\[p_i=(2^n-1)^{\underline{i}} \]

\(f_i\)表示\(i\)堆石子先手必败的方案数。

当前\(i-1\)堆石子异或和不为零时,若不考虑互不相同,那么总可以用最后一个数调整至异或和为零。这部分方案为\(p_{i-1}-f_{i-1}\)

接下来容斥掉有数相同的情况。当前\(i-1\)堆石子有\(i-2\)堆石子异或和为零时,另两堆石子必定相等。这种情况数量为\((i-1)\times(2^n-i+1)\times f_{i-2}\)。位置乘数乘方案数。

综上,

\[f_i=p_{i-1}-f_{i-1}-(i-1)\times(2^n-i+1)\times f_{i-2} \]

线性递推即可。

\(code:\)

T3
#include<bits/stdc++.h>
#define int long long
using namespace std;

namespace IO{
	typedef long long LL;
	int read(){
		LL 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;
	}
	void write(int x,char sp){
		char ch[20]; int len=0;
		if(x<0) x=-x,putchar('-');
		do{ ch[len++]=x%10+'0'; x/=10; }while(x);
		for(int i=len-1;~i;i--) putchar(ch[i]); putchar(sp);
	}
	void ckmin(int& x,int y){ x=x<y?x:y; }
	void ckmax(int& x,int y){ x=x>y?x:y; }
} using namespace IO;

const int NN=10000010,mod=1e9+7;
int n,ans,base,p[NN],f[NN];

namespace Combination{
	int fac[NN],inv[NN];
	int C(int x,int y){ return x<0||y<0||x<y?0:fac[x]*inv[y]%mod*inv[x-y]%mod; }
	int qpow(int a,int b){
		int res=1;
		for(;b;b>>=1){
			if(b&1) res=res*a%mod;
			a=a*a%mod;
		}
		return res;
	}
	void init(){
		fac[0]=inv[0]=1;
		for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
		inv[n]=qpow(fac[n],mod-2);
		for(int i=n-1;i;i--) inv[i]=inv[i+1]*(i+1)%mod;
	}
} using namespace Combination;

signed main(){
	freopen("yui.in","r",stdin);
	freopen("yui.out","w",stdout);
	n=read(); init(); base=qpow(2,n); p[0]=1;
	for(int i=1;i<=n;i++) p[i]=p[i-1]*(base-i)%mod;
	for(int i=3;i<=n;i++)
		f[i]=(p[i-1]+mod-f[i-1]+mod-(i-1)*(base-i+1)%mod*f[i-2]%mod)%mod;
	ans=(mod+p[n]-f[n])%mod;
	write(ans,'\n');
	return 0;
}

T4 七负我

既然真白你说不要的话,神田君就归我了哦。

结论:最后答案为取图中最大团,平均分配点权后得到的答案。

证明团:

\(u,v\)为互不相连的两点,设\(s_u,s_v\)分别为与\(u,v\)相连的点权和,那么当\(s_u\neq s_v\)时,显然\(u,v\)其中一点权值为\(0\)\(s_u=s_v\)时也可保证\(u,v\)其中一点权值为\(0\)是一种最优解。因此两个不连通的点间总有一点不会被分配权值,即最终答案会出现在团中。

证明平均分配:

\(t_i\)为团中第\(i\)个点的权值,\(s\)为团大小,则\(x=\sum_{i=1}^st_i\),有

\[ans=\sum_{u\neq v} t_u\times t_v \]

\[ans=\frac{x^2-\sum_{i=1}^st_i^2}{2} \]

由均值不等式,当\(t_i\)都相等时\(ans\)取最大值。

证明最大:

设团大小为\(s\),则

\[ans=\frac{s\times(s-1)}{2}\times(\frac{x}{s})^2 \]

\[ans=\frac{(s-1)\times x^2}{2s}=x^2\times(\frac{1}{2}-\frac{1}{2s}) \]

\(s\)越大,\(\frac{1}{2s}\)越趋近于\(0\),答案取值越大。

综上可得最上面的结论。

\(meet\;in\;the\;middle\),找到前一半所有点集的最大团子集,不妨设点集\(s\)的最大团子集为\(f_s\),之后枚举后一半点集\(t\),若\(t\)为团,令\(e\)\(t\)中所有点的邻接点在前一部分的交集,则\(|t|+f_e\)可以成为答案。

\(code:\)

T4
#include<bits/stdc++.h>
using namespace std;

namespace IO{
	typedef long long LL;
	int read(){
		LL 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;
	}
	void write(int x,char sp){
		char ch[20]; int len=0;
		if(x<0) x=-x,putchar('-');
		do{ ch[len++]=x%10+'0'; x/=10; }while(x);
		for(int i=len-1;~i;i--) putchar(ch[i]); putchar(sp);
	}
	void ckmin(int& x,int y){ x=x<y?x:y; }
	void ckmax(int& x,int y){ x=x>y?x:y; }
} using namespace IO;

const int NN=45;
int n,m,x,lmt,ans,f[1<<20];
int ef[NN],eb[NN];
double res;

int getnum(int s){
	int res=0;
	while(s) res+=(s&1),s>>=1;
	return res;
}
bool check(int s,bool typ){
	for(int i=0;i<lmt;i++) if(s&(1<<i))
		if(((s^(1<<i))|ef[i+1+typ*lmt])!=ef[i+1+typ*lmt]) return 0;
	return 1;
}

signed main(){
	freopen("nanami.in","r",stdin);
	freopen("nanami.out","w",stdout);
	n=read(); m=read(); x=read(); lmt=n+1>>1;
	for(int u,v,i=1;i<=m;i++){
		u=read(); v=read();
		if(u>v) swap(u,v);
		if(v<=lmt){
			ef[u]|=1<<v-1;
			ef[v]|=1<<u-1;
		} else if(u>lmt){
			ef[u]|=1<<v-1-lmt;
			ef[v]|=1<<u-1-lmt;
		} else eb[v]|=1<<u-1;
	}
	for(int i=1;i<(1<<lmt);i++)
		if(check(i,0)) ckmax(ans,f[i]=getnum(i));
	for(int s=1;s<(1<<lmt);s++)
		for(int i=0;i<lmt;i++)
			if(!(s&(1<<i))) ckmax(f[s|(1<<i)],f[s]);
	for(int s=1;s<(1<<lmt);s++) if(check(s,1)){
		int tmp=(1<<lmt)-1;
		for(int i=0;i<lmt;i++)
			if(s&(1<<i)) tmp&=eb[i+1+lmt];
		ckmax(ans,getnum(s)+f[tmp]);
	}
	res=(1.0*x/ans)*(1.0*x/ans)*(ans-1)*ans/2;
	printf("%.6lf\n",res);
	return 0;
}
posted @ 2021-10-29 19:04  keen_z  阅读(90)  评论(0编辑  收藏  举报