多校A层冲刺NOIP2024模拟赛18

多校A层冲刺NOIP2024模拟赛18

\(T1\) A. 选彩笔(rgb) \(100pts/100pts\)

  • 观察到 \(0 \le r,g,b \le 255\) 且答案具有单调性,故考虑二分答案。

  • \(r,g,b\) 分别抽象成三维坐标下的 \(x,y,z\)

  • 设当前二分出的答案为 \(mid\) ,由调整法分析可知若存在一个边长为 \(mid\) 的正方体中点的数量 \(\le k\) 则合法。

  • 三维前缀和加速匹配即可。

    点击查看代码
    int cnt[260][260][260];
    int sum(int x1,int y1,int z1,int x2,int y2,int z2)
    {
    	return cnt[x2][y2][z2]-(-cnt[x1-1][y1-1][z2]-cnt[x1-1][y2][z1-1]-cnt[x2][y1-1][z1-1]+cnt[x1-1][y2][z2]+cnt[x2][y1-1][z2]+cnt[x2][y2][z1-1]+cnt[x1-1][y1-1][z1-1]);
    }
    bool check(int mid,int n,int m)
    {
    	for(int i=1;i<=256-mid;i++)
    	{
    		for(int j=1;j<=256-mid;j++)
    		{
    			for(int k=1;k<=256-mid;k++)
    			{
    				if(sum(i,j,k,i+mid,j+mid,k+mid)>=m)
    				{
    					return true;
    				}
    			}
    		}
    	}
    	return false;
    }
    int main()
    {
    	freopen("rgb.in","r",stdin);
    	freopen("rgb.out","w",stdout);
    	int n,m,r,g,b,l=0,mid,ans=0,i,j,k;
    	cin>>n>>m;
    	for(i=1;i<=n;i++)
    	{
    		cin>>r>>g>>b;
    		r++;
    		g++;
    		b++;
    		cnt[r][g][b]++;
    	}
    	for(i=1;i<=256;i++)
    	{
    		for(j=1;j<=256;j++)
    		{
    			for(k=1;k<=256;k++)
    			{
    				cnt[i][j][k]+=-cnt[i-1][j-1][k]-cnt[i-1][j][k-1]-cnt[i][j-1][k-1]+cnt[i-1][j][k]+cnt[i][j-1][k]+cnt[i][j][k-1]+cnt[i-1][j-1][k-1];
    			}
    		}
    	}
    	r=256;
    	while(l<=r)
    	{
    		mid=(l+r)/2;
    		if(check(mid,n,m)==true)
    		{
    			ans=mid;
    			r=mid-1;
    		}
    		else
    		{
    			l=mid+1;
    		}
    	}
    	cout<<ans<<endl;
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    

\(T2\) B. 兵蚁排序(sort) \(0pts/0pts\)

  • 强化版: CF1187D Subarray Sorting

  • 考虑手动模拟下排序的过程,不妨选择冒泡排序。

  • 冒泡排序的过程中不断选择长度为 \(2\) 的区间进行排序/交换,从而消除逆序对。这个过程中不会再产生新的逆序对,故若 \(\{ b \}\) 中存在 \(\{ a \}\) 中没有的逆序对则无解。

  • 交换的过程中在 \(j \in [i,n]\) 中找到第一个满足 \(a_{j}=b_{i}\)\(j\) ,这个时候直接对 \([i,j]\) 排序是假的(因为破坏内部的关系),就只能自右向左依次交换来保证内部相对大小顺序不变。

    • 如果不需要同步交换的话,找到 \(b_{i}\) 对应的 \(a_{j}\) 即可。
  • 此时若无法进行交换(大的数不可能跑到小的数前面)即说明 \(\{ b \}\) 中存在 \(\{ a \}\) 中没有的逆序对。

  • 考虑加强版时,不进行同步交换,等价于 \(\min\limits_{k=1}^{j}\{ a_{k }\}=b_{i}\) ,可以使用线段树维护区间查询和单点删除(转化为修改)。

    点击查看代码
    int a[1010],b[1010];
    vector<pair<int,int> >ans;
    int main()
    {
    	freopen("sort.in","r",stdin);
    	freopen("sort.out","w",stdout);
    	int t,n,flag,pos,i,j,k;
    	cin>>t;
    	for(k=1;k<=t;k++)
    	{
    		cin>>n;
    		flag=0;
    		ans.clear();
    		for(i=1;i<=n;i++)
    		{
    			cin>>a[i];
    		}
    		for(i=1;i<=n;i++)
    		{
    			cin>>b[i];
    		}
    		for(i=1;i<=n;i++)
    		{
    			for(j=i;j<=n;j++)
    			{
    				if(b[i]==a[j])
    				{
    					pos=j;
    					break;
    				}
    			}
    			for(j=pos;j>=i+1;j--)
    			{
    				if(a[j-1]<a[j])
    				{
    					flag=-1;
    					break;
    				}
    				swap(a[j-1],a[j]);
    				ans.push_back(make_pair(j-1,j));
    			}
    			if(flag==-1)
    			{
    				break;
    			}
    		}
    		cout<<flag<<endl;
    		if(flag==0)
    		{
    			cout<<ans.size()<<endl;
    			for(i=0;i<ans.size();i++)
    			{
    				cout<<ans[i].first<<" "<<ans[i].second<<endl;
    			}
    		}
    	}
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    
    点击查看加强版代码
    int a[300010],b[300010],cnt[300010];
    vector<int>pos[300010];
    struct SMT
    {
    	struct SegmentTree
    	{
    		int minn;
    	}tree[1200010];
    	int lson(int x)
    	{
    		return x*2;
    	}
    	int rson(int x)
    	{
    		return x*2+1;
    	}
    	void pushup(int rt)
    	{
    		tree[rt].minn=min(tree[lson(rt)].minn,tree[rson(rt)].minn);
    	}
    	void build(int rt,int l,int r)
    	{
    		if(l==r)
    		{
    			tree[rt].minn=a[l];
    			return;
    		}
    		int mid=(l+r)/2;
    		build(lson(rt),l,mid);
    		build(rson(rt),mid+1,r);
    		pushup(rt);
    	}
    	void update(int rt,int l,int r,int pos,int val)
    	{
    		if(l==r)
    		{
    			tree[rt].minn=val;
    			return;
    		}
    		int mid=(l+r)/2;
    		if(pos<=mid)
    		{
    			update(lson(rt),l,mid,pos,val);
    		}
    		else
    		{
    			update(rson(rt),mid+1,r,pos,val);
    		}
    		pushup(rt);
    	}
    	int query(int rt,int l,int r,int x,int y)
    	{
    		if(x<=l&&r<=y)
    		{
    			return tree[rt].minn;
    		}
    		int mid=(l+r)/2,ans=0x3f3f3f3f;
    		if(x<=mid)
    		{
    			ans=min(ans,query(lson(rt),l,mid,x,y));
    		}
    		if(y>mid)
    		{
    			ans=min(ans,query(rson(rt),mid+1,r,x,y));
    		}
    		return ans;
    	}
    }T;
    int main()
    {
    	int t,n,flag,i,j;
    	cin>>t;
    	for(j=1;j<=t;j++)
    	{
    		cin>>n;
    		flag=1;
    		for(i=1;i<=n;i++)
    		{
    			cnt[i]=0;
    			pos[i].clear();
    		}
    		for(i=1;i<=n;i++)
    		{
    			cin>>a[i];
    			cnt[a[i]]++;
    			pos[a[i]].push_back(i);
    		}
    		for(i=1;i<=n;i++)
    		{
    			cin>>b[i];
    			cnt[b[i]]--;
    		}
    		for(i=1;i<=n;i++)
    		{
    			flag&=(cnt[i]==0);
    			reverse(pos[i].begin(),pos[i].end());
    		}
    		if(flag==1)
    		{
    			T.build(1,1,n);
    			for(i=1;i<=n;i++)
    			{
    				if(pos[b[i]].size()==0||T.query(1,1,n,1,pos[b[i]].back())!=b[i])
    				{
    					flag=0;
    					break;
    				}
    				T.update(1,1,n,pos[b[i]].back(),0x3f3f3f3f);
    				pos[b[i]].pop_back();
    			}
    			if(flag==1)
    			{
    				cout<<"YES"<<endl;
    			}
    			else
    			{
    				cout<<"NO"<<endl;
    			}	
    		}
    		else
    		{
    			cout<<"NO"<<endl;
    		}
    	}
    	return 0;
    }
    

\(T3\) C. 人口局 DBA(dba) \(7pts/0pts\)

  • 部分分

    • \(34pts/10pts\) :当成数位 \(DP\) 拆位做即可,记忆化搜索。

      点击查看代码
      const ll p=1000000007;
      unordered_map<ll,ll>f[2010];
      ll a[2010];
      ll dfs(ll pos,ll pre,ll limit,ll m,ll sum)
      {
      	if(pos<=0)
      	{
      		return (pre==sum&&limit==0);
      	}
      	if(limit==0&&f[pos].find(pre)!=f[pos].end())
      	{
      		return f[pos][pre];
      	}
      	ll ans=0,maxx=(limit==1)?a[pos]:m-1;
      	for(ll i=0;i<=maxx&&pre+i<=sum;i++)
      	{
      		ans=(ans+dfs(pos-1,pre+i,(limit==1)*(i==maxx),m,sum))%p;
      	}
      	return (limit==0)?f[pos][pre]=ans:ans;
      }
      int main()
      {
      	freopen("dba.in","r",stdin);
      	freopen("dba.out","w",stdout);
      	ll m,sum=0,i;
      	cin>>m>>a[0];
      	for(i=1;i<=a[0];i++)
      	{
      		cin>>a[i];
      		sum+=a[i];
      	}
      	reverse(a+1,a+1+a[0]);
      	cout<<(dfs(a[0],0,1,m,sum)+p)%p<<endl;
      	fclose(stdin);
      	fclose(stdout);
      	return 0;
      }
      
    • \(64pts/10pts\) :将记忆化搜索改成递推版。

      点击查看代码
      const int p=1000000007;
      int a[2010],f[2][2][4000010];
      int qadd(int a,int b,int p)
      {
      	return a+b>=p?a+b-p:a+b;
      }
      int main()
      {
      	freopen("dba.in","r",stdin);
      	freopen("dba.out","w",stdout);
      	int m,sum=0,i,j,k;
      	cin>>m>>a[0];
      	for(i=1;i<=a[0];i++)
      	{
      		cin>>a[i];
      		sum+=a[i];
      	}
      	reverse(a+1,a+1+a[0]);
      	for(i=0;i<=a[a[0]]-1;i++)
      	{
      		f[a[0]&1][0][i]=1;
      	}
      	f[a[0]&1][1][a[a[0]]]=1;
      	for(i=a[0];i>=1;i--)
      	{
      		for(k=0;k<=sum;k++)
      		{
      			f[(i-1)&1][0][k]=f[(i-1)&1][1][k]=0;
      		}
      		for(k=0;k<=sum;k++)
      		{
      			for(j=0;j<=a[i-1]-1&&k+j<=sum;j++)
      			{
      				f[(i-1)&1][0][k+j]=qadd(f[(i-1)&1][0][k+j],f[i&1][1][k],p);
      			}
      			if(k+a[i-1]<=sum)
      			{
      				f[(i-1)&1][1][k+a[i-1]]=qadd(f[(i-1)&1][1][k+a[i-1]],f[i&1][1][k],p);
      			}
      			for(j=0;j<=m-1&&k+j<=sum;j++)
      			{
      				f[(i-1)&1][0][k+j]=qadd(f[(i-1)&1][0][k+j],f[i&1][0][k],p);
      			}
      		}
      	}
      	cout<<f[1&1][0][sum]<<endl;
      	fclose(stdin);
      	fclose(stdout);
      	return 0;
      }
      
  • 正解

    • 观察递推的写法,猜测可能与组合计数有关。
    • 考虑枚举填入的数补足 \(l\) 位(空位补 \(0\) )后和 \(l\) 的极长公共前缀的长度 \(len\) ,设其 \(S\) 值等于 \(s\) ,等价于求 \(\begin{cases} \forall i \in [1,len],x_{i}=n_{i} \\ x_{len+1} \in [0,n_{len+1}) \\ \forall i \in [len+2,l],x_{i} \in [0,m) \end{cases},\sum\limits_{i=1}^{l}x_{i}=S(n)\)\(\begin{cases} x_{len+1} \in [0,n_{len+1}) \\ \forall i \in [len+2,l],x_{i} \in [0,m) \end{cases},\sum\limits_{i=len+1}^{l}x_{i}=S(n)-\sum\limits_{i=1}^{len}n_{i}\) 的整数解的方案数。
    • 不妨设 \(s=S(n)-\sum\limits_{i=1}^{len}n_{i},a=l-len-1\) ,等价于求 \(\begin{cases} x_{0} \in [0,n_{len+1}) \\ \forall i \in [1,a],x_{i} \in [0,m) \end{cases}x_{0}+\sum\limits_{i=1}^{a}x_{i}=s\) 的整数解的方案数 \(\sum\limits_{x_{0}=0}^{n_{len+1}-1}\sum\limits_{i=0}^{a}(-1)^{i}\dbinom{a}{i}\dbinom{s-x_{0}-im+a-1}{a-1}\)
    • 尝试化简这个式子,有 \(\begin{aligned} & \sum\limits_{x_{0}=0}^{n_{len+1}-1}\sum\limits_{i=0}^{a}(-1)^{i}\dbinom{a}{i}\dbinom{s-x_{0}-im+a-1}{a-1} \\ &=\sum\limits_{i=0}^{a}(-1)^{i}\dbinom{a}{i}\sum\limits_{x_{0}=0}^{n_{len+1}-1}\dbinom{s-x_{0}-im+a-1}{a-1} \\ &=\sum\limits_{i=0}^{a}(-1)^{i}\dbinom{a}{i}(\dbinom{s-im+a}{a}-\dbinom{s-im+a-n_{len+1}}{a}) \end{aligned}\)
      • 从第二步到第三步利用了一个结论 \(\sum\limits_{i=0}^{k-1}\dbinom{n-i}{m}=\dbinom{n+1}{m+1}-\dbinom{n-k+1}{m+1}\)
      • 证明
        • 考虑从杨辉三角上 \(\dbinom{n+1}{m+1}\) 的来源入手,有 \(\begin{aligned} &\sum\limits_{i=0}^{k-1}\dbinom{n-i}{m} \\ &=\sum\limits_{n-k+1}^{n}\dbinom{n}{m} \\ &=\sum\limits_{n-k+1}^{n}\dbinom{n+1}{m+1}-\dbinom{n}{m+1} \\ &=\dbinom{n+1}{m+1}-\dbinom{n-k+1}{m+1} \end{aligned}\)
    点击查看代码
    const ll p=1000000007;
    ll inv[4002010],jc[4002010],jc_inv[4002010],n[2010];
    ll C(ll n,ll m,ll p)
    {
    	return (n>=m&&n>=0&&m>=0)?(jc[n]*jc_inv[n-m]%p)*jc_inv[m]%p:0;
    }
    ll ask(ll s,ll a,ll up,ll m)
    {
    	ll ans=0;
    	for(ll i=0;i<=a;i++)
    	{
    		if(i%2==0)
    		{
    			ans=(ans+C(a,i,p)*((C(s-i*m+a,a,p)-C(s-i*m+a-up,a,p)+p)%p)%p)%p;
    		}
    		else
    		{
    			ans=(ans-C(a,i,p)*((C(s-i*m+a,a,p)-C(s-i*m+a-up,a,p)+p)%p)%p+p)%p;
    		}
    	}
    	return ans;
    }
    int main()
    {
    	freopen("dba.in","r",stdin);
    	freopen("dba.out","w",stdout);
    	ll m,l,ans=0,sum=0,i;
    	cin>>m>>l;
    	for(i=1;i<=l;i++)
    	{
    		cin>>n[i];
    		sum+=n[i];
    	}
    	inv[1]=1;
    	jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1;
    	for(i=2;i<=l*m+l+10;i++)
    	{
    		inv[i]=(p-p/i)*inv[p%i]%p;
    		jc[i]=jc[i-1]*i%p;
    		jc_inv[i]=jc_inv[i-1]*inv[i]%p;
    	}
    	for(i=0;i<=l-1;i++)
    	{
    		sum-=n[i];
    		ans=(ans+ask(sum,l-i-1,n[i+1],m))%p;
    	}
    	cout<<ans<<endl;
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    

\(T4\) D. 银行的源起(banking) \(3pst/0pts\)

  • 部分分

    • \(10pts\) :枚举两个银行,时间复杂度为 \(O(n^{3})\)

      点击查看代码
      ll a[100010],dfn[100010],dis[100010],tot=0;
      vector<pair<ll,ll> >e[100010];
      void add(ll u,ll v,ll w)
      {
      	e[u].push_back(make_pair(v,w));
      }
      ll sx_min(ll x,ll y)
      {
      	return dfn[x]<dfn[y]?x:y;
      }
      struct ST
      {
      	ll fminn[100010][25];
      	void init(ll n)
      	{
      		for(ll j=1;j<=__lg(n);j++)
      		{
      			for(ll i=1;i+(1<<j)-1<=n;i++)
      			{
      				fminn[i][j]=sx_min(fminn[i][j-1],fminn[i+(1<<(j-1))][j-1]);
      			}
      		}
      	}
      	ll query(ll l,ll r)
      	{
      		ll t=__lg(r-l+1);
      		return sx_min(fminn[l][t],fminn[r-(1<<t)+1][t]);
      	}
      }T;
      void dfs(ll x,ll fa,ll w)
      {
      	tot++;
      	dfn[x]=tot;
      	dis[x]=dis[fa]+w;
      	T.fminn[tot][0]=fa;
      	for(ll i=0;i<e[x].size();i++)
      	{
      		if(e[x][i].first!=fa)
      		{
      			dfs(e[x][i].first,x,e[x][i].second);
      		}
      	}
      }
      ll lca(ll u,ll v)
      {
      	if(dfn[u]>dfn[v])
      	{
      		swap(u,v);
      	}
      	return (dfn[u]==dfn[v])?u:T.query(dfn[u]+1,dfn[v]);
      }
      ll get_dis(ll u,ll v)
      {
      	return dis[u]+dis[v]-2*dis[lca(u,v)];
      }
      int main()
      {
      	freopen("banking.in","r",stdin);
      	freopen("banking.out","w",stdout);
      	ll t,n,sum,ans,u,v,w,i,j,k,h;
      	scanf("%lld",&t);
      	for(h=1;h<=t;h++)
      	{
      		tot=0;
      		ans=0x7f7f7f7f7f7f7f7f;
      		scanf("%lld",&n);
      		for(i=1;i<=n;i++)
      		{
      			scanf("%lld",&a[i]);
      			e[i].clear();
      		}
      		for(i=1;i<=n-1;i++)
      		{
      			scanf("%lld%lld%lld",&u,&v,&w);
      			add(u,v,w);
      			add(v,u,w);
      		}
      		dfs(1,0,0);
      		T.init(n);
      		for(i=1;i<=n;i++)
      		{
      			for(j=i+1;j<=n;j++)
      			{
      				sum=0;
      				for(k=1;k<=n;k++)
      				{
      					sum+=min(get_dis(i,k),get_dis(j,k))*a[k];
      				}
      				ans=min(ans,sum);
      			}
      		}
      		printf("%lld\n",ans);
      	}
      	fclose(stdin);
      	fclose(stdout);
      	return 0;
      }
      
    • \(34pts\) :观察到两个银行的覆盖实际上是将整个图分成了两个连通块,枚举断的那条边后跑换根 \(DP\) 即可,做法同 luogu P2986 [USACO10MAR] Great Cow Gathering G | CF1092F Tree with Maximum Cost ,时间复杂度为 \(O(n^{2})\)

      点击查看代码
      ll a[100010],siz[100010],f[100010],g[100010],u[100010],v[100010],w[100010];
      vector<pair<ll,ll> >e[100010];
      void add(ll u,ll v,ll w)
      {
      	e[u].push_back(make_pair(v,w));
      }
      void dfs(ll x,ll fa)
      {
      	f[x]=0;
      	siz[x]=a[x];
      	for(ll i=0;i<e[x].size();i++)
      	{
      		if(e[x][i].first!=fa)
      		{
      			dfs(e[x][i].first,x);
      			f[x]+=f[e[x][i].first]+siz[e[x][i].first]*e[x][i].second;
      			siz[x]+=siz[e[x][i].first];
      		}
      	}
      }
      ll reroot(ll x,ll fa,ll rt,ll n,ll w)
      {
      	ll minn=g[x]=((x!=rt)?g[fa]+(n-siz[x])*w-siz[x]*w:f[x]);
      	for(ll i=0;i<e[x].size();i++)
      	{
      		if(e[x][i].first!=fa)
      		{
      			minn=min(minn,reroot(e[x][i].first,x,rt,n,e[x][i].second));
      		}
      	}
      	return minn;
      }
      int main()
      {
      	freopen("banking.in","r",stdin);
      	freopen("banking.out","w",stdout);
      	ll t,n,w,ans,i,j;
      	scanf("%lld",&t);
      	for(j=1;j<=t;j++)
      	{
      		ans=0x7f7f7f7f7f7f7f7f;
      		scanf("%lld",&n);
      		for(i=1;i<=n;i++)
      		{
      			scanf("%lld",&a[i]);
      			e[i].clear();
      		}
      		for(i=1;i<=n-1;i++)
      		{
      			scanf("%lld%lld%lld",&u[i],&v[i],&w);
      			add(u[i],v[i],w);
      			add(v[i],u[i],w);
      		}
      		for(i=1;i<=n-1;i++)
      		{
      			dfs(u[i],v[i]);
      			dfs(v[i],u[i]);
      			ans=min(ans,reroot(u[i],v[i],u[i],siz[u[i]],0)+reroot(v[i],u[i],v[i],siz[v[i]],0));
      		}
      		printf("%lld\n",ans);
      	}
      	fclose(stdin);
      	fclose(stdout);
      	return 0;
      }
      
  • 正解

    • 考虑优化断边后将图分成两个连通块的做法。
    • 假设断开了 \((u,v)\) 这条边,以 \(u\) 所在的连通块为例,并钦定 \(v\)\(u\) 的父亲。
    • \(u\) 所在的连通块内的每条边 \((x,y,w)\) 对答案的贡献为 \(w \times \min(siz_{x},siz_{u}-siz_{x})=w \times \begin{cases} siz_{x} & siz_{x} \le \left\lfloor \frac{siz_{u}}{2} \right\rfloor \\ siz_{u}-siz_{x} & siz_{x}> \left\lfloor \frac{siz_{u}}{2} \right\rfloor \end{cases}=w \times(siz_{x} \times [siz_{x} \le \left\lfloor \frac{siz_{u}}{2} \right\rfloor]+siz_{u} \times [siz_{x}> \left\lfloor \frac{siz_{u}}{2} \right\rfloor]-siz_{x} \times [siz_{x}> \left\lfloor \frac{siz_{u}}{2} \right\rfloor])\)
    • 对于 \(u\) 所在的连通块, \(DFS\) 途中线段树合并是容易维护的。而 \(v\) 所在的连通块分成了 \(1 \to v\) 这条链和整棵树的其他部分,考虑怎么维护这部分变化的 \(siz\)
    • \(DFS\)\(u\)\(1 \to v\) 这条链上的节点都减少了 \(siz_{u}\) ,且单调递减。不妨维护一棵全局线段树,每次减掉链的贡献,然后再减去 \(u\) 的贡献。在回溯的时候统计答案,并消除影响。
      • 查询前避免链减的方法是移项,然后统一减去贡献。
    • 需要适当卡常。
    点击查看代码
    #define LOCAL
    namespace IO{
    	#ifdef LOCAL
    	FILE*Fin(fopen("banking.in","r")),*Fout(fopen("banking.out","w"));
    	#else
    	FILE*Fin(stdin),*Fout(stdout);
    	#endif
    	class qistream{static const size_t SIZE=1<<16,BLOCK=64;FILE*fp;char buf[SIZE];int p;public:qistream(FILE*_fp=stdin):fp(_fp),p(0){fread(buf+p,1,SIZE-p,fp);}void flush(){memmove(buf,buf+p,SIZE-p),fread(buf+SIZE-p,1,p,fp),p=0;}qistream&operator>>(char&x){x=getch();while(isspace(x))x=getch();return*this;}template<class T>qistream&operator>>(T&x){x=0;p+BLOCK>=SIZE?flush():void();bool flag=false;for(;!isdigit(buf[p]);++p)flag=buf[p]=='-';for(;isdigit(buf[p]);++p)x=x*10+buf[p]-'0';x=flag?-x:x;return*this;}char getch(){p+BLOCK>=SIZE?flush():void();return buf[p++];}qistream&operator>>(char*str){char ch=getch();while(ch<=' ')ch=getch();int i=0;for(;ch>' ';++i,ch=getch())str[i]=ch;str[i]='\0';return*this;}}qcin(Fin);
    	class qostream{static const size_t SIZE=1<<16,BLOCK=64;FILE*fp;char buf[SIZE];int p;public:qostream(FILE*_fp=stdout):fp(_fp),p(0){}~qostream(){fwrite(buf,1,p,fp);}void flush(){fwrite(buf,1,p,fp),p=0;}template<class T>qostream&operator<<(T x){int len=0;p+BLOCK>=SIZE?flush():void();x<0?(x=-x,buf[p++]='-'):0;do buf[p+len]=x%10+'0',x/=10,++len;while(x);for(int i=0,j=len-1;i<j;++i,--j)std::swap(buf[p+i],buf[p+j]);p+=len;return*this;}qostream&operator<<(char x){putch(x);return*this;}void putch(char ch){p+BLOCK>=SIZE?flush():void();buf[p++]=ch;}qostream&operator<<(char*str){for(int i=0;str[i];++i)putch(str[i]);return*this;}qostream&operator<<(const char*s){for(int i=0;s[i];++i)putch(s[i]);return*this;}}qcout(Fout);
    }
    #define cin IO::qcin
    #define cout IO::qcout
    struct node
    {
    	int nxt,to;
    	ll w;
    }e[200010];
    int head[100010],n,cnt=0;
    ll a[100010],siz[100010],m=0,ans;
    void add(int u,int v,ll w)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	e[cnt].w=w;
    	head[u]=cnt;
    }
    struct SMT
    {
    	int root[100010],rt_sum=0;
    	struct SegmentTree
    	{
    		int ls,rs;
    		ll sum1,sum2;
    	}tree[100010<<6];
    	#define lson(rt) (tree[rt].ls)
    	#define rson(rt) (tree[rt].rs)
    	void clear()
    	{
    		memset(root,0,sizeof(root));
    		rt_sum=0;
    	}
    	int build_rt()
    	{
    		rt_sum++;
    		lson(rt_sum)=rson(rt_sum)=tree[rt_sum].sum1=tree[rt_sum].sum2=0;
    		return rt_sum;
    	}
    	void pushup(int rt)
    	{
    		tree[rt].sum1=tree[lson(rt)].sum1+tree[rson(rt)].sum1;
    		tree[rt].sum2=tree[lson(rt)].sum2+tree[rson(rt)].sum2;
    	}
    	void update(int &rt,int l,int r,int pos,ll val1,ll val2)
    	{
    		rt=(rt==0)?build_rt():rt;
    		if(l==r)
    		{
    			tree[rt].sum1+=val1;
    			tree[rt].sum2+=val2;
    			return;
    		}
    		int mid=(l+r)/2;
    		if(pos<=mid)
    		{
    			update(lson(rt),l,mid,pos,val1,val2);
    		}
    		else
    		{
    			update(rson(rt),mid+1,r,pos,val1,val2);
    		}
    		pushup(rt);
    	}
    	void merge(int &rt1,int rt2,int l,int r)
    	{
    		if(rt1==0||rt2==0)
    		{
    			rt1+=rt2;
    			return;
    		}
    		if(l==r)
    		{
    			tree[rt1].sum1+=tree[rt2].sum1;
    			tree[rt1].sum2+=tree[rt2].sum2;
    			return;
    		}
    		int mid=(l+r)/2;
    		merge(lson(rt1),lson(rt2),l,mid);
    		merge(rson(rt1),rson(rt2),mid+1,r);	
    		pushup(rt1);
    	}
    	pair<ll,ll> query(int rt,int l,int r,int x,int y)
    	{
    		if(rt==0)
    		{
    			return make_pair(0,0);
    		}
    		if(x<=l&&r<=y)
    		{
    			return make_pair(tree[rt].sum1,tree[rt].sum2);
    		}
    		int mid=(l+r)/2;
    		pair<ll,ll>ans=make_pair(0ll,0ll),tmp;
    		if(x<=mid)
    		{
    			tmp=query(lson(rt),l,mid,x,y);
    			ans.first+=tmp.first;
    			ans.second+=tmp.second;
    		}
    		if(y>mid)
    		{
    			tmp=query(rson(rt),mid+1,r,x,y);
    			ans.first+=tmp.first;
    			ans.second+=tmp.second;
    		}
    		return ans;
    	}
    }T;
    struct BIT
    {
    	ll c[2][100010],sum1,sum2;
    	ll lowbit(ll x)
    	{
    		return (x&(-x));
    	}
    	void clear()
    	{
    		sum1=sum2=0;
    		memset(c,0,sizeof(c));
    	}
    	void add(ll n,ll x,ll val1,ll val2)
    	{
    		sum1+=val1;
    		sum2+=val2;
    		for(ll i=x;i<=n;i+=lowbit(i))
    		{
    			c[0][i]+=val1;
    			c[1][i]+=val2;
    		}
    	}
    	pair<ll,ll> getsum(ll x)
    	{
    		pair<ll,ll>ans=make_pair(0,0);
    		for(ll i=x;i>=1;i-=lowbit(i))
    		{
    			ans.first+=c[0][i];
    			ans.second+=c[1][i];
    		}
    		return ans;
    	}
    }B[2];
    void dfs(int x,int fa)
    {
    	siz[x]=a[x];
    	for(int i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(e[i].to!=fa)
    		{
    			dfs(e[i].to,x);
    			siz[x]+=siz[e[i].to];
    		}
    	}
    	a[x]=siz[x];
    }
    void dfs1(int x,int fa)
    {
    	for(int i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(e[i].to!=fa)
    		{
    			dfs1(e[i].to,x);
    			int pos=lower_bound(a+1,a+1+m,siz[e[i].to])-a;
    			B[0].add(m,pos,e[i].w,siz[e[i].to]*e[i].w);
    		}
    	}
    }
    void work(int x)
    {
    	int pos=upper_bound(a+1,a+1+m,siz[x]/2)-a-1;
    	ll sum=0;
    	pair<ll,ll>tmp=(pos>=1)?T.query(T.root[x],1,m,1,pos):make_pair(0ll,0ll);
    	sum+=tmp.second+(T.tree[T.root[x]].sum1-tmp.first)*siz[x]-(T.tree[T.root[x]].sum2-tmp.second);
    	pos=upper_bound(a+1,a+1+m,(siz[1]+siz[x])/2)-a-1;
    	tmp=B[1].getsum(pos);
    	sum+=tmp.second+(B[1].sum1-tmp.first)*siz[1]-(B[1].sum2-tmp.second)-tmp.first*siz[x];
    	pos=upper_bound(a+1,a+1+m,(siz[1]-siz[x])/2)-a-1;
    	tmp=B[0].getsum(pos);
    	sum+=tmp.second+(B[0].sum1-tmp.first)*(siz[1]-siz[x])-(B[0].sum2-tmp.second);
    	tmp=(pos>=1)?T.query(T.root[x],1,m,1,pos):make_pair(0ll,0ll);
    	sum-=tmp.second+(T.tree[T.root[x]].sum1-tmp.first)*(siz[1]-siz[x])-(T.tree[T.root[x]].sum2-tmp.second);
    	ans=min(ans,sum);
    }
    void dfs2(int x,int fa)
    {
    	for(int i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(e[i].to!=fa)
    		{
    			int pos=lower_bound(a+1,a+1+m,siz[e[i].to])-a;
    			B[0].add(m,pos,-e[i].w,-siz[e[i].to]*e[i].w);
    			B[1].add(m,pos,e[i].w,siz[e[i].to]*e[i].w);
    			dfs2(e[i].to,x);
    			B[1].add(m,pos,-e[i].w,-siz[e[i].to]*e[i].w);
    			work(e[i].to);
    			B[0].add(m,pos,e[i].w,siz[e[i].to]*e[i].w);
    			T.update(T.root[x],1,m,pos,e[i].w,siz[e[i].to]*e[i].w);
    			T.merge(T.root[x],T.root[e[i].to],1,m);
    		}
    	}
    }
    int main()
    {
    	int t,u,v,w,i,j;
    	cin>>t;
    	for(j=1;j<=t;j++)
    	{
    		cnt=0;
    		ans=0x7f7f7f7f7f7f7f7f;
    		memset(e,0,sizeof(e));
    		memset(head,0,sizeof(head));
    		T.clear();
    		B[0].clear();
    		cin>>n;
    		for(i=1;i<=n;i++)	
    		{
    			cin>>a[i];
    		}
    		for(i=1;i<=n-1;i++)
    		{
    			cin>>u>>v>>w;
    			add(u,v,w);
    			add(v,u,w);
    		}
    		dfs(1,0);
    		sort(a+1,a+1+n);
    		m=unique(a+1,a+1+n)-(a+1);
    		dfs1(1,0);	
    		dfs2(1,0);
    		cout<<ans<<endl;
    	}
    	return 0;
    }
    

总结

  • \(T2\) 觉得写迭代加深或者普通搜索有点麻烦遂最后再开始写。
  • \(T3\) 理解错输入了,以为给出的是 \(n=(n_{l}n_{l-1} \dots n_{1})_{10}\) (实际上是 \(n=(n_{l}n_{l-1} \dots n_{1})_{m}\) ),遂先将 \(n\) 转化成了 \(m\) 进制再进行数位 \(DP\) ,并为此写了高精模低精,高精除低精,高精减高精(因为不会写高精减低精)。同时因为知道自己大样例肯定跑不过去遂没看大样例,并错过了知道自己读假题的机会。挂了 \(57pts/10pts\)
  • \(T4\) 最后半个小时想出换根 \(DP\) 后换根时连通块点数传成了总点数,挂了 \(31pts/30pts\)

后记

  • 学校 \(OJ\)\(T2\) 赛时才加的文件读写,但没通知我们更改了。

  • 赛后下发的 \(T2\)Special Judge 貌似直接写的是 \(O(mn \log n)\) 的暴力代码,都不屑于写珂朵莉树加线段树分裂的 \(O(m \log n)\) 是吧。

    点击查看代码
    #include "testlib.h"
    #include<bits/stdc++.h>
    #define MAXN 200005
    using namespace std;
    
    int n;
    int a[MAXN], b[MAXN];
    
    bool chk_ok(){
    	for(int i=1;i<=n;i++){
    		if(a[i]!=b[i]) return 0;
    	}
    	return 1;
    }
    
    
    int main(int argc, char* argv[]){
    	registerTestlibCmd(argc, argv);
    
    	int T = inf.readInt();
    	while(T--){
    		n = inf.readInt();
    		for(int i=1;i<=n;i++){
    			a[i] = inf.readInt();
    		}
    		for(int i=1;i<=n;i++){
    			b[i] = inf.readInt();
    		}
    
    		int ans1 = ouf.readInt();
    		int ans0 = ans.readInt();
    		if(ans0 != ans1){
    			//cerr<<ans0<<" "<<ans1<<endl;
    			quitf(_wa, "ans is wrong! %d %d", ans0, ans1);
    		}
    		if(ans1 == -1) continue;
    
    		int m = ouf.readInt();
    		if(m > n*n){
    			quitf(_wa, "m is larger than n^2!");
    		}
    
    		int l,r;
    		for(int t=1;t<=m;t++){
    			l = ouf.readInt(1,n);
    			r = ouf.readInt(1,n);
    			if(l > r) quitf(_wa, "l > r!");
    			sort(a+l, a+r+1);
    		}
    
    		if(chk_ok()==0){
    			quitf(_wa, "a and b are different!");
    		}
    	}
    	quitf(_ok, "The answer is correct.");
    	return 0;
    }
    //
    
    
    
  • \(T3,T4\) 的捆绑测试在学校 \(OJ\) 上没有绑包,直接散着测试了。

posted @ 2024-11-05 21:08  hzoi_Shadow  阅读(36)  评论(0编辑  收藏  举报
扩大
缩小