noip模拟测试17

由于玄学错误以及各种挂分,这场考试连100都没上……

A. 序列

玄学思路可以蹭过去,但是log函数在long long的情况下异常玄学,下面这段代码在Windows下运行结果为2,Linux下正确

#include<bits/stdc++.h>
using namespace std;
#define int long long
signed main(){
	int val=8,p=2;
	int c=log(val)/log(p);
	cout<<c;
	return 0;
}

所以还是少用内置函数比较好……

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=2e5+5;
int n,a[maxn],b[maxn],lsh[maxn],ans,mx,last[maxn],pos[maxn];
int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=x*10+ch-48;
		ch=getchar();
	}
	return x*f;
}
int po(int a,int b){
	int ans=1;
	while(b){
		if(b&1)ans=ans*a;
		a=a*a;
		b>>=1;
	}
	return ans;
}
signed main(){
//	freopen("shuju.in","r",stdin);
//	freopen("my.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++){
		lsh[i]=a[i]=read();
		mx=max(mx,a[i]);
	}
	sort(lsh+1,lsh+n+1);
	int cnt=unique(lsh+1,lsh+n+1)-lsh-1;
	for(int i=1;i<=n;i++){
		b[i]=lower_bound(lsh+1,lsh+cnt+1,a[i])-lsh;
	}
	for(int i=1;i<=n;i++){
		last[i]=pos[b[i]];
		pos[b[i]]=i;
	}
	int sum=0;
	for(int i=1;i<=n;i++){
		if(a[i]==a[i-1])sum++;
		else sum=1;
		ans=max(ans,sum);
	}
	for(int j=2;j<=min(mx,1000ll);j++){
		sum=1;
//		double p=log(j);
		for(int i=2;i<=n;i++){
			if(a[i]==a[i-1]){
				sum=1;
				continue;
			}
			int val;
			if(a[i]>a[i-1]){
				val=a[i]/a[i-1];
				if(a[i-1]*val!=a[i]){
					sum=1;
					continue;
				}
			}
			else{
				val=a[i-1]/a[i];
				if(a[i]*val!=a[i-1]){
					sum=1;
					continue;
				}
			}
//			if(j==2)cout<<i<<" "<<val<<" "<<j<<" ";
			int pp;
			for(int k=1;k<=63;k++){
				if(po(j,k)>=val||po(j,k)<0){
					pp=k;
					break;
				}
			}
//			int pp=log(val)/log(j);
//			if(j==2)cout<<pp<<" ";
			if(po(j,pp)==val){
				sum++;
			}
			else sum=1;
			if(i-sum+1<=last[i])sum=i-last[i];
			ans=max(ans,sum);
//			if(j==2)cout<<sum<<endl;
		}
	}
	cout<<ans;
	return 0;
}

B. 熟练剖分(tree)

树形dp神题

首先转化一下思想,从上往下找没前途(就像我),可以从下往上枚举计算
因为最后算期望,所以可以统计出每种长度链的概率再乘链的长度即可
\(f_{ij}\) 表示第 \(i\) 个节点的子树中节点到这个节点的距离为 \(j\) 的概率是多少

考虑转移:
可以枚举每一个儿子节点钦定为重儿子记为 \(v\),分别计算这时的概率贡献
可以再枚举每一个儿子记为 \(z\),这时分为两种情况:

  • \(v=z\), 长度为 \(k\) 的链的来源有两个,要么是这只重儿子的长度恰好为 \(k\), 且其他儿子小于等于 \(k\); 要么是这只重儿子的长度小于等于 \(k\), 且其他儿子的长度有等于 \(k\) 的。
  • \(v\ne u\), 来源也有两个,要么这只轻儿子的长度恰好为 \(k-1\), 而其他儿子的长度都小于等于 \(k\), 反之同理

由于要处理当前节点其他所有儿子的情况,所以可以开一个 \(g_u\) 数组储存
由于处理的信息存在“小于等于”,可以将数组的改为前缀和的存储方式

#include<bits/stdc++.h>
using namespace std;
const int maxn=3005;
const int maxm=3005;
const int mod=1e9+7;
int n,num[maxn],cnt,hd[maxn],f[maxn][maxn],g[maxn],h[maxn],ans,siz[maxn],x,root;
bool flag[maxn];
int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=x*10+ch-48;
		ch=getchar();
	}
	return x*f;
}
struct Edge{
	int nxt,to;
}edge[maxm];
void add(int u,int v){
	edge[++cnt].nxt=hd[u];
	edge[cnt].to=v;
	hd[u]=cnt;
	return ;
}
int po(int a,int b=mod-2){
	int ans=1;
	while(b){
		if(b&1)ans=1ll*a*ans%mod;
		a=1ll*a*a%mod;
		b>>=1;
	}
	return ans;
}
void dfs(int u,int father){
	siz[u]=1;
	for(int i=hd[u];i;i=edge[i].nxt){
		int v=edge[i].to;
		if(v==father)continue;
		dfs(v,u);
		siz[u]+=siz[v];
	}
	int inv=po(num[u]);
	for(int i=hd[u];i;i=edge[i].nxt){
		int v=edge[i].to;
		if(v==father)continue;
		for(int j=0;j<=n;j++)g[j]=1;
		for(int j=hd[u];j;j=edge[j].nxt){
			int z=edge[j].to;
			if(z==father)continue;
			for(int k=0;k<=siz[z]+1;k++){
				int other1,other;
				other1=other=g[k];
				if(k)other-=g[k-1];
				if(z==v){
					int own1,own;
					own1=own=f[z][k];
					if(k)own-=f[z][k-1];
					h[k]=((1ll*other*own1%mod+1ll*own*other1%mod-1ll*own*other%mod)%mod+mod)%mod;
				}
				else{
					int own1,own;
					own1=own=f[z][k-1];
					if(k>1)own-=f[z][k-2];
					h[k]=((1ll*other*own1%mod+1ll*own*other1%mod-1ll*other*own%mod)%mod+mod)%mod;
				}
			}
			g[0]=h[0];
			h[0]=0;
			for(int k=1;k<=siz[z]+1;k++){
				g[k]=(g[k-1]+h[k])%mod;
				h[k]=0;
			}
		}
		for(int j=siz[u];j>=1;j--){
			g[j]=(g[j]-g[j-1]+mod)%mod;
		}
		for(int j=0;j<=siz[u];j++){
			f[u][j]=(f[u][j]+1ll*g[j]*inv%mod)%mod;
		}
	}
	if(!hd[u])f[u][0]=1;
	for(int i=1;i<=siz[u]+1;i++){
		f[u][i]=(f[u][i]+f[u][i-1])%mod;
	}
	return ;
}
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		num[i]=read();
		for(int j=1;j<=num[i];j++){
			x=read();
			flag[x]=true;
//			flag[i]=true;
			add(i,x);
		}
	}
	for(int i=1;i<=n;i++){
		if(!flag[i]){
			root=i;
			break;
		}
	}
	dfs(root,0);
	for(int i=1;i<=n;i++){
		ans=(ans+1ll*i*(f[root][i]-f[root][i-1])%mod)%mod;
	}
	cout<<(ans%mod+mod)%mod;
	return 0;
}

C. 建造游乐园(play)

组合推公式

HKH巨佬凭借超强的猜想能力与数学天赋A掉了这道题%%%

首先,这道题如果被题面迷惑住会连暴力都不会打的(像我一样
一次操作可以反向来考虑,对于一个欧拉图删去一条边或加上一条边形成的图就是满足题意的图了(因为欧拉一个点度数至少为2,删去边后不可能孤立),而对于一个 \(n\) 个点的欧拉图删边共有 \(C_{n}^{2}\) 种方案(即n个点中任选2个断边或连边)

于是问题转化为求 \(n\) 个点的欧拉图的数量。
考虑欧拉图的性质,欧拉图为所有点度数为偶数的连通图
那么可以所有点度数为偶数的图减去不连通的图即可

\(i\) 个点总的偶图的数量为 \(g_i\),欧拉图的数量 \(f_i\)
那么 \(g_i=2^{C_{n-1}^{2}}\), 可以这么理解:先单独选出一个点来补漏,剩下 \(i-1\) 个点随意建图,则单独的点总可以通过向奇数度数的点连边而补成偶数度数的
再来看 \(f_i\) 的转移:首先总共的图为 \(g_i\), 而不连通的图可以这样考虑:先选出一个 \(j\) 个点的欧拉图,剩下的 \(i-j\) 个点为随意偶图,于是转移为:

\(f_i=g_i-\sum_{j=1}^{i-1} f_j*g_{i-j}*C_{i-1}^{j-1}\)

其中C的部分表示编号的轮换

时间复杂度 \(n^2\)
貌似类似于 城市规划 可以用FFT优化至 \(nlogn\)

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=2005;
const int mod=1e9+7;
int n,c[maxn][maxn],g[maxn],f[maxn];
int po(int a,int b){
	int ans=1;
	while(b){
		if(b&1)ans=1ll*ans*a%mod;
		a=1ll*a*a%mod;
		b>>=1;
	}
	return ans;
}
signed main(){
	cin>>n;
	c[0][0]=1;
	for(int i=1;i<=n;i++){
		c[i][0]=1;
		for(int j=1;j<=n;j++){
			c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
		}
	}
	for(int i=1;i<=n;i++){
		g[i]=po(2,c[i-1][2]);
		int sum=0;
		for(int j=1;j<=i-1;j++){
			sum=(sum+1l*f[j]*g[i-j]%mod*c[i-1][j-1]%mod)%mod;
		}
		f[i]=(g[i]-sum+mod)%mod;
	}
	cout<<1ll*f[n]*c[n][2]%mod;
	return 0;
}
posted @ 2021-05-02 11:22  y_cx  阅读(101)  评论(0编辑  收藏  举报