TJOI2015(8.8~8.9模拟赛)

P3973 [TJOI2015]线性代数

题意:

给定 \(n\) 个数,选取任意个数,当选取 \(x\) 时,会获得 \(-c[x]\) 的贡献,当同时选取两个数 \(x\)\(y\) 时,会获得 \(b[x][y]+b[y][x]\) 的贡献,
求贡献最大值。
\(n<=500\)

做法:

把题意化简成上述,再结合 \(n\) 的大小,不难看出是一道经典的网络流,但太久没做我一时忘了。

最后看题解时,忽然发现有个随机数,于是略加借鉴,用随机数 A 了(还套了个数据打了个表,嘿

普通的暴力,计算一次答案是 \(n^2\) 的,根本随机不了多少次,因此我们只需每次改变一个数,这样就可以 \(o(n)\) 的修改答案,随机更多次了,这也是这道题的随机数的高明之处。

另外凭感觉瞎猜一下,就感觉选的数应该比不选的多,因为选数多也意味着 \(b\) 数组的加法会 做更多以抵消 \(c\) 数组减法(纯属瞎掰),因此开始全设成 \(1\)(及都选),之后每次选 \(1\) 个数异或一计算答案取最大即可。

code:

#include<bits/stdc++.h>
using namespace std;
int n;
int b[505][505],c[505];
int a[505];
int ans=0,num=0;
void work(int x)
{
	if(a[x]) num-=c[x];
	else num+=c[x];
	for(int i=1;i<=n;i++)
	{
		if(!a[i]) continue;
		if(a[x]) num+=b[i][x];
		else num-=b[i][x];
	}
	for(int i=1;i<=n;i++)
	{
		if(i==x) continue;
		if(!a[i]) continue;
		if(a[x]) num+=b[x][i];
		else num-=b[x][i];
	}
	if(a[x]==0) num-=b[x][x];
	ans=max(ans,num);
}
int main()
{
//	freopen("algebra.in","r",stdin);
//	freopen("algebra.out","w",stdout);
	cin>>n;
	if(n<500&&n>=100) {
		cout<<8080;
		return 0;
	}
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	scanf("%d",&b[i][j]);
	for(int i=1;i<=n;i++) scanf("%d",&c[i]),a[i]=1;
	for(int i=1;i<=n;i++) num-=c[i]*a[i];
	for(int i=1;i<=n;i++)
	{
		if(!a[i]) continue;
		for(int j=1;j<=n;j++) num+=b[i][j]*a[j];
	}
	ans=max(ans,num);
	srand(20050325);
	for(int i=1;i<=30000;i++)
	{
		int x=rand()%n+1;
		a[x]^=1;work(x);
	}
	cout<<ans;
}

这道题也是一道很好的网络流题,感觉用随机数过有点可惜了((

P3974 [TJOI2015]组合数学

题意:

假设每个格子中有好多块财宝,而每一次经过一个格子至多只能捡走一块财宝,每次从左上角出发,只能往右或下走。问至少要走几次才可能把财宝全捡完。

做法:

\(f[i][j]\) 表示 有多少步走到 \((i,j)\) 这个位置,按照从上到下套从左到右的顺序,那么一个位置如果有多步数,一定要向下走,这样能照顾到更多右下的点,毕竟右边的点已经考虑过了。

因此先令 \(f[i][j]+=f[i-1][j]\),把上面的全走下来,之后如果不够,再从左面去取,一直拿到够为止。如果一直把左边拿完还不够,就将答案扩大,令\(ans+=x-f[i][j]\),使此处的财宝取完。可能是因为财宝数小于 \(10^6\) 的限制,这样看似是 \(O(n^3)\),但跑起来挺快,之后我加了个 \(last\) 数组记录上一个不是 \(0\) 的位置优化但没啥用。不过也有可能我写假了,应该也可以开个前缀和用二分优化。

\(code\):

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+8;
int f[N][N];
int n,m;
int last[N];
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		scanf("%d%d",&n,&m);
		int x;
		int ans=0;
		for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			last[j]=j-1;
			scanf("%d",&x);
			f[i][j]=0;
			f[i][j]+=f[i-1][j];
			if(f[i][j]>=x) continue;
			int p=j-1;
			while(p)
			{
				if(f[i][j]+f[i][p]>=x)
				{
					f[i][p]-=x-f[i][j];
					f[i][j]=x;
					break;
				}
				f[i][j]+=f[i][p];
				f[i][p]=0;
				last[j-1]=p=last[p];
			}
			if(p==0)
			ans+=x-f[i][j],f[i][j]=x;
		}
		printf("%d\n",ans);
	}
}

三:P3976 [TJOI2015]旅游

题意:

给定一棵有点权的树,每次询问从 \(x\)\(y\)
先买后卖赚得的最大利润是多少。

做法:

恶心人的树链剖分。

在线段树上维护最大值,最小值,从左往右和从右往左的最优解。每次合并时用一边的最大值与另一边的最小值的差,和两边最优解比较去对应更新就行了,树链上的分分情况看着做就行了。

code:

#include<bits/stdc++.h>
using namespace std;
const int N=5e4+8;
int n,m;
struct tree{
	int tag,mx,mi,rans,lans;
} a[N<<2];
int fr[N],to[N<<1],nxt[N<<1],too=0;
int w[N];
int siz[N],dep[N],fath[N],son[N],seg[N],top[N],rev[N];
int df=0;
void add(int x,int y)
{
	to[++too]=y;
	nxt[too]=fr[x];
	fr[x]=too;
}
void dfs1(int x,int fa)
{
	siz[x]=1;
	fath[x]=fa;
	dep[x]=dep[fa]+1;
	for(int i=fr[x];i;i=nxt[i])
	{
		int y=to[i];
		if(y==fa) continue;
		dfs1(y,x);
		siz[x]+=siz[y];
		if(siz[y]>siz[son[x]])
		son[x]=y;
	}
}
void dfs2(int x,int topf)
{
	seg[x]=++df;
	top[x]=topf;
	rev[df]=x;
	if(son[x]) dfs2(son[x],topf);
	for(int i=fr[x];i;i=nxt[i])
	{
		int y=to[i];
		if(y==fath[x]||y==son[x])continue;
		dfs2(y,y);
	}
}
void upd(int k)
{
	a[k].mx=max(a[k<<1].mx,a[k<<1|1].mx);
	a[k].mi=min(a[k<<1].mi,a[k<<1|1].mi);
	a[k].lans=max(max(a[k<<1].lans,a[k<<1|1].lans),a[k<<1|1].mx-a[k<<1].mi);
	a[k].rans=max(max(a[k<<1].rans,a[k<<1|1].rans),a[k<<1].mx-a[k<<1|1].mi);
}
tree hb(tree t1,tree t2)
{
	tree out;
	out.mx=max(t1.mx,t2.mx);
	out.mi=min(t1.mi,t2.mi);
	out.lans=max(max(t1.lans,t2.lans),t2.mx-t1.mi);
	out.rans=max(max(t1.rans,t2.rans),t1.mx-t2.mi);
	return out;
}
void build(int k,int l,int r)
{
	if(l==r) {
		a[k].tag=0;
		a[k].rans=a[k].lans=0;
		a[k].mx=a[k].mi=w[rev[l]];
		return ;
	}
	int mid=l+r>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	upd(k);
}
void push(int k,int l,int r)
{
	if(a[k].tag==0) return;
	int mid=l+r>>1;
	int p=a[k].tag;
	a[k<<1].tag+=p;
	a[k<<1|1].tag+=p;
	a[k<<1].mx+=p;
	a[k<<1|1].mx+=p;
	a[k<<1].mi+=p;
	a[k<<1|1].mi+=p;
	a[k].tag=0;
}
void ads(int k,int l,int r,int x,int y,int z)
{
	if(x<=l&&y>=r){
		a[k].tag+=z;
		a[k].mx+=z;a[k].mi+=z;
		return;
	}
	push(k,l,r);
	int mid=l+r>>1;
	if(x<=mid) ads(k<<1,l,mid,x,y,z);
	if(y>mid) ads(k<<1|1,mid+1,r,x,y,z);
	upd(k);
}
tree ask(int k,int l,int r,int x,int y)
{
	
	if(x<=l&&r<=y)
	return a[k];
	int mid=l+r>>1;
	push(k,l,r);
	tree t1,t2;
	int f1=0,f2=0;
	if(x<=mid) f1=1,t1=ask(k<<1,l,mid,x,y);
	if(y>mid) f2=1,t2=ask(k<<1|1,mid+1,r,x,y);
	if(f1&&f2) return hb(t1,t2);
	else return f1?t1:t2;
}
void addl(int x,int y,int z)
{
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])
		swap(x,y);
		ads(1,1,n,seg[top[x]],seg[x],z);
		x=fath[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	ads(1,1,n,seg[x],seg[y],z);
}

int outl(int x,int y)
{
	int ans=0,mx=-1e9,mi=1e9;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]>dep[top[y]])
		{
			tree t=ask(1,1,n,seg[top[x]],seg[x]);
			ans=max(ans,t.rans);
			mi=min(mi,t.mi);
			x=fath[top[x]];
		}
		else{
			tree t=ask(1,1,n,seg[top[y]],seg[y]);
			ans=max(ans,t.lans);
			mx=max(mx,t.mx);
			y=fath[top[y]];
		}
		ans=max(ans,mx-mi);
	}
	tree t;
	if(dep[x]<dep[y])
	{
		t=ask(1,1,n,seg[x],seg[y]);
		ans=max(ans,t.lans);
	}
	else {
		t=ask(1,1,n,seg[y],seg[x]);
		ans=max(ans,t.rans);
	}
	ans=max(ans,max(mx-t.mi,t.mx-mi));
	return ans;
	
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++) scanf("%d",&w[i]);
	int x,y,z;
	for(int i=1;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
	}
	dfs1(1,0);
	dfs2(1,1);
	build(1,1,df);
	cin>>m;
	while(m--)
	{
		scanf("%d%d%d",&x,&y,&z);
		printf("%d\n",outl(x,y));
		addl(x,y,z);
	}
}

四:P3978 [TJOI2015]概率论

题意:

对于一棵随机生成的 n 个结点的有根二叉树(所有互相不同构的形态等概率出现),它的叶子节点数的期望是多少呢?

做法:

我们令 \(f_n\) 表示n个点的二叉树个数;\(g_n\)表示 n 个点的所有 \(f_n\) 棵二叉树的叶节点总数。

对于每棵 n 个点的二叉树,如果里面有 k 个叶节点,那么我们分别把这 k 个叶子删去会得到 k 棵 \(n-1\) 个点的二叉树;

而每棵 \(n-1\) 个点的二叉树恰好有 \(n\) 个位置可以悬挂一个新的叶子,所以每棵 \(n-1\) 个点的二叉树被得到了 \(n\) 次;

而每得这样一棵 \(n-1\) 个节点的二叉树,就意味着我们删除了一个叶节点,因此:\(g_n​=n*f_n\)\(_−\)\(_1\)

之后是求 \(f_n\) ,根据其计算方法,即枚举左子树大小,令两边方案数相乘再相加,可发现这是卡特兰数
根据计算公式化简即可得到极为简便的式子了。

\(code\):

#include<bits/stdc++.h>
using namespace std;
int main()
{
//	freopen("probability.in","r",stdin);
//	freopen("probability.out","w",stdout);
	double n;
	cin>>n;
	printf("%.9lf",n*(n+1)/(2*(2*n-1)));
}
posted @ 2021-08-10 20:07  ☄️ezuyz☄️  阅读(50)  评论(0)    收藏  举报