多校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\) 则合法。
-
三维前缀和加速匹配即可。
- 三维前缀和详见 暑假集训CSP提高模拟26 T1' 前缀 。
点击查看代码
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\)
-
考虑手动模拟下排序的过程,不妨选择冒泡排序。
-
冒泡排序的过程中不断选择长度为 \(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}\) 。
- 推导过程详见 冲刺CSP联训模拟2 T2 P295. 工地难题 。
- 尝试化简这个式子,有 \(\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\) 上没有绑包,直接散着测试了。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18528822,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。