20240609比赛总结

因为很多奇奇怪怪的问题,成功被小学生爆杀

T1 小奇挖矿2

https://gxyzoj.com/d/hzoj/p/767

最简单的方法,枚举m,然后暴力求解,显然会T

可以发现,当飞船停在有矿的点时,才会有贡献,所以可以枚举n

经过列举,可以得到当两个点的距离大于17时,就一定可以转移成功,所以可以开一个数组,记录到每个点之前的所有点的最大值,满足条件则直接转移

对于其他部分,则暴力判断是否能转移即可

代码:

#include<cstdio>
#include<algorithm>
using namespace std;
const int inf=1e9+7;
int n,m,a[100005],b[100005],f[100005],ans,mx[100005];
struct node{
	int a,b;
}x[100005];
bool cmp(node x,node y)
{
	return x.b<y.b;
}
int lst[100005];
bool fl[105];
void solve2()
{
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&x[i].a,&x[i].b);
	}
	fl[0]=fl[4]=fl[7]=1;
	for(int i=8;i<=100;i++)
	{
		fl[i]=fl[i-4]|fl[i-7];
	}
	sort(x+1,x+n+1,cmp);
	lst[1]=1;
	for(int i=1;i<=n;i++)
	{	
		int j=i-1;
		for(;j>=0;j--)
		{
			int p=x[i].b-x[j].b;
			if(p>18) break;
			if(p>=18||fl[p])
			{
				f[i]=max(f[i],f[j]+x[i].a);
				lst[i]=j;
			}
		}
		if(x[i].b<18&&!fl[x[i].b]) continue;
		f[i]=max(mx[j]+x[i].a,f[i]);
		mx[i]=max(mx[i-1],f[i]);
		ans=max(ans,f[i]);
	}
	printf("%d",ans);
}
int main()
{
	scanf("%d%d",&n,&m);
	solve2();
	return 0;
}

T2 小奇的矩阵(matrix)

可以直接把平均数替换为\(\dfrac{\sum A_i}{n+m-1}\),得到:

\[\begin{aligned} V &= (n+m-1)\sum_{i-1}^{n+m-1} (A_i+\dfrac{\sum A_i}{n+m-1})^2\\ &= (n+m-1)\sum_{i-1}^{n+m-1} (A_i^2-2\times A_i\times \dfrac{\sum A_i}{n+m-1}+(\dfrac{\sum A_i}{n+m-1})^2)\\ &= (n+m-1)\sum_{i-1}^{n+m-1} A_i^2-2(\sum A_i)^2+(\sum A_i)^2 \\ &=(n+m-1)\sum A_i^2-(\sum A_i)^2 \end{aligned} \]

因为\(A_i\)的范围很小,所以直接枚举即可

此时,可以直接用一个三维的dp记录最小的平方和,然后再与和的平方相加即可

代码:

#include<cstdio>
#include<algorithm>
using namespace std;
const int inf=1e9;
int T,n,m,a[35][35],dp[35][35][1805];
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=m;j++)
			{
				scanf("%d",&a[i][j]);
			}
		}
		for(int i=0;i<=n;i++)
		{
			for(int j=0;j<=m;j++)
			{
				for(int k=0;k<=(n+m-1)*30;k++)
				{
					dp[i][j][k]=inf;
				}
			}
		}
		dp[1][1][a[1][1]]=a[1][1]*a[1][1];
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=m;j++)
			{
				if(i==1&&j==1) continue;
				for(int k=a[i][j];k<=(n+m-1)*30;k++)
				{
					dp[i][j][k]=min(dp[i][j][k],min(dp[i-1][j][k-a[i][j]],dp[i][j-1][k-a[i][j]])+a[i][j]*a[i][j]);
				}
			}
		}
		int ans=inf;
		for(int i=1;i<=(n+m-1)*30;i++)
		{
			ans=min(1ll*ans,1ll*dp[n][m][i]*(n+m-1)-i*i);
		}
		printf("%d\n",ans);
	}
	return 0;
}

T3 小奇的仓库(warehouse)

https://gxyzoj.com/d/hzoj/p/769

可以发现,因为m<16,所以至多只有四位会发生改变,前面的不会变

所以前面的直接暴力树形dp,后面的四位则另外开一个数组记录,再转移的过程中累加即可

最后百里计算最后四位有没有值即可

代码:

#include<cstdio>
using namespace std;
int n,m,edgenum,head[100005];
struct edge{
	int to,nxt,val;
}e[200005];
void add_edge(int u,int v,int w)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	e[edgenum].val=w;
	head[u]=edgenum;
}
int size[100005],f1[100005],d[100005][20],f2[100005];
int id(int x)
{
	return (x%16+16)%16;
}
void dfs(int u,int fa)
{
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to,w=e[i].val;
		if(v==fa) continue;
		dfs(v,u);
		size[u]+=size[v];
		f1[u]+=f1[v]+w*size[v];
		for(int j=0;j<16;j++)
		{
			d[u][id(j+w)]+=d[v][j];
		}
	}
	d[u][0]++;
	size[u]++;
}
int p[100005][20];
void dfs2(int u,int fa)
{
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to,w=e[i].val;
		if(v==fa) continue;
		f2[v]=f2[u]+(n-2*size[v])*w;
		for(int j=0;j<16;j++)
		{
			p[v][id(j+w)]=p[u][j]-d[v][id(j-w)]+d[v][id(j+w)];
		}
		dfs2(v,u);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add_edge(u,v,w);
		add_edge(v,u,w);
	}
	dfs(1,0);
	for(int i=0;i<16;i++)
	{
		p[1][i]=d[1][i];
	}
	f2[1]=f1[1];
	dfs2(1,0);
	for(int i=1;i<=n;i++)
	{
	//	printf("%d\n",f2[i]);
		int sum=0,a1=0,a2=0,a3=0,a4=0;
		for(int j=0;j<16;j++)
		{
			sum+=p[i][j]*j;
			if(j&1) a1+=p[i][j];
			if(j&2) a2+=p[i][j];
			if(j&4) a3+=p[i][j];
			if(j&8) a4+=p[i][j];
		}
	//	printf("%d %d %d %d\n",a1,a2,a3,a4); 
		if(m&1) a1=n-a1-1;
		if(m&2) a2=n-a2-1;
		if(m&4) a3=n-a3-1;
		if(m&8) a4=n-a4-1;
		f2[i]=f2[i]-sum+a1*1+a2*2+a3*4+a4*8;
		printf("%d\n",f2[i]);
	}
	return 0;
}

[COCI2014-2015#1] Kamp

https://gxyzoj.com/d/hzoj/p/3720

出了道原题……

显然,如果回到原点,那么每个直接或间接连接根和关键点的边必然会走两次,所以可以先树形dp求出从1号点出发的路程,再进行up and down求出其他点

接下来考虑不回原点的情况,可以分为两种情况,一种是少走子树内一条链,二是少走子树外一条链

对于第一种情况,直接从儿子向父亲转移子树内最长链

对于第二种情况,分为两部分,一部分是有fa的子树外直接转移,另一部分则是fa除u外的其他子树的最长链

因为最长链可能在u的子树内,所以要记录次长链

代码:

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
int n,k,head[500006],edgenum;
struct edge{
	int to,nxt;
	ll val;
}e[1000006];
void add_edge(int u,int v,ll w)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	e[edgenum].val=w;
	head[u]=edgenum;
}
int size[500005],d[500005];
ll dp[500005],f1[500005],f2[500005],g[500005],dp2[500005];
void dfs1(int u,int fa)
{
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to,w=e[i].val;
		if(v==fa) continue;
		dfs1(v,u);
		size[u]+=size[v];
		if(size[v])
		{
			dp[u]+=dp[v]+e[i].val;
			if(f1[u]<=f1[v]+e[i].val)
			{
				g[u]=f1[u];
				f1[u]=f1[v]+e[i].val;
				d[u]=v;
			}
			else if(g[u]<=f1[v]+e[i].val)
			{
				g[u]=f1[v]+e[i].val;
			}
		}
	}
}
void dfs2(int u,int fa)
{
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to,w=e[i].val;
		if(v==fa) continue;
		if(k-size[v])
		{
			dp2[v]=dp2[u]+dp[u]-dp[v];
			if(!size[v]) dp2[v]+=e[i].val;
			f2[v]=f2[u]+e[i].val;
			if(v==d[u])
			{
				f2[v]=max(g[u]+e[i].val,f2[v]);
			}
			else
			{
				f2[v]=max(f1[u]+e[i].val,f2[v]);
			}
		}
		dfs2(v,u);
	}
}
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<n;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add_edge(u,v,w);
		add_edge(v,u,w);
	}
	for(int i=1;i<=k;i++)
	{
		int x;
		scanf("%d",&x);
		size[x]=1;
	}
	dfs1(1,0);
	dfs2(1,0);
	for(int i=1;i<=n;i++)
	{
		printf("%lld\n",dp[i]*2+dp2[i]*2-max(f1[i],f2[i]));
//		printf("%lld %lld %lld %lld\n",dp[i],dp2[i],f1[i],f2[i]);
	}
	return 0;
}
posted @ 2024-06-09 18:00  wangsiqi2010916  阅读(17)  评论(1编辑  收藏  举报