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}\) 组解。
考虑 \(x_1\) 后:
来源于题解
点击查看代码
#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\) 乘以通过这条路径的总居民个数。
那么,对于每一条边,我们都有两种选择:
-
选择子树内的所有居民通过这条边(银行在子树外);
-
选择子树外的所有居民通过这条边(银行在子树内)。
简单来说,就是
其中 \(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;
}