NOIP模拟62

T1 Set

解题思路

抽屉原理

发现对于前缀和向 \(n\) 取模之后一定是右两个值相等的(包括什么都不选的 0 )。

假设 \(pre_j=pre_i\) 那么 \([j+1,i]\) 之间这一段数字的和一定是 \(n\) 的倍数。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=1e6+10,M=1e3+10;
int n,m,s[N],pre[N],las[N];
signed main()
{
	freopen("a.in","r",stdin); freopen("a.out","w",stdout);
	memset(las,-1,sizeof(las)); n=read(); las[0]=0;
	for(int i=1;i<=n;i++)
	{
		s[i]=read()%n;
		pre[i]=(pre[i-1]+s[i])%n;
		if(las[pre[i]]!=-1)
		{
			printf("%lld\n",i-las[pre[i]]);
			for(int j=las[pre[i]]+1;j<=i;j++)
				printf("%lld ",j);
			exit(0);
		}
		las[pre[i]]=i;
	}
	return 0;
}

T2 Read

解题思路

发现内存限制了我们的想象(其实我考场上看错题了。。)

发现对于有一种书的个数大于其他书的个数之和 +1 答案才不是 0 。

因此,可以 \(n\) 的复杂度扫一遍,用两个变量id, cnt, cnt 初始为0。

然后生成每一个 \(A_i\) , 如果 \(cnt=0\) , 那么就令 \(id=A[i],cnt=1\) , 否则如果 \(id==A[i]\) , 则 \(cnt++\) , 如果不等于, \(cnt--\)

最后只要再扫一遍求出id 的出现次数即可。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=5e7+10,Mx=1e3+10;
int M,K,n,all,cnt,id,Num[Mx],X[Mx],Y[Mx],Z[Mx];
void init()
{
	M=read(); K=read();
	for(int i=1;i<=M;i++) Num[i]=read(),n+=Num[i];;
	for(int i=1;i<=M;i++) X[i]=read();
	for(int i=1;i<=M;i++) Y[i]=read();
	for(int i=1;i<=M;i++) Z[i]=read();
	int S=(1<<K)-1,len=0;
	for(int i=1,temp;i<=M;i++)
	{
		temp=X[i]; int las=X[i];
		if(!cnt) id=temp,cnt=1;
		else if(id==temp) cnt++;
		else cnt--;
		for(int j=1;j<Num[i];j++)
		{
			las=(las*Y[i]+Z[i])&S;
			temp=las;
			if(!cnt) id=temp,cnt=1;
			else if(id==temp) cnt++;
			else cnt--;
		}
	}
}
void work()
{
	int S=(1<<K)-1,len=0;
	for(int i=1,temp;i<=M;i++)
	{
		temp=X[i]; int las=X[i]; all+=(temp==id);
		for(int j=1;j<Num[i];j++)
		{
			las=(las*Y[i]+Z[i])&S;
			temp=las; all+=(temp==id);
		}
	}
}
signed main()
{
	freopen("b.in","r",stdin); freopen("b.out","w",stdout);
	init(); work(); printf("%lld",(all<=(n+1)/2)?0:all-(n-all)-1);
	return 0;
}

T3 题目交流通道

解题思路

好像是个容斥经典题(尽管我不会)。。

对于每一个距离为 0 的联通块给缩起来,称为团。

两个团之间的点的距离是一定的,那么就要考虑重边的情况,假设有 \(a\) 条边重复,如果存在 \(k\) 使得 \(d_{i,j}=d_{i,k}+d_{j,k}\) ,那么剩下所有的边只要大于等于 \(d_{i,j}\) 就可以了,方案数是 \((K-d_{i,j}+1)^a\)

如果不存在的话至少有一条边要等于 \(d_{i,j}\) 就好了,所以在上述情况的方案数中除去都是 大于 \(d_{i,j}\) 的情况,也就是 \((K-d_{i,j}+1)^a-(K-d_{i,j})^a\)

考虑处理团内部的边,然后经典的容斥就来了: \(f_i\) 表示 \(i\) 个点距离为 0 的方案数, \(g_i\) 表示不管是否合法 \(i\) 个点的图的方案数。

就有 \(g_i=(K+1)^{\binom{i}{2}}\) 也就是取值的边数次方。

\[f_i=\sum\limits_{j=1}^{n-1}f_i\times g_{i-j}\times \binom{i-1}{j-1}\times K^{j(i-j)} \]

那么为什么组合系数是 \(\binom{i-1}{j-1}\) 呢,枚举时我们把图分成了两个部分:已知合法的和未知的。

那么显然未知的部分包含了一部分合法的团,为了防止重复,我们钦定枚举的是 1 节点(也可以钦定别的节点)所在的团是合法的部分,这样就相当于是从 \(i-1\) 个点中选择 \(j-1\) 个点与 1 节点联通。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=410,mod=998244353;
int n,m,ans=1,siz[N],fac[N],ifac[N],dis[N][N],f[N],g[N],fa[N];
int find(int x){if(fa[x]==x) return x;return fa[x]=find(fa[x]);}
int C(int x,int y,int p=mod){return fac[x]*ifac[y]%p*ifac[x-y]%p;}
int power(int x,int y,int p=mod){int temp=1;while(y){if(y&1) temp=temp*x%p;x=x*x%p;y>>=1;}return temp;}
signed main()
{
	freopen("c.in","r",stdin); freopen("c.out","w",stdout);
	n=read(); m=read();
	fac[0]=ifac[0]=1; for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod,g[i]=f[i]=power(m+1,i*(i-1)/2),fa[i]=i;
	ifac[n]=power(fac[n],mod-2); for(int i=n-1;i>=1;i--) ifac[i]=ifac[i+1]*(i+1)%mod;
	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) dis[i][j]=read();
	for(int i=1;i<=n;i++) if(dis[i][i]) printf("0"),exit(0);
	for(int i=1;i<=n;i++) for(int j=1;j<i;j++) if(dis[i][j]!=dis[j][i]||dis[i][j]>m) printf("0"),exit(0);
	for(int i=1;i<=n;i++) for(int j=1;j<i;j++) for(int k=1;k<=n;k++) if(dis[i][k]+dis[k][j]<dis[i][j]) printf("0"),exit(0);
	for(int i=1;i<=n;i++) for(int j=1;j<i;j++) f[i]=(f[i]-f[j]*g[i-j]%mod*C(i-1,j-1)%mod*power(m,j*(i-j))%mod+mod)%mod;
	for(int i=1;i<=n;i++) for(int j=1;j<i;j++) if(!dis[i][j]&&find(i)!=find(j)) fa[find(i)]=find(j);
	for(int i=1;i<=n;i++) siz[find(i)]++;
	for(int i=1;i<=n;i++) if(find(i)==i) ans=ans*f[siz[i]]%mod;
	for(int i=1;i<=n;i++)
		for(int j=1,k;j<i;j++)
			if(find(i)==i&&find(j)==j)
			{
				for(k=1;k<=n;k++) if(dis[i][k]&&dis[k][j]&&dis[i][k]+dis[k][j]==dis[i][j]) break;
				if(k<=n) ans=ans*power(m-dis[i][j]+1,siz[i]*siz[j])%mod;
				else ans=ans*(power(m-dis[i][j]+1,siz[i]*siz[j])-power(m-dis[i][j],siz[i]*siz[j]))%mod;
			}
	printf("%lld",ans);
	return 0;
}

T4 题目难度提升

解题思路

貌似我的做法被 Hack 了(OJ 可以过就行。。),大概是细节处理不到位吧。

其实就是题解的做法,有了那两个思想时候我们就可以开两个 set 维护已经加入的数字以及剩下的数字。

加入我们需要查询 \((m,k)\) 之间是否有数字被放进去,可以直接在维护放入数字的 set 中查找第一个大于 \(m\) 数字,看他与 \(k\) 的大小关系。

对于每一次中位数的维护可以维护中间的两个数字什么然后就没啥了。。。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=1e5+10,INF=1e18;
int n,cnt,ans[N];
double mid,s[N];
bool flag;
multiset<double> se,in;
void solve1(int i)
{
	double num=(*se.upper_bound(mid)),temp=*in.upper_bound(mid),r=2*num-mid; 
	if(temp<num) ans[i]=(*se.rbegin()),mid=(mid+temp)/2.0;
	else if(temp<=r) ans[i]=(*se.rbegin()),mid=(mid+temp)/2.0;
	else ans[i]=(*(--se.upper_bound(r))),mid=(mid+1.0*ans[i])/2.0;
	se.erase(se.find(ans[i])); in.insert(ans[i]);
}
void solve2(int i)
{
	double num=*se.lower_bound(mid),temp=*in.upper_bound(mid);
	if(temp<num) ans[i]=(*se.rbegin()),mid=temp;
	else ans[i]=num,mid=num;
	se.erase(se.find(ans[i])); in.insert(ans[i]);
}
void solve()
{
	mid=ans[1]=(*se.begin()); se.erase(se.find(ans[1]));
	in.insert(s[1]); in.insert(INF);
	for(int i=2;i<=n;i++)if(i&1) solve2(i); else solve1(i);
	for(int i=1;i<=n;i++) printf("%lld ",ans[i]); exit(0);
}
signed main()
{
	freopen("d.in","r",stdin); freopen("d.out","w",stdout);
	n=read(); for(int i=1;i<=n;i++) s[i]=read(),se.insert(s[i]);
	sort(s+1,s+n+1);
	for(int i=2;i<=n;i++) if(s[i]==s[i-1]){flag=true;break;}
	if(!flag) solve();
	if(n&1) mid=s[n/2+1];
	else mid=(1.0*s[n/2]+1.0*s[n/2+1])/2.0;
	int pos=upper_bound(s+1,s+n+1,mid)-s-1,r,poi=n,i;
	for(int i=2;i<=pos;i++) if(s[i]==s[i-1]){flag=false;break;}
	if(flag) solve();
	for(int i=pos-1;i>=1;i--)if(s[i]==s[i+1]){r=s[i];pos=i-1;break;}
	in.insert(r); in.insert(r); in.insert(INF); mid=r;
	se.erase(se.find(r)); se.erase(se.find(r));
	ans[++cnt]=r; ans[++cnt]=r;
	for(i=pos;i>=1&&poi>pos+2;i--)
	{
		ans[++cnt]=s[poi]; ans[++cnt]=s[i];
		se.erase(se.find(s[i])); se.erase(se.find(s[poi]));
		in.insert(s[i]); in.insert(s[poi]);
		poi--;
	}
	while(cnt<n) pos=*se.rbegin(),ans[++cnt]=pos,in.insert(pos),se.erase(se.find(pos));
	for(int i=1;i<=n;i++) printf("%lld ",ans[i]);
	return 0;
}
posted @ 2021-09-27 20:51  Varuxn  阅读(70)  评论(0编辑  收藏  举报