2025端午集训 Day 1 阴间题选记
P4220 [WC2018] 通道
黑?别急。
用爬山算法,考虑两点 \(u,v\) 的估价函数 \(f_{u,v}\) 直接表示两点通过模拟题目中的过程所得的使用者数量之和。
随机一个点,在三个图上三次单源最短路,用 BFS,那么 \(f_{u,v}=d_1(u,v)+d_2(u,v)+d_3(u,v)\),原因显然。
枚举所有点 \(i\),找到 \(f_{u,i}\) 最大的那一个。
因为 \(i\) 有 \(w_i\) 这一条链是大的,那么从 \(i\) 出发还可能会有更大的 \((i,j)\) 吗?于是在这里爬山,即在从这里开始在三个图上三次单源最短路,计算 \(f_{i,j}\) 最大的那一个,重复几次。记录各过程中 \(f\) 最大值的最大值
如果你发现一次的 \(f\) 最大值比之前 \(f\) 最大值的最大值不优,那么意味着这个局部最优解就被找到了,此时退出循环,重新随机一个点开始到另一个局部去找最优解,即重复上述过程。
随机个 \(100\) 次,在找局部最优解时加上个卡时就过了。
但是只限洛谷能过。也就是当个纪念意义的黑题。
上去不知道讲了什么。
code
Show me the code
#define psb push_back
#define mkp make_pair
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
ll x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
const int N=2e5+5;
struct e{
int v;
ll w;
};
vector<e> e1[N],e2[N],e3[N];
ll dist[5][N];
bool vis[N];
void bfs(int r,int sta){
queue<int> q;
q.push(r);
while(q.size()){
int u=q.front();
vis[u]=1;
q.pop();
//cout<<u<<'\n';
if(sta==1){
for(auto v:e1[u]){
if(!vis[v.v]){
dist[1][v.v]=dist[1][u]+v.w;
vis[v.v]=1;
q.push(v.v);
}
}
}
if(sta==2){
for(auto v:e2[u]){
if(!vis[v.v]){
dist[2][v.v]=dist[2][u]+v.w;
vis[v.v]=1;
q.push(v.v);
}
}
}
if(sta==3){
for(auto v:e3[u]){
if(!vis[v.v]){
dist[3][v.v]=dist[3][u]+v.w;
vis[v.v]=1;
q.push(v.v);
}
}
}
}
}
int main(){
int n;cin>>n;
for(int i=1;i<n;i++){
int u,v;ll w;cin>>u>>v>>w;
e1[u].push_back(e{v,w});
e1[v].push_back(e{u,w});
}
for(int i=1;i<n;i++){
int u,v;ll w;cin>>u>>v>>w;
e2[u].push_back(e{v,w});
e2[v].push_back(e{u,w});
}
for(int i=1;i<n;i++){
int u,v;ll w;cin>>u>>v>>w;
e3[u].push_back(e{v,w});
e3[v].push_back(e{u,w});
}
srand(time(0));
ll aaaans=0;
ll rdm=0,cid=1,id=1,iteraa=0;
for(int i=1;i<=100;i++){
ll res=0;
cid=abs(rand())%n+1;
memset(dist,0,sizeof dist);
rdm=0;id=0,iteraa=0;
while(clock() < 3.5 * CLOCKS_PER_SEC){
memset(dist,0,sizeof dist);
bfs(cid,1);
memset(vis,0,sizeof vis);
bfs(cid,2);
memset(vis,0,sizeof vis);
bfs(cid,3);
memset(vis,0,sizeof vis);
for(int j=1;j<=n;j++){
if(res<dist[1][j]+dist[2][j]+dist[3][j]){
res=dist[1][j]+dist[2][j]+dist[3][j];
id=j;
}
}
if(res>rdm){
rdm=res;
cid=id;
}
else break;
}
aaaans=max(rdm,aaaans);
}
cout<<aaaans;
return 0;
}
P10680 [COTS 2024] 双双决斗 Dvoboj
糊了个假的线段树,不会也不想写根号 ds。
只有 13pts,在榜上比较幽默。
有几个点因为神秘原因 RE 了,不然还能多得几分。
WRONG code
Show me the code
#define psb push_back
#define mkp make_pair
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
ll x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
const int N=3e5+5;
int f[N];
struct seg{
int l,r,mid;
ll res;
}t1[N<<2],t2[N<<2];
ll pi[N];
void buildt1(int p,int bord,int l,int r){
t1[p].l=l+bord;t1[p].r=r+bord;
if(l==r){
t1[p].res=pi[l+bord];
return ;
}
int mid=(1<<(__lg(r)));
if(mid==r){mid=r/2;}
t1[p].mid=bord+mid;
//cout<<"construct "<<l+bord<<' '<<r+bord<<" mid "<<t1[p].mid<< '\n';
buildt1(p*2,bord,1,mid);buildt1(p*2+1,t1[p].mid,1,r-mid);
t1[p].res=llabs(t1[p*2].res-t1[p*2+1].res);
return ;
}
void buildt2(int p,int bord,int l,int r){
t2[p].l=l+bord;t2[p].r=r+bord;
if(l==r){
t2[p].res=pi[l+bord];
return ;
}
int mid=(1<<(__lg(r)));
if(mid==r){mid=r/2;}
t2[p].mid=bord+mid;
//cout<<"construct "<<l+bord<<' '<<r+bord<<" mid "<<t2[p].mid<< '\n';
buildt2(p*2,bord,1,mid);buildt2(p*2+1,t2[p].mid,1,r-mid);
t2[p].res=llabs(t2[p*2].res-t2[p*2+1].res);
return ;
}
void updt1(int p,int pos,int k){
if(t1[p].l==pos&&t1[p].r==pos){
t1[p].res=k;
return;
}
int mid=t1[p].mid;
if(pos<=mid)updt1(p*2,pos,k);
if(mid<pos)updt1(p*2+1,pos,k);
t1[p].res=llabs(t1[p*2].res-t1[p*2+1].res);
return ;
}
void updt2(int p,int pos,int k){
if(t2[p].l==pos&&t2[p].r==pos){
t2[p].res=k;
return;
}
int mid=t2[p].mid;
if(pos<=mid)updt2(p*2,pos,k);
if(mid<pos)updt2(p*2+1,pos,k);
t2[p].res=llabs(t2[p*2].res-t2[p*2+1].res);
return ;
}
ll queryt1(int p,int l,int r){
if(l<=t1[p].l&&t1[p].r<=r){
return t1[p].res;
}
int mid=t1[p].mid;
ll sub=0;
if(l<=mid&&mid<r)return llabs(queryt1(p*2,l,r)-queryt1(p*2+1,l,r));
else if(l<=mid)return queryt1(p*2,l,r);
else return queryt1(p*2+1,l,r);
}
ll queryt2(int p,int l,int r){
if(l<=t2[p].l&&t2[p].r<=r){
return t2[p].res;
}
int mid=t2[p].mid;
ll sub=0;
if(l<=mid&&mid<r)return llabs(queryt2(p*2,l,r)-queryt2(p*2+1,l,r));
else if(l<=mid)return queryt2(p*2,l,r);
else return queryt2(p*2+1,l,r);
}
int main(){
int n,q;
cin>>n>>q;
for(int i=1;i<=n;i++){
cin>>pi[i];
}
if(n%2==0){
buildt1(1,0,1,n);
buildt2(1,1,1,n-1);
}
else{
buildt1(1,0,1,n-1);
buildt2(1,1,1,n-1);
}
for(int i=1;i<=q;i++){
int op;cin>>op;
if(op==1){
ll pos,r;cin>>pos>>r;
if(pos==1){
updt1(1,pos,r);
}
else{
updt1(1,pos,r);
updt2(1,pos,r);
}
}
if(op==2){
int l,k;cin>>l>>k;
int r=l+(1<<k)-1;
if(l%2==0)cout<<queryt2(1,l,r)<<'\n';
else cout<<queryt1(1,l,r)<<'\n';
}
}
return 0;
}
ABC294F
算是个阳间题,说点有意思的:在下面的代码中运行样例一,注释和取消第 29 行的注释会导致主函数中答案的输出发生变化。
最幽默的一个。
code
Show me the code
#define psb push_back
#define mkp make_pair
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
ll x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
const int N=5e4+5;
struct tms{
int x,y;
bool operator<(const tms &sa)const{
return x<sa.x;
}
}taka[N],aoki[N];
int n,m,k;
bool check(long double pp){
int cnt=0;
vector<long double> ta,ao;
for(int i=1;i<=n;i++){
ta.push_back(1.0*taka[i].x-(taka[i].x+taka[i].y)*pp);
//cout<<ta[i-1]<<' ';
}
for(int j=1;j<=m;j++){
ao.push_back(pp*(aoki[j].x+aoki[j].y)-1.0*aoki[j].x);
//cout<<ao[j-1]<<' ';
}
sort(ao.begin(),ao.end());
for(int i=1;i<=n;i++){
int wp=upper_bound(ao.begin(),ao.end(),ta[i])-ao.begin();
cnt+=wp;
//cout<<wp<<'\n';
}
//cout<<"varium "<<pp<<' '<<cnt<<'\n';
if(cnt>=k)return 0;
else return 1;
}
int main(){
cin>>n>>m>>k;
for(int i=1;i<=n;i++){cin>>taka[i].x>>taka[i].y;}
for(int i=1;i<=m;i++){cin>>aoki[i].x>>aoki[i].y;}
sort(aoki+1,aoki+1+m);
long double l=0.0,r=1.0;
while(abs(r-l)>=1e-9){
long double mid=(l+r)/2.0;
if(check(mid))r=mid;
else l=mid;
}
//cout<<"\n\n\n\n\n\n\n\n\n\n\n\n";
cout<<l;
return 0;
}
ABC295G
想了半天 DAG 上做法甚至在嘴里念叨了几遍这要在树上不是乱做吗才发现输入给的就是棵树。
保证 \(u\) 可达 \(v\) 这个性质挺好的。保证了这些边全是返祖边,于是用并查集维护下强连通分量编号和答案即可。
用并查集路径压缩了下这棵树,似乎暴力往上跳也能过?
这题 DAG 上怎么做?
code
Show me the code
#define psb push_back
#define mkp make_pair
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
ll x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
const int N=5e5+5;
vector<int> edge[N],iedge[N];
struct fau{
int fa,ans,top;
}f[N];
int dep[N];
int _find(int u){return f[u].fa==u?u:f[u].fa=_find(f[u].fa);}
int dfs(int u){
f[u].fa=u;
int res=u;
for(int v:edge[u]){
dep[v]=dep[u]+1;
res=min(dfs(v),res);
}
f[u].top=u;
f[u].ans=res;
return res;
}
int univans=0x3f3f3f3f;
void merge(int u,int tar,int mid){
int t=_find(u);
u=f[t].top;
univans=min(univans,f[t].ans);
if(u==tar)return ;
for(int v:iedge[u]){
merge(v,tar,mid);
}
f[t].fa=mid;f[t].top=tar;f[t].ans=univans;
return ;
}
int main(){
int n;cin>>n;
for(int i=1;i<n;i++){
int p;cin>>p;
edge[p].push_back(i+1);
iedge[i+1].push_back(p);
}
dfs(1);
int q;cin>>q;
for(int i=1;i<=q;i++){
int op;cin>>op;
if(op==1){
int u,v;cin>>u>>v;
if(dep[v]>dep[u])swap(u,v);
int t=_find(v);
univans=0x3f3f3f3f;
merge(u,v,t);
}
if(op==2){
int x;cin>>x;
int t=_find(x);
cout<<f[t].ans<<'\n';
}
}
return 0;
}
[NOIP 2012 提高组] 开车旅行
阴间码量题。
怎么把所有变量都想了一遍就是没把天数想进去。
定好长度限制和起点后走过的路径是一定的,但是直接模拟会炸。
于是我们倍增优化模拟,(其实本质上像个 DP),天数是个很好欺负的东西,于是我们把它倍增压掉。
位置和开车的人放在另两维,让 \(0\) 代表 \(A\),\(1\) 代表 \(B\),于是定义:
\(G_{i,j,k} (k\in{0,1})\) 表示从 \(j\) 点出发,出发时是 \(A,B\) 开车,走了 \(2^i\) 天后能到的城市。
\(A_{i,j,k} (k\in{0,1})\) 表示从 \(j\) 点出发,出发时是 \(A,B\) 开车,走了 \(2^i\) 天后 \(A\) 开车走的距离。
\(B_{i,j,k} (k\in{0,1})\) 表示从 \(j\) 点出发,出发时是 \(A,B\) 开车,走了 \(2^i\) 天后 \(B\) 开车走的距离。
你会发现这三个东西都有像倍增 LCA 里面那个 \(fa\) 数组一样可以倍增的性质。
于是转移方程很显然,列出来写上去。
但是你还没有初始化呢,怎么初始化?
\(G_{0,j,k} (k\in{0,1})\) 表示从 \(j\) 点出发,出发时是 \(A,B\) 开车,走了 \(1\) 天后能到的城市。也就是对应驾驶员从 \(j\) 点出发能到的下一个城市。我们要对于两个驾驶员算出这个。剩下的 \(A_{0,j,k},B_{0,j,k}\) 在知道了 \(G\) 后都是好算的。
这题最难的地方应该在怎么算出对应驾驶员从 \(j\) 点出发能到的下一个城市。有双向链表解法,平衡树解法,这里我用 STL 解法,用 set 把迭代器跳一跳,至于怎么跳我也不会描述,总之很阴间,我也不知道我是怎么写出这些东西然后调过的。
但是竟然交了三发就过了。
看看代码吧。
code
Show me the code
#define psb push_back
#define mkp make_pair
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef long long ll;
ll read(){
ll x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
const int N=1e5+5;
const int lgr=23;
int height[N];
struct work{int x,id;};
struct work1{int regx,relx,id;};
struct COMPARE{
bool operator()(const work &x,const work &y)const{return x.x<y.x;}
};
int ta[N],tb[N];
ll gotoi[30][N][3];
ll dista[30][N][3];
ll distb[30][N][3];
bool cmp733(work1 x1,work1 y1){
if(x1.regx==y1.regx)return x1.relx<y1.relx;
else return x1.regx<y1.regx;
}
signed main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>height[i];
}
set<work,COMPARE> s;
s.clear();
for(int i=n;i>=1;i--){
if(i==n){
s.insert(work{height[i],i});
ta[i]=0;tb[i]=0;
continue;
}
if(i==n-1){
ta[i]=0;tb[i]=i+1;
s.insert(work{height[i],i});
continue;
}
auto maxcdx=s.end();maxcdx--;
auto mincdx=s.begin();
set<work,COMPARE>::iterator cur,upper;
vector<work1> cc;
if((*maxcdx).x<height[i]){
cc.push_back(work1{abs(height[i]-(*maxcdx).x),(*maxcdx).x,(*maxcdx).id});
maxcdx--;
cc.push_back(work1{abs(height[i]-(*maxcdx).x),(*maxcdx).x,(*maxcdx).id});
}
else if((*mincdx).x>height[i]){
cc.push_back(work1{abs(height[i]-(*mincdx).x),(*mincdx).x,(*mincdx).id});
++mincdx;
cc.push_back(work1{abs(height[i]-(*mincdx).x),(*mincdx).x,(*mincdx).id});
}
else{
int cnt=3;
cur=s.lower_bound(work{height[i],0});
cc.push_back(work1{abs(height[i]-(*cur).x),(*cur).x,(*cur).id});
auto lim=s.end();
if(++cur==lim){
cur--;
do{cur--;cnt--;
cc.push_back(work1{abs(height[i]-(*cur).x),(*cur).x,(*cur).id});
}while(cur!=s.begin()&&cnt>0);
}
else{
cc.push_back(work1{abs(height[i]-(*cur).x),(*cur).x,(*cur).id});
cnt--;cur--;
do{cur--;cnt--;
cc.push_back(work1{abs(height[i]-(*cur).x),(*cur).x,(*cur).id});
}while(cur!=s.begin()&&cnt>0);
}
}
sort(cc.begin(),cc.end(),cmp733);
tb[i]=cc.front().id;ta[i]=cc[1].id;
s.insert(work{height[i],i});
}
for(int i=1;i<=n;i++){
if(ta[i]){
gotoi[0][i][0]=ta[i];
dista[0][i][0]=abs(height[i]-height[ta[i]]);
distb[0][i][0]=0;
}
if(tb[i]){
gotoi[0][i][1]=tb[i];
dista[0][i][1]=0;
distb[0][i][1]=abs(height[i]-height[tb[i]]);
}
}
for(int i=1;i<=lgr;i++){
for(int j=1;j<=n;j++){
for(int g=0;g<=1;g++){
int to=g;
if(i==1)to^=1;
if(gotoi[i-1][j][g])gotoi[i][j][g]=gotoi[i-1][gotoi[i-1][j][g]][to];
if(gotoi[i][j][g]){
dista[i][j][g]=dista[i-1][j][g]+dista[i-1][gotoi[i-1][j][g]][to];
distb[i][j][g]=distb[i-1][j][g]+distb[i-1][gotoi[i-1][j][g]][to];
}
}
}
}
int xhj;cin>>xhj;
int aa=0,bb=0,id=0;
for(int i=1;i<=n;i++){
int ca=0,cb=0;
int s=i;int dr=0;int xhj1=xhj;
for(int j=lgr;j>=0;j--){
if(gotoi[j][s][dr]&&dista[j][s][dr]+distb[j][s][dr]<=xhj1){
xhj1-=dista[j][s][dr]+distb[j][s][dr];
ca+=dista[j][s][dr];cb+=distb[j][s][dr];
if(j==0)dr^=1;
s=gotoi[j][s][dr];
}
}
if(cb==0)ca=1;
if(aa*cb>bb*ca||(aa==0&&bb==0)){
aa=ca;bb=cb;id=i;
}
}
cout<<id<<'\n';
int m;cin>>m;
for(int i=1;i<=m;i++){
int s,x;int dr=0;
cin>>s>>x;
int ca=0,cb=0;
for(int j=lgr;j>=0;j--){
if(gotoi[j][s][dr]&&dista[j][s][dr]+distb[j][s][dr]<=x){
x-=dista[j][s][dr]+distb[j][s][dr];
ca+=dista[j][s][dr];cb+=distb[j][s][dr];
if(j==0)dr^=1;
s=gotoi[j][s][dr];
}
}
cout<<ca<<' '<<cb<<'\n';
}
return 0;
}

浙公网安备 33010602011771号