noip模拟20[玩具·y·z]

noip模拟20 solutions

这次考了105分,主要是第一题之前我做过但是没调出来,挂掉40pts,就挺伤心的,

最重要的是最后一题我现在还没调出来,aaa

以后一定有时间就调他,一定要调出来,补好这个坑!!!

·

T1 玩具

就这个题我一眼就知道是一棵树随意连边,求期望深度

第二眼就想起来我之前做过

第三眼就是我忘记咋做了

然后就苦思冥想,想也想不到哦啊。。。

最后还是有了一点点思路,可是有一个边界卡错了,最后还是只拿了暴力分

其实那也不是正解,只能拿到70pts;

\(O(n^4)\) 1节点原本就在那里,2节点一定会接在1节点上,所以我们所有的节点不是在1子树上就是在2子树上

我们分类讨论,先讨论最大深度在1这个子树上,在讨论在2子树上的时候

注意有可能1,2子树的最大深度相等,这种情况只能算进其中一种里,如果两种都算了这个那就多了

这是统计方案数,从之前的深度转移过来,乘上组合数

\(f[i][j]\)表示i个点组成的树最大深度为j的方案数,转移就是:

最大深度在1子树:

\(\huge f[i][j]=\sum\limits^{i-1}_{k=1}\sum\limits^{min(k-1,j-2)}_{x=1}f[k][j]×f[i-k][x]×C^{k-1}_{i-2}\)

为啥是这样,我将深度相等的情况放在下一种中处理,所以我们这个时候的最大值一定在1子树,所以f[k][j];

2子树的深度必须小于j-1,所以x从1循环到j-2,组合数代表从i-2个数中选k-1个因为1,2已经固定了

最大深度在2子树,1子树的最大深度可以等于2子树的:

\(\huge f[i][j]=\sum\limits^{i-1}_{k=1}\sum\limits^{min(k-1,j)}_{x=1}f[k][j-1]×f[i-k][x]×C^{k-1}_{i-2}\)

这个和上面是一样的。。。

70pts
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=205;
ll jc[N];
ll yh[N][N];
ll n,mod;
ll f[N][N];
ll ksm(ll x,ll y){
	ll ret=1;
	while(y){
		if(y&1)ret=ret*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return ret;
}
signed main(){
	scanf("%lld%lld",&n,&mod);
	jc[1]=1;for(re i=2;i<n;i++)jc[i]=jc[i-1]*i%mod;
	for(re i=1;i<=n;i++){
		yh[i][0]=1;
		yh[i][i]=1;
		for(re j=1;j<i;j++){
			yh[i][j]=(yh[i-1][j-1]+yh[i-1][j])%mod;
		}
	}
	f[1][0]=1;f[2][1]=1;
	for(re i=3;i<=n;i++){
		for(re j=1;j<i;j++){
			for(re k=1;k<=i-1;k++){//1
				for(re x=0;x<=min(k-1,j-2);x++){
					f[i][j]=(f[i][j]+f[k][j]*f[i-k][x]%mod*yh[i-2][k-1]%mod)%mod;
				}
			}
			for(re k=1;k<=i-1;k++){//2
				for(re x=0;x<=j;x++){
					f[i][j]=(f[i][j]+f[k][j-1]*f[i-k][x]%mod*yh[i-2][k-1]%mod)%mod;
				}
			}
		}
	}
	ll ans=0;
	for(re i=1;i<n;i++){
		ans=(ans+1ll*i*f[n][i]%mod)%mod;
	}
	ans=ans*ksm(jc[n-1],mod-2)%mod;
	printf("%lld",ans);
}

·

下面的才是正解,\(O(n^3)\),它这个思路极其的叼钻,极其的不好想,但是好理解

那么这个正解是如何得到的呢?

首先我们将这个问题转化为求每一种深度的概率,而这个概率可以用\(n^3\)来求

我们要找到一棵树深度为i的概率,想一想我们找不到所有树的形态,但是所有形态其实就是一个森林

当我们把所有的边都连上,他就变成了一颗树,而且这个过程中可以转移dp,就是正解的思路

这个让我明白了,整个不好求就拆开求

代码有细节,注意当深度大于节点数的时候要赋值为1

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=205;
ll n,mod,jc=1,ans;
ll inv[N];
ll dp[N][N],f[N][N],g[N][N];
ll ksm(ll x,ll y){
	ll ret=1;
	while(y){
		if(y&1)ret=ret*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return ret;
}
signed main(){
	scanf("%lld%lld",&n,&mod);
	inv[1]=1;for(re i=2;i<=n;i++){
		inv[i]=ksm(i,mod-2);
	}
	dp[1][1]=1;
	for(re i=2;i<=n;i++){
		//cout<<i<<"    ";
		for(re j=1;j<=i;j++){
			dp[i][j]=(dp[i-1][j-1]*(j-1)%mod*inv[i]%mod+dp[i-1][j]*(i-j)%mod*inv[i]%mod)%mod;
			//cout<<dp[i][j]<<" ";
		}
		//cout<<endl;
	}
	for(re i=0;i<=n;i++)f[1][i]=1;
	for(re i=0;i<=n;i++)g[0][i]=1;
	for(re i=1;i<=n;i++){
		for(re j=0;j<i;j++){
			for(re k=1;k<=i;k++){
				g[i][j]=(g[i][j]+f[k][j]*g[i-k][j]%mod*dp[i][k]%mod)%mod;
			}
			f[i+1][j+1]=g[i][j];
			for(re k=j+1;k<=n;k++)f[i+1][k]=f[i+1][j+1];
			if(j==i-1){
				for(re k=i;k<=n;k++)g[i][k]=g[i][j];
			}
			//f[i+1][j+1]=g[i][j];
		}
	}
	//cout<<f[1][1]<<" "<<f[2][1]<<endl;
	for(re i=1;i<n;i++){
		ans=(ans+i*(f[n][i]+mod-f[n][i-1])%mod)%mod;
		//cout<<f[n][i]<<endl;
	}
	//cout<<inv[jc]<<endl;
	printf("%lld",ans);
}

·

T2 y

这个题我搞到了60pts,如何搞到的,用trie树

我用trie树记录每一位后面目前有多少种,就是它的siz

还是利用dfs每扫到一条边,我们就把它加入到trie树中相应的位置,如果此时这个位置已经是一颗满二叉树了,就可以break

复杂度没法算,但是比一般的暴力快。。。

60pts
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=95;
int n,m,d;
int to[N*N*2],nxt[N*N*2],val[N*N*2],head[N],rp;
int sz[N];
void add_edg(int x,int y,int z){
	to[++rp]=y;
	val[rp]=z;
	nxt[rp]=head[x];
	head[x]=rp;
}
struct TRIE{
	int siz[1<<21],son[1<<21][2],fa[1<<21];
	int seg;
	TRIE(){seg=1;}
	void pushup(int x){
		if(!x)return ;
		siz[x]++;
		pushup(fa[x]);
	}
	int ins(int x,int v,int dep){
		if(siz[son[x][v]]==sz[dep])return 0;
		//cout<<"ins "<<x<<" "<<v<<" "<<dep<<endl;
		if(!son[x][v]){
			son[x][v]=++seg;
			fa[seg]=x;
		}
		if(dep==d){
			//cout<<"ok"<<" "<<x<<endl;
			siz[son[x][v]]=1;
			pushup(x);
		}
		return son[x][v];
	}
}t;
void dfs(int x,int f,int rt,int dep){
	if(dep>d)return ;
	//cout<<x<<" "<<f<<" "<<rt<<" "<<dep<<endl;
	for(re i=head[x];i;i=nxt[i]){
		int y=to[i];
		int pd=t.ins(rt,val[i],dep);
		if(!pd)continue;
		dfs(y,x,pd,dep+1);
	}
}
signed main(){
	scanf("%d%d%d",&n,&m,&d);
	int flag=0;
	for(re i=1,x,y,z;i<=m;i++){
		scanf("%d%d%d",&x,&y,&z);
		add_edg(x,y,z);
		add_edg(y,x,z);
		if(z!=0)flag=1;
	}
	if(m==0){
		printf("0");
		return 0;
	}
	for(re i=1;i<=d;i++){
		sz[i]=1<<(d-i);
	}
	//cout<<t.seg<<endl;
	dfs(1,0,1,1);
	printf("%d",t.siz[1]);
}

·

正解竟然是---深搜,没错就是dfs

设f[i][j][s]表示有一条从i出发,从j结束的,状态为s的路径,

我知道你会告诉我,第一维根本没有用,直接从1开始搜不就完事了,确实可以,可是你只有21pts

MEET IN THE MIDDLE这是个好思想,整个做不了就直接给它拆掉,我们重新定义一下数组,根据cty的思想

设f[i][j][s]表示走了i步,走到了j,状态为s

因为我们的起点是1,而终点没有要求,我们只需要枚举到\(\frac{d}{2}\)就好了,

具体是先给f[0][1][0]赋值为真,\(O(2^{\frac{d}{2}}nm)\)找到第\(\frac{d}{2}\)步的终点,

因为终点是没有要求的,第二次我们从后往前找,所有的f[0][i][0]都赋值为真,

所以最后我们三层循环\(O(2^dn)\)复杂度遍历一下就好了

不要用深搜,因为这是图,深搜的最劣复杂度为\(O(n^d)\),直接爆炸

为什么循环的复杂度较小??,因为有许多重复的状态都放到一起了

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int 
const int N=95;
int n,m,d;
int to[N*N*2],nxt[N*N*2],val[N*N*2],head[N],rp;
void add_edg(int x,int y,int z){
	to[++rp]=y;
	val[rp]=z;
	nxt[rp]=head[x];
	head[x]=rp;
}
int f[25][N][1<<12],f1[N][1<<12],f2[N][1<<12];
signed main(){
	scanf("%d%d%d",&n,&m,&d);
	for(re i=1,x,y,z;i<=m;i++){
		scanf("%d%d%d",&x,&y,&z);
		add_edg(x,y,z);
		add_edg(y,x,z);
	}
	int ans=0,nd=d+1>>1,vd=d>>1;
	//memset(f,-1,sizeof(f));
	f[0][1][0]=true;
	for(re i=0;i<nd;i++){
		for(re x=1;x<=n;x++){
			for(re j=head[x];j;j=nxt[j]){
				int y=to[j];
				for(re s=0;s<(1<<i);s++){
					f[i+1][y][(s<<1)|val[j]]|=f[i][x][s];
				}
			}
		}
	}
	for(re i=1;i<=n;i++){
		for(re s=0;s<(1<<nd);s++){
			f1[i][s]=f[nd][i][s];
			//if(f1[i][s])cout<<i<<" "<<s<<endl;
		}
	}
	memset(f,false,sizeof(f));
	for(re i=1;i<=n;i++)f[0][i][0]=true;
	for(re i=0;i<vd;i++){
		for(re x=1;x<=n;x++){
			for(re j=head[x];j;j=nxt[j]){
				int y=to[j];
				for(re s=0;s<(1<<i);s++){
					f[i+1][y][(s<<1)|val[j]]|=f[i][x][s];
				}
			}
		}
	}
	for(re i=1;i<=n;i++){
		for(re s=0;s<(1<<vd);s++){
			f2[i][s]=f[vd][i][s];
			//if(f2[i][s])cout<<i<<" "<<s<<endl;
		}
	}
	for(re i=0;i<(1<<nd);i++){
		for(re j=0;j<(1<<vd);j++){
			for(re k=1;k<=n;k++){
				if(f1[k][i]&&f2[k][j]){
					ans++;break;
				}
			}
		}
	}
	printf("%d",ans);
}

·

T3 z

这个我到如今还没有做,只会一个小暴力

大体思路我会了,但是这个是需要判断起始位置和终止位置的

这个判断极其的麻烦,

要用到map+priority_queue,调试过程会极其复杂,

我先放放,因为昨天做这个的时候做懵了。。。

写完啦写完啦

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
#define pa pair<int,int>
#define mpa(x,y) make_pair(x,y)
#define fi first
#define se second
const int N=1e5+5;
int n,Q;
int a[N],tas[N],cnt;
ll ans[N];
pa qus[N];
map<int,int> mp;
ll sum;
priority_queue<pa,vector<pa>,greater<pa> > q;
ll sol(ll x){
	//cout<<"sol"<<x<<" "<<sum<<endl;
	if(mp.empty())return sum;
	if(mp.begin()->se<0)return sum-(mp.size()-1)*x;
	return sum-mp.size()*x;
}
signed main(){
	scanf("%d%d",&n,&Q);
	for(re i=1;i<=n;i++)scanf("%d",&a[i]);
	for(re i=1;i<=Q;i++)scanf("%d",&qus[i].fi),qus[i].se=i;
	int typ=(a[1]>=0?0:1);
	for(re i=2;i<=n;i++){
		if(a[i]>a[i-1]&&typ==1)tas[++cnt]=a[i-1],typ^=1;
		if(a[i]<a[i-1]&&typ==0)tas[++cnt]=a[i-1],typ^=1;
	}
	tas[++cnt]=a[n];
	for(re i=1;i<=cnt;i++){
		sum+=abs(tas[i]-tas[i-1]);
		mp.insert(mpa(i,tas[i]-tas[i-1]));
		//cout<<mp[i]<<" ";
		q.push(mpa(abs(tas[i]-tas[i-1]),i));
	}
	//cout<<endl;
	sort(qus+1,qus+Q+1);
	int now=1;
	while(!q.empty()){
		int id=q.top().se,tmp=q.top().fi;q.pop();//find the minnest edge
		//cout<<"id:"<<" "<<id<<"tmp:"<<tmp<<endl;
		map<int,int>::iterator p=mp.lower_bound(id);
		//cout<<p->fi<<" "<<p->se<<endl;
		//cout<<mp[4]<<endl;
		if(p==mp.end()||p->fi!=id||abs(p->se)!=tmp)continue;//can't find
		//cout<<"sb"<<tmp<<endl;
		while(now<=Q&&qus[now].fi<tmp)ans[qus[now].se]=sol(qus[now].fi),now++;
		//cout<<now<<" "<<qus[now].fi<<" "<<tmp<<endl;
		if(p!=mp.begin()){
			if(p!=prev(mp.end())){
				tmp=p->se;sum-=abs(p->se);
				tmp+=prev(p)->se;sum-=abs(prev(p)->se);
				tmp+=next(p)->se;sum-=abs(next(p)->se);
				mp.erase(prev(p));mp.erase(next(p));
				sum+=abs(tmp);
				p->se=tmp;
				//cout<<p->fi<<" "<<p->se<<endl;
				q.push(mpa(abs(tmp),p->fi));
			}
			else {
				sum-=abs(p->se);
				mp.erase(p);
			}
		}
		else if(p->se>0){
			if(p!=prev(mp.end())){
				tmp=p->se;sum-=abs(p->se);
				tmp+=next(p)->se;sum-=abs(next(p)->se);
				mp.erase(next(p));
				if(tmp){// not 0
					p->se=tmp;sum+=abs(tmp);
					q.push(mpa(abs(tmp),p->fi));
				}
				else {//000000
					mp.erase(p);
				}
			}
			else {
				sum-=abs(p->se);
				mp.erase(p);
				break;
			}
		}	
	}
	//cout<<now<<" "<<sum<<" "<<prev(mp.end())->se<<endl;
	while(now<=Q)ans[qus[now].se]=sol(qus[now].fi),now++;
	for(re i=1;i<=Q;i++)printf("%lld\n",ans[i]);
}
posted @ 2021-07-21 10:42  fengwu2005  阅读(42)  评论(0编辑  收藏  举报