【LGR-122】T1 & T2 题解

T1

下面所说的做法来自于 dty(%%%%%%%%%)

注意到每一个数的绝对值是大于等于 \(2\) 的,那么就可以发现一个性质:当一个区间长度为 \(len\) 时,如果 \(len\ge 62\),那么这个区间的答案一定会是 Too large,假设这个区间存在一个负数,那么当这个负数处于最中间的时候才会使的两边区间乘积的最大值最小,也就是说只要半个区间的乘积是大于 \(2^{30}\),那么这个区间就是 Too large。再根据绝对值大于等于 \(2\) 这一条件,可以得知需要计算答案的区间长度一定是在 \(62\) 以内的。这样每一次询问的规模就被降到了 \(1e2\) 以内,暴力处理即可。

对于暴力,其实也可以发现有着最大的乘积的区间一定是至少包含这个区间的一个端点的(分一下类很好证明)。所以直接以区间的左右端点为起点扫一下就知道了。

对于代码实现的时候有一些需要注意的点:

  • 如果计算乘积的过程中乘积已经大于了 \(2^{30}\),那么直接退出即可

  • 如果此时的乘积是小于 \(-2^{30}\) 的,那么需要继续向后找,找到是否还存在负数,如果有,那么负负得正,一定会 Too large。如果不进行这个处理,很有可能在负数的时候炸了 long long

Code

#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof a)
#define int long long
using namespace std;
template<typename T> void read(T &k)
{
	k=0;T flag=1;char b=getchar();
	while (!isdigit(b)) {flag=(b=='-')?-1:1;b=getchar();}
	while (isdigit(b)) {k=k*10+b-48;b=getchar();}
	k*=flag;
}
template<typename T> void write(T k) {if (k<0) {putchar('-'),write(-k);return;}if (k>9) write(k/10);putchar(k%10+48);}
template<typename T> void writewith(T k,char c) {write(k);putchar(c);}
const int _SIZE=2e5;
int n,q,a[_SIZE+5];
int res=1,temp=1;
signed main()
{
	//freopen("T1ex2.in","r",stdin);
	//freopen("T1ex2.ans","w",stdout);
	read(n),read(q);
	for (int i=1;i<=n;i++) read(a[i]);
	for (int i=1;i<=q;i++)
	{
		int opt,x,y;read(opt),read(x),read(y);
		if (opt==1) a[x]=y;
		else
		{
			if (y-x+1>=65) {puts("Too large");continue;}
			temp=1,res=1;
			bool flag=0;
			for (int i=x;i<=y;i++)
			{
				temp*=a[i];
				if (temp<-(1ll<<30)) 
				{
					for (int j=i+1;j<=y;j++) if (a[j]<0) {puts("Too large");flag=1;break;}
					break;
				}
				if (temp>(1ll<<30)) {puts("Too large");flag=1;break;}
				res=max(res,temp);
			}
			if (flag) continue;
			temp=1;
			for (int i=y;i>=x;i--)
			{
				temp*=a[i];
				if (temp<-(1ll<<30)) 
				{
					for (int j=i-1;j>=x;j--) if (a[j]<0) {puts("Too large");flag=1;break;}
					break;
				}
				if (temp>(1ll<<30)) {puts("Too large");flag=1;break;}
				res=max(temp,res);
			}
			if (!flag) writewith(res,'\n');
		}
	}
	return 0;
}

T2

一眼树形 \(\text{DP}\)

\(dp[i][j]\) 表示以 \(i\) 为根节点的子树中删除掉 \(j\) 个节点的最小代价,那么可以有这样的转移方程:

\[dp[i][j]=\min\limits_{s\in son[i]}\{dp[i][k]+dp[s][j-k]\} \]

需要注意的是,这个方程中用到的 \(dp[s][j-k]\) 一定是当前儿子删的只剩儿子这一个节点的,也就是说 \(dp[i][j]\) 没办法更新到 \(dp[i][size-1]\) 这一个位置,即没法把这个子树删的只剩子树的根,所以需要额外补一个用于转移 \(dp[i][size-1]\) 的方程:

\[dp[i][size-1]=\min\{dp[i][j]+f[size-j]\} \]

这样就完事了。

代码有些细节需要注意。

Code

#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof a)
#define int long long
using namespace std;
template<typename T> void read(T &k)
{
	k=0;T flag=1;char b=getchar();
	while (!isdigit(b)) {flag=(b=='-')?-1:1;b=getchar();}
	while (isdigit(b)) {k=k*10+b-48;b=getchar();}
	k*=flag;
}
template<typename T> void write(T k) {if (k<0) {putchar('-'),write(-k);return;}if (k>9) write(k/10);putchar(k%10+48);}
template<typename T> void writewith(T k,char c) {write(k);putchar(c);}
const int _SIZE=5e3;
int n;
struct EDGE{
	int nxt,to;
}edge[(_SIZE<<1)+5];
int tot,head[_SIZE+5];
void AddEdge(int x,int y) {edge[++tot]=(EDGE){head[x],y};head[x]=tot;}
int f[_SIZE+5],dp[_SIZE+5][_SIZE+5],siz[_SIZE+5];
void dfs(int x,int fa)
{
	dp[x][0]=0,siz[x]=1;
	for (int i=head[x];i;i=edge[i].nxt)
	{
		int twd=edge[i].to;
		if (twd==fa) continue;
		dfs(twd,x);
		for (int j=siz[x]+siz[twd]-1;j>0;j--)
			for (int k=max(j-siz[x],1ll);k<=j && k<siz[twd];k++)
				dp[x][j]=min(dp[x][j],dp[twd][k]+dp[x][j-k]);
		siz[x]+=siz[twd];//如果把这句放在dfs(twd,x)之后,就表示x删点也删到了twd内,而k又再次计算了这一部分,所以应该算完了dp数组才更新siz[x]
	}
	for (int j=0;j<siz[x]-1;j++)
		dp[x][siz[x]-1]=min(dp[x][siz[x]-1],dp[x][j]+f[siz[x]-j]);
}
signed main()
{
	mem(dp,0x3f);
	read(n);
	for (int i=2;i<=n;i++) read(f[i]);
	for (int i=1;i<n;i++)
	{
		int u,v;read(u),read(v);
		AddEdge(u,v);AddEdge(v,u);
	}
	dfs(1,0);
	writewith(dp[1][siz[1]-1],'\n');
	return 0;
}

posted @ 2022-10-05 14:17  Hanx16Msgr  阅读(28)  评论(0编辑  收藏  举报