noip模拟6

A 选彩笔(rgb)

一眼转三维坐标系搞。

但是最开始想歪了,以为要转曼哈顿距离,但是发现三维切比雪夫距离不支持转曼哈顿距离。

补充一个知识点
\(x\) 维的曼哈顿距离支持转到 \(2^{x-1}\) 维的切比雪夫距离
所以一维和二维可以直接转化,但是三维及以上就不行了。三维曼哈顿距离等同于某种坐标系下的四维切比雪夫距离。

然后想到题目就是在求最最的一个立方体满足立方体中点的个数为 \(k\) 个。

然后就想到了三维前缀和,值域 \([0,255]\) 可做。

但是总体复杂度是 \(O(n^2)\) 的,没多少分。

其实在三维前缀和的基础上改成二分答案,每次 check 搜索值域内全部边长为 \(mid\) 的立方体,用三维前缀和查个数是否大于等于 \(k\) 即可。

点击查看代码
//自己的没调出来崩溃了,这个是别人的。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5+5,INF=1E9;
inline ll read()
{
	ll x=0,f=1;char ch=getchar();
	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
	return x*f;
}
inline void write(ll x)
{
	if(x<0)x=-x,putchar('-');
	if(x>9)write(x/10);
	putchar((x%10)|48);
}
inline void write(ll x,char p){
	write(x);putchar(p);
}
int n,K;
struct ac
{
	int r,g,b;
}a[N];
int c[260][260][260];
inline int get(int a,int b,int C,int x,int y,int z){
	return c[x][y][z]-c[a-1][y][z]-c[x][b-1][z]-c[x][y][C-1]+c[a-1][b-1][z]+c[a-1][y][C-1]+c[x][b-1][C-1]-c[a-1][b-1][C-1];
}
inline bool check(int mid){
	for(int i=1;i+mid<=256;i++){
		for(int j=1;j+mid<=256;j++){
			for(int k=1;k+mid<=256;k++){
				if(get(i,j,k,i+mid,j+mid,k+mid)>=K){return 1;}
			}
		}
	}
	return 0;
}
inline void fen(int l,int r){
	int ans=0;
	while(l<=r){
		int mid=l+r>>1;
		if(check(mid)){
			ans=mid;r=mid-1;
		}else l=mid+1;
	}
	write(ans);
}
int main()
{
	freopen("rgb.in","r",stdin),freopen("rgb.out","w",stdout);
	n=read();K=read();
	for(int i=1;i<=n;i++){
		a[i]={read()+1,read()+1,read()+1};
		// v[a[i].r][a[i].g][a[i].b].pb(i);
		c[a[i].r][a[i].g][a[i].b]++;
	}
	for(int i=1;i<=256;i++){
		for(int j=1;j<=256;j++){
			for(int k=1;k<=256;k++){
				c[i][j][k]+=c[i-1][j][k];
			}
		}
	}
	for(int i=1;i<=256;i++){
		for(int j=1;j<=256;j++){
			for(int k=1;k<=256;k++){
				c[i][j][k]+=c[i][j-1][k];
			}
		}
	}
	for(int i=1;i<=256;i++){
		for(int j=1;j<=256;j++){
			for(int k=1;k<=256;k++){
				c[i][j][k]+=c[i][j][k-1];
			}
		}
	}
	fen(0,255);
	return 0;
}

B 兵蚁排序(sort)

非常好 \(gxyz OJ\) 我随便写的 \(dfs\) 都有 \(65\)

这个题正解已经想出来了,但是没敢打。

考虑每一对失配的点,如果合法,一定是到最终位置为逆序,那么每次 swap 保证其它点相对位置不变,对于一个点最多移动 \(O(n)\),类似于冒泡排序。

意思就是忽略题目给的排序条件,每次只进行 swap 来保证正确性,然后每个点 \(O(n)\),总复杂度 \(O(n^2)\)

其实我是考虑到移动次数会大于 \(n^2\)。但是不会,最大才会 \(\frac{n^2}{2}\) 次左右(就是倒序转顺序的类型)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+5;
int read()
{
	int num=0,typ=1,c=getchar();
	while('0'>c||c>'9')
	{
		if(c=='-') typ=-1;
		c=getchar();
	}while('0'<=c&&c<='9')
	{
		num=num*10+c-48;
		c=getchar();
	}return num*typ;
}
int t,n;
int a[N],b[N];
int tot,l[N*N],r[N*N];
int main()
{
	freopen("sort.in","r",stdin);
	freopen("sort.out","w",stdout);
	t=read();
	while(t--)
	{
		n=read();tot=0;
		for(int i=1;i<=n;++i)a[i]=read();
		for(int i=1;i<=n;++i)b[i]=read();
		for(int i=1;i<=n;++i)
		{
			if(a[i]==b[i]) continue;
			int j=n+1;
			for(j=i+1;j<=n;++j)
				if(a[j]==b[i]) break;
			if(j>n)
			{
				tot=-1;break;
			}
			for(int k=j;k>i;k--)
			{
				if(a[k]<a[k-1])
				{
					tot++,l[tot]=k-1,r[tot]=k,swap(a[k-1],a[k]);
				}
				else
				{
					tot=-1;break;
				}
			}
			if(tot==-1) break;
		}
		if(tot==-1)
		{
			printf("-1\n");
			continue;
		}else
		{
			printf("0\n%d\n",tot);
			for(int i=1;i<=tot;++i)
				printf("%d %d\n",l[i],r[i]);
		}
	}
	return 0;
}

C 人口局 DBA(dba)

对于 \(m\) 进制的数,求解的个数,就是解 \(L-1\) 个方程。

给定 \(1<a \le L,p<m,s\le a(m-1)\),求 \(\displaystyle \sum_{i=1}^{a}x_i=s,0\le x_i<m x_1<p\) 的非负整数解的个数。

先不考虑 \(x_1\) 的条件如何操作,我们考虑容斥,钦定 \(k\) 个元素一定大于等于 \(m\),那剩下 \(a\) 个数的和为 \(s-km\),有 \(\displaystyle\binom{s-km+a-1}{a-1}\) 组解。

\[f_a(s)=\sum_{k=0}^a (-1)^k \binom{a}{k}\binom{s-km+a-1}{a-1} \]

考虑 \(x_1\) 后:

\[ans=\sum_{i=0}^{p-1}f_{a-1}(s-i) \]

来源于题解

点击查看代码
#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int N=2e3+10,M=4e6,mod=1e9+7;
int m,n,sum,ans,a[N],fac[M+10],inv[M+10];
int qpow(int a,int b)
{
	int res=1;
	while(b)
	{
		if(b&1) res=(res*a)%mod;
		a=(a*a)%mod,b>>=1; 
	}return res;
}
void init()
{
	fac[0]=inv[0]=1;
	for(int i=1;i<=M;i++) fac[i]=fac[i-1]*i%mod;
	inv[M]=qpow(fac[M],mod-2);
	for(int i=M-1;i;i--) inv[i]=inv[i+1]*(i+1)%mod;
}
int getc(int n,int m)
{
	if(m<0||n<m)return 0;
	return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
signed main()
{
	freopen("dba.in","r",stdin);
	freopen("dba.out","w",stdout);
	cin>>m>>n;
	for(int i=n;i;i--) cin>>a[i],sum+=a[i];
	init();
	for(int i=n,cnt=0,flag=1;i;i--,cnt=0,flag=1)
	{
		for(int j=0;j<=sum/m;flag=-flag,j++) 
		{
			cnt+=(flag*getc(i-1,j)+mod)*(getc(sum-j*m+i-1,i-1)-getc(sum-a[i]+1-j*m+i-2,i-1)+mod)%mod;
		}
		sum-=a[i],ans=(ans+cnt%mod)%mod;
	}
	cout<<ans;
	return 0;
}

D 银行的源起(banking)

有一个 \(30\) 分的思路。

考虑树上只有一个银行,答案的最小值是什么。

考虑一条路径 \((x,fa)\) 的贡献,是 \(w\) 乘以通过这条路径的总居民个数

那么,对于每一条边,我们都有两种选择:

  • 选择子树内的所有居民通过这条边(银行在子树外);

  • 选择子树外的所有居民通过这条边(银行在子树内)。

简单来说,就是

\[\sum_{(x,fa)}w\times \min(size_x,S-size_x) \]

其中 \(size_x\) 表示以 \(x\) 为根的子树居民数量\(S\) 表示这棵树全部的居民数量。

对于有两个银行的情况,我们知道,一定会有一条边没有居民通过,这条边左边的居民去一个银行,右边去另一个银行。

可以枚举所有的边,考虑将这条边断开,分裂成两棵树,分别用上诉做法求解答案,那这条边的最小答案就是两树答案之和。

具体地,对于枚举到一条边 \((u,v)\),其中 \(u\)\(v\) 的父亲:

  • \(v\) 的子树,答案取 \(\displaystyle\sum_{(x,fa)\in v}w\times \min(size_x,size_v-size_x)\)

  • 另一棵树:

  • 若当前点的子树有 \(v\),答案在这里取 \(w\times \min(size_u-size_v,S-size_u)\)

  • 否则,答案在这里取 \(w\times \min(size_u,S-size_u-size_v)\)

就好了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int T;
int n;const int N=1e5+4;
vector<int>e[N];int ssiz[N];
int a[N];
int dep[N],siz[N],id[N],rk[N],tim,fa[N],top[N],son[N];
void dfs1(int u,int f)
{
	fa[u]=f,dep[u]=dep[f]+1,siz[u]=1;ssiz[u]=a[u];
	for(int i=0;i<e[u].size();i++)
	{
		int v=e[u][i];
		if(v==f) continue;
		dfs1(v,u);
		siz[u]+=siz[v];ssiz[u]+=ssiz[v];
		if(siz[v]>siz[son[u]]) son[u]=v;
	}
}
void dfs2(int u,int t)
{
	top[u]=t;id[u]=++tim;
	if(son[u]) dfs2(son[u],t);
	for(int i=0;i<e[u].size();i++)
	{
		int v=e[u][i];
		if(v==son[u]||v==fa[u]) continue;
		dfs2(v,v);
	}
}
int d[N];
vector<int>w[N];
inline int read()
{
	int w{1},x{};
	char c=getchar();
	while(c<'0'||c>'9'){if(c == '-')w=-1;c=getchar();}
	while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+c-'0',c=getchar();
	return w * x;
}
inline void write(int x)
{
	if(x<0)x=-x,putchar('-');
	if(x>9)write(x/10);
	putchar(x%10+'0');
}
inline void writeln(int x){write(x);putchar(10);}
inline void writek(int x){write(x);putchar(' ');}
int disu,disv;
int sum1=0,sum2=0;

void calc(int u)
{
	for(int i=0;i<e[u].size();i++)
	{
		if(e[u][i]==fa[u])continue;
		sum1+=w[u][i]*min(ssiz[e[u][i]],ssiz[disv]-ssiz[e[u][i]]);
		
		calc(e[u][i]);
	}
}
void calc2(int u,int d)
{
	for(int i=0;i<e[u].size();i++)
	{
		if(e[u][i]==fa[u]||e[u][i]==d) continue;
		if(id[d]>=id[e[u][i]]&&id[d]<id[e[u][i]]+siz[e[u][i]])
		sum2+=w[u][i]*min(ssiz[e[u][i]]-ssiz[d],ssiz[1]-ssiz[e[u][i]]);
		else
		sum2+=w[u][i]*min(ssiz[e[u][i]],ssiz[1]-ssiz[e[u][i]]-ssiz[disv]);
//		cout<<ssiz[1]-ssiz[e[u][i]]-ssiz[disv]<<" ";
		calc2(e[u][i],d);
	}
 } 
signed main()
{
//	freopen("sub2.in","r",stdin);
	freopen("banking.in","r",stdin);
	freopen("banking.out","w",stdout);
	T=read();
	while(T--)
	{
		n=read();
		for(int i=1;i<=n;i++) a[i]=read();
		for(int i=1;i<n;i++)
		{
			int v=read(),u=read(),W=read();
			e[u].push_back(v),e[v].push_back(u);
			w[u].push_back(W),w[v].push_back(W);
		}
		tim=0;
		dfs1(1,0),dfs2(1,1);
		int ans=1e18;
		for(int u=1;u<=n;u++)
		{
			for(int v:e[u])
			{
				if(v==fa[u]) continue;
				disu=u,disv=v;
				sum1=sum2=0;
				calc(v),calc2(1,v);
//				cout<<sum1+sum2<<"\n";
				ans=min(ans,sum1+sum2);
			}
		}
		writeln(ans);
		for(int i=1;i<=n;i++)e[i].clear(),w[i].clear(),top[i]=siz[i]=dep[i]=son[i]=id[i]=fa[i]=0;
	}
	return 0;
}
posted @ 2024-11-05 17:53  ccjjxx  阅读(16)  评论(0编辑  收藏  举报