2019 南昌邀请赛网络赛题解

D. Match Stick Game

题意:有一堆火柴构成了一个加减法式子,你可以把火柴重新组合,要求数字个数和原来一样多,每个数字的位数和对应原数字位数一样多,总火柴数量也一样多,要求你构造新的式子算出来的结果最大。

思路:设mx[ i ][ j ]为用了 j 根火柴组成了最大的 i 位数,mn[ i ][ j ]为最小值,这两个数组用dp预处理随便搞一搞就行,然后我们求出来原式子的所有火柴为m,数字个数为k,设d[ i ][ j ]为算到了第 i 个数字用了 j 根火柴所得到最大的值,很显然对于d[ i + 1 ][ x ]只有两个转移方程,假设第 i + 1 个数有x位数字,枚举火柴数量 p 所能组成的最大数,用加法: d[ i + 1 ][ j + p + 2] = max(d[ i + 1 ][ j + p + 2] , d[ i ][ j ] + mx[ x ][ p ]),用减法:你来搞定吧。那么这题就结束了。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll mn[11][1000],mx[11][1000],inf=1e18;
int a[11]={6,2,5,5,4,5,6,3,7,6};
void init()
{
	for(int i=0;i<11;i++)
	for(int j=0;j<1000;j++)
	mx[i][j]=-1,mn[i][j]=1e18;
	mn[0][0]=mx[0][0]=0;
	for(int i=0;i<10;i++)
	for(int j=0;j<800;j++)
	if(mx[i][j]!=-1)
	{
		for(int k=1;k<=9;k++)
		{
			mn[i+1][j+a[k]]=min(mn[i+1][j+a[k]],mn[i][j]*10+k);
			mx[i+1][j+a[k]]=max(mx[i+1][j+a[k]],mx[i][j]*10+k);
		}
		int k=0;
		if(mn[i][j]!=0)
		mn[i+1][j+a[k]]=min(mn[i+1][j+a[k]],mn[i][j]*10+k);
		if(mx[i][j]!=0)
		mx[i+1][j+a[k]]=max(mx[i+1][j+a[k]],mx[i][j]*10+k);
	}
	mn[1][6]=0;
}
ll d[101][10001];
int b[101];
int main()
{
	init();
	int T;
	scanf("%d",&T);
	while(T--)
	{
		int n,m=0,k=1;
		char s[101];
		for(int i=1;i<100;i++)b[i]=0;
		scanf("%d%s",&n,s+1);
		for(int i=1;i<=n;i++)
		{
			if(s[i]=='+')m+=2,k++;
			else if(s[i]=='-')m++,k++;
			else m+=a[s[i]-'0'],b[k]++;
		}
		for(int i=0;i<=k;i++)
		for(int j=0;j<=m;j++)d[i][j]=-1;
		
		d[0][0]=0;
		for(int p=1;p<=700;p++)
		if(mx[b[1]][p]!=-1)
		d[1][p]=mx[b[1]][p];
	
		for(int i=1;i<k;i++)
		for(int j=0;j<=m;j++)
		if(d[i][j]!=-1)
		{
			for(int p=1;p<=700;p++)
			{
				if(mx[b[i+1]][p]!=-1)
					d[i+1][j+p+2]=max(d[i+1][j+p+2],d[i][j]+mx[b[i+1]][p]);
				if(mn[b[i+1]][p]!=inf)
					d[i+1][j+p+1]=max(d[i+1][j+p+1],d[i][j]-mn[b[i+1]][p]);
			}
		}
		printf("%lld\n",d[k][m]);
	}
}

update:

// 感谢吉林大学的一位数学大佬网友

G. tsy's number

思路:    

令                                         f(x)=\sum_{i=1}^{n}\sum_{j=1}^{n}\sum_{k=1}^{n}jk^2 [gcd(i,j,k)=x]

则:                                                         ans=\sum_{x=1}^{n}\varphi (x)f(x)

令:                                               F(x)=\sum_{x|d}f(d)=x^3\sum_{i=1}^{\frac{n}{x}}\sum_{j=1}^{\frac{n}{x}}\sum_{k=1}^{\frac{n}{x}}jk^2

令:                                 G(\frac{n}{x})=\sum_{i=1}^{\frac{n}{x}}\sum_{j=1}^{\frac{n}{x}}\sum_{k=1}^{\frac{n}{x}}jk^2=(\frac{n}{x})^3(\frac{n}{x}+1)^2(2\frac{n}{x}+1)/12

我们反演一下:                                           f(x)=\sum_{x|d}\mu (\frac{d}{x})F(d)

那么:                                                    ans=\sum_{x=1}^{n}\varphi (x)\sum_{x|d}\mu (\frac{d}{x})F(d)

切换枚举次序:                   ans=\sum_{d=1}^{n}F(d)\sum_{x|d}\mu (x)\varphi (\frac{d}{x})=\sum_{d=1}^{n}G(\frac{n}{d})\sum_{x|d}d^3\mu (x)\varphi (\frac{d}{x})/

我们线性筛预处理 \sum_{x|d}d^3\mu (x)\varphi (\frac{d}{x})的前缀和,然后就可用整除分块搞定了。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e7+1,mod=1<<30;
int pri[maxn/10],mu[maxn],phi[maxn],vis[maxn],cnt;
int d[maxn];
void add(int &x,ll y)
{
	x=x-mod+y;
	if(x<0)x+=mod;
	if(x>=mod)x-=mod;
}
void add(int &x,int y)
{
	x+=y;
	if(x>=mod)x-=mod;
}
void init(int n)
{
	mu[1]=phi[1]=d[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(!vis[i])
		pri[++cnt]=i,mu[i]=-1,phi[i]=i-1,d[i]=i-2;
		for(int j=1;j<=cnt&&pri[j]*i<=n;j++)
		{
			vis[pri[j]*i]=1;
			if(i%pri[j]!=0)
			{
				mu[i*pri[j]]=-mu[i];
				phi[i*pri[j]]=phi[i]*phi[pri[j]];
				d[i*pri[j]]=(1ll*d[i]*phi[pri[j]]-d[i]+mod)%mod;
			}
			else
			{
				phi[i*pri[j]]=phi[i]*pri[j];
				int m=i*pri[j],x=1;
				while(m%pri[j]==0)
				m/=pri[j],x*=pri[j];
				d[i*pri[j]]=(1ll*d[m]*phi[x]%mod-1ll*d[m]*phi[x/pri[j]]%mod+mod)%mod;
				break;
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		d[i]=1ll*d[i]*i%mod*i%mod*i%mod;
		add(d[i],d[i-1]);
	}
	
}
ll cal(int x)
{
	if(x%2)
	{
		ll res=1ll*(x+1)*(x+1)/4;
		if(x%3==1)
		return res%mod*x%mod*x%mod*x%mod*((2*x+1)/3)%mod;
		if(x%3==2)
		return res/3%mod*x%mod*x%mod*x%mod*(2*x+1)%mod;
		return 1ll*x/3*x%mod*x%mod*(res%mod)%mod*(2*x+1)%mod; 
	}
	else
	{
		ll res=1ll*x*x/4;
		if(x%3==1)
		return res*x%mod*(x+1)%mod*(x+1)%mod*((2*x+1)/3)%mod;
		if(x%3==2)
		return res*x%mod*((x+1)/3)%mod*(x+1)%mod*(2*x+1)%mod;
		return res/3*x%mod*(x+1)%mod*(x+1)%mod*(2*x+1)%mod; 
	}
}
int main()
{
	init(1e7);
	int T,n;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		int ans=0;
		for(int l=1,r;l<=n;l=r+1)
		{
			r=n/(n/l);
			add(ans,(1ll*(d[r]-d[l-1]+mod)*cal(n/l))%mod);
		}
		printf("%d\n",ans);
	}
}

I. Max answer

题意:你可以选择一个区间,然后得到一个权值=该区间的和*该区间最小值,求最大权值。

思路:枚举最小值ai,然后用线段树找一个最大的包含 i 的区间[ l , r ],该区间最小值为ai,然后再用最大最小值线段树记录ai的前缀和,我们记maxR为 [ i , r ]的最大前缀和,maxL为[ l -1, i - 1 ]的最大前缀和,minR,minL同理,然后用两颗线段树求出这四个值,如果ai>=0,那么选择ai为最小值所能得到最优的答案是 ai*(maxR - minL),如果ai<0,那么最优答案就是 ai*(minR - maxL),搞定。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5e5+10;
int mn[maxn*4],a[maxn],n,cur1,cur2;
void up(int o,int l,int r,int k,int v)
{
	if(l==r)
	{
		mn[o]=v;
		return;
	}
	int m=(l+r)/2,ls=o*2,rs=o*2+1;
	if(k<=m)up(ls,l,m,k,v);
	else up(rs,m+1,r,k,v);
	mn[o]=min(mn[ls],mn[rs]);
}
void qu(int o,int l,int r,int ql,int qr,int v,int tp)
{
	int m=(l+r)/2,ls=o*2,rs=o*2+1;
	if(l>=ql&&r<=qr)
	{
		if(l==r)
		{
			if(mn[o]<v)
			{
				if(tp==1)
				cur1=max(cur1,l);
				else
				cur2=min(cur2,l);
			}
			return;
		}
		if(tp==1)
		{
			if(mn[rs]<v)
			qu(rs,m+1,r,ql,qr,v,tp);
			else if(mn[ls]<v)qu(ls,l,m,ql,qr,v,tp);
		}
		else
		{
			if(mn[ls]<v)qu(ls,l,m,ql,qr,v,tp);
			else if(mn[rs]<v)qu(rs,m+1,r,ql,qr,v,tp);
		}
		return;
	}
	if(ql<=m)qu(ls,l,m,ql,qr,v,tp);
	if(qr>m)qu(rs,m+1,r,ql,qr,v,tp);
}
ll Mx[maxn*4],Mn[maxn*4];
void update(int o,int l,int r,int k, ll v)
{
	if(l==r)
	{
		Mx[o]=Mn[o]=v;
		return;
	}
	int m=(l+r)/2,ls=o*2,rs=o*2+1;
	if(k<=m)update(ls,l,m,k,v);
	else update(rs,m+1,r,k,v);
	Mx[o]=max(Mx[ls],Mx[rs]);
	Mn[o]=min(Mn[ls],Mn[rs]); 
}
ll qumax(int o,int l,int r,int ql,int qr)
{
	
	if(l>=ql&&r<=qr)return Mx[o];
	int m=(l+r)/2,ls=o*2,rs=o*2+1;
	ll res=-1e18;
	if(ql<=m)res=max(res,qumax(ls,l,m,ql,qr));
	if(qr>m)res=max(res,qumax(rs,m+1,r,ql,qr));
	return res;
}
ll qumin(int o,int l,int r,int ql,int qr)
{
	if(l>=ql&&r<=qr)return Mn[o];
	int m=(l+r)/2,ls=o*2,rs=o*2+1;
	ll res=1e18;
	if(ql<=m)res=min(res,qumin(ls,l,m,ql,qr));
	if(qr>m)res=min(res,qumin(rs,m+1,r,ql,qr));
	return res;
}
ll query(int p,int l,int r,int tp)
{
	if(tp==1)return qumax(1,0,n,p,r)-qumin(1,0,n,l-1,p-1);
	else return qumin(1,0,n,p,r)-qumax(1,0,n,l-1,p-1);
}
ll gao(int p)
{
	cur1=0,cur2=n+1;
	qu(1,1,n,p,n,a[p],2);
	qu(1,1,n,1,p,a[p],1);
	cur1++,cur2--;
	ll ans=0;
	if(a[p]>=0)ans=query(p,cur1,cur2,1)*a[p];
	else ans=query(p,cur1,cur2,2)*a[p];
	return ans;
}
int main()
{
	int x;
	ll ans=-1e18,res=0;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&x);
		a[i]=x;
		up(1,1,n,i,x);
		res+=x;
		update(1,0,n,i,res);
	}
	for(int i=1;i<=n;i++)
	ans=max(ans,gao(i));
	printf("%lld\n",ans);
}

J. Distance on the tree

题意:给一颗树,有m次询问,每次询问 u v w,要求 u 到 v 的路径中有多少边权值<=w。

思路:以1位根建树,然后在树上建可持久化线段树,记录根节点到当前节点经过的边权,每次查询就在rt[ u ]和rt[ v ]中查询[ 0 , w ]的区间和然后加起来,假设他们lca 为fu,那么肯定还要减去rt[ fu ]的[ 0, w ]区间和*2。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10,inf=1e9+7;
int rt[maxn],sum[maxn*40],ls[maxn*40],rs[maxn*40],n,cnt;
vector<int>G[maxn],dis[maxn];
int deep[maxn],f[maxn][20];
void up(int pre,int& o,int l,int r,int k)
{
	o=++cnt;
	ls[o]=ls[pre],rs[o]=rs[pre];
	sum[o]=sum[pre]+1;
	if(l==r)return;
	int m=(1ll*l+r)/2;
	if(k<=m)up(ls[pre],ls[o],l,m,k);
	else up(rs[pre],rs[o],m+1,r,k);
}
int qu(int o,int l,int r,int k)
{
	if(l==r)return sum[o];
	int m=(1ll*l+r)/2;
	if(k<=m)return qu(ls[o],l,m,k);
	else return sum[ls[o]]+qu(rs[o],m+1,r,k);
}
void dfs(int u,int fa,int dep)
{
	deep[u]=dep;
	f[u][0]=fa;
	for(int i=0;i<G[u].size();i++)
	{
		int v=G[u][i];
		if(v==fa)continue;
		up(rt[u],rt[v],0,inf,dis[u][i]);
		dfs(v,u,dep+1);
	}
}
void init()
{
	for(int i=1;i<20;i++)
	for(int j=1;j<=n;j++)
	f[j][i]=f[f[j][i-1]][i-1];
}
int lca(int p,int q)
{
	if(deep[p]<deep[q])swap(p,q);
	for(int i=19;i>=0;i--)
	if(deep[f[p][i]]>=deep[q])
	p=f[p][i];
	if(p==q)return p;
	
	for(int i=19;i>=0;i--)
	if(f[p][i]!=f[q][i])
	p=f[p][i],q=f[q][i];
	return f[p][0];
}
int main()
{
	int m,u,v,w;
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++)
	{
		scanf("%d%d%d",&u,&v,&w);
		G[u].push_back(v);
		G[v].push_back(u);
		dis[u].push_back(w);
		dis[v].push_back(w); 
	}
	dfs(1,0,1);
	init();
	while(m--)
	{
		scanf("%d%d%d",&u,&v,&w);
		int ans=0;
		ans=qu(rt[u],0,inf,w)+qu(rt[v],0,inf,w);
		int fu=lca(u,v);
		ans-=qu(rt[fu],0,inf,w)*2;
		printf("%d\n",ans);
	}
}

虽然 K 题也是我写的,但是我觉得这题有点恶心,就不写这题题解了。

7题倒二,这个罚时,我枯了。

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长沙橘子猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值