xinyoudui20241106

写一下。

赛时打了 3.5h T1 暴力,然后最后几分钟发现可以倍增但是没写了。

A - 生物(cycle)

给你 \(m\) 组限制和一个长为 \(n\) 的环,环上有若干点,自由顺时针移动,直到碰到未被消去限制的限制格停下,一次操作可以选择消去小于 \(m\) 组限制,对于每个 \(v\in[1,n]\),新增限制格 \(v\),求至少多少次操作可以让所有点停在 \(v\),或报告无解。

\(T\le 10^5,\sum n,\sum m\le 10^6\)\(\sum\) 限制格总数 \(\le 10^6\)

可以发现当且仅当 \(v+1\) 的点到达 \(v\) 时所有点到达 \(v\).

预处理出来一个 \(jmp(x)\) 表示从位置 \(x\) 通过一次操作最远能到哪里。这个考虑反向维护出一个 \(t(x)\) 表示 \(x\) 作为限制格,最远会从哪个地方直接到这里,这个做区间 \([t(x),x]\) 覆盖即可。

预处理出 \(jmp(x)\) 以后直接暴力跳是 \(O(n^2)\) 可得 70 分。倍增一下就 \(O(n\log n+k)\) 了。把数组倍长一下好做些。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+7;
const int maxk=1e6+7;
int T;
int n,m;
vector<int>b[maxn];
vector<int>t[maxn<<1];
int v[maxn<<1]; // 对于位置 i,最靠前的空当
int jmp[22][maxn<<1],cnt[maxn],tms[maxn<<1],fkc,fkpos,mxbck;
set<int,greater<int> >s;
int jump(int x,int y){
    int cnt=0;
    for(int j=20;~j;j--)
        if(jmp[j][x]<y) x=jmp[j][x],cnt+=(1<<j);
    if(x<y) return cnt+1;
    return cnt;
}
void solve(){
	cin>>n>>m;
	mxbck=2*n;
	for(int i=1;i<=2*n+1;i++) v[i]=2*n+1;
	for(int i=1;i<=m;i++){
		cin>>cnt[i];
		for(int j=1,x;j<=cnt[i];j++){
			cin>>x;
			tms[x]++;
			if(tms[x]==m) fkc++,fkpos=x;
			if(j>1)	v[x]=min(v[x],b[i].back()+1),
					v[x+n]=min(v[x+n],b[i].back()+n+1);
			else	v[x]=min(v[x],2),v[x+n]=min(v[x+n],n+1);
			b[i].emplace_back(x);
		}
		if(cnt[i]) v[b[i][0]+n]=min(v[b[i][0]+n],b[i].back()+1),mxbck=min(mxbck,b[i].back()+1);
	}
	if(n==1){
		cout<<"0\n";
		return;
	}
	for(int i=n+1;i<=2*n;i++)
		v[i]=min(v[i],mxbck+n);
	for(int i=2*n;i;i--)
		if(v[i]==2*n+1||v[i]==i) v[i]=v[i+1];
	for(int i=1;i<=2*n;i++)
		t[v[i]].emplace_back(i);
	for(int i=1;i<=2*n;i++){
		for(int j:t[i]) s.insert(j);
		t[i].clear();
		jmp[0][i]=*(s.begin());
		s.erase(i);
	}
	s.clear();
    for(int j=1;j<=20;j++)
        for(int i=1;i<=2*n;i++)
            jmp[j][i]=jmp[j-1][jmp[j-1][i]];
	if(fkc>=1){
		if(fkc==1){
			for(int i=1;i<fkpos;i++) cout<<-1<<' ';
			cout<<jump(fkpos+1,fkpos+n)<<" \n"[fkpos==n];
			for(int i=fkpos+1;i<=n;i++) cout<<-1<<" \n"[i==n];
		}else
			for(int i=1;i<=n;i++) cout<<-1<<" \n"[i==n];
		return;
	}
	for(int i=1;i<=n;i++)
        cout<<jump(i+1,i+n)<<" \n"[i==n];
}
signed main(){
	freopen("cycle.in","r",stdin);
	freopen("cycle.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>T;
	while(T--){
		solve();
		for(int i=1;i<=m;i++) b[i].clear();
		for(int i=1;i<=2*n;i++) jmp[0][i]=tms[i]=0;
		fkc=fkpos=0;
	}
	return 0;
}

B - 冲刺(dash)

给你一个无向图,有两种走路方式:

  • 移动到相邻节点;
  • 重复上述操作 \(d\) 次,可以走重复点或边,但不能在两个节点之间徘徊。

某些点被视为不能停下的,即每次走完不能停在这些点。

求从 1 到任意点的最小步数。

\(n\le 10^5,m\le 2\times 10^5,d\le 20\)

考虑最朴素的暴力,跑 0-1 BFS,对于每个点,松弛相邻的点,并跑 DFS 求出走 \(d\) 步能到的点并松弛,记忆化一下,时间复杂度 \(O(nmd)\)

考虑优化 DFS 过程,在这个过程中由于重复走环导致复杂度升高。于是我们钦定每条边最多走两次,记 \(vis(u,d)=fr\) 表示从 \(u\) 出发再走 \(d\) 步仅有 \(fr\) 方向没有被更新过。如果当前是从 \(fr\) 走来的且 \(vis(u,d)=fr\) 则这次就没必要走了,否则就走 \(fr\),并标记 \(vis(u,d)=-1\) 表示走过两遍没必要再走了。时间复杂度 \(O((n+m)d)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+7;
int id,n,m,d;
int vis[maxn][22],dis[maxn],a[maxn];
vector<int>e[maxn];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
void dfs(int u,int dep,int fa,int fr){
    if(!dep){
        if(!a[u]) return;
        if(dis[fr]+1<dis[u]){
            dis[u]=dis[fr]+1;
            q.push({dis[u],u});
        }
        return ;
    }
    if(vis[u][dep]==-1) return;
    else if(vis[u][dep]>0){
        dfs(vis[u][dep],dep-1,u,fr);
        vis[u][dep]=-1;
        return;
    }
    for(int v:e[u]){
        if(v==fa) continue;
        dfs(v,dep-1,u,fr);
    }
    vis[u][dep]=fa;
}
signed main(){
	freopen("dash.in","r",stdin);
	freopen("dash.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>id>>n>>m>>d;
    for(int i=1;i<=n;i++) cin>>a[i],dis[i]=n+1;
    for(int i=1,u,v;i<=m;i++){
        cin>>u>>v;
        e[u].emplace_back(v);
        e[v].emplace_back(u);
    }
    q.push({0,1});
    dis[1]=0;
    while(!q.empty()){
        auto u=q.top().second;
        q.pop();
        dfs(u,d,0,u);
        if(vis[u][0]==-1) continue;
        for(int v:e[u]){
            if(!a[v]) continue;
            if(dis[v]>dis[u]+1){
                dis[v]=dis[u]+1;
                q.push({dis[v],v});
            }
        }
    }
    for(int i=1;i<=n;i++){
        if(dis[i]==n+1) cout<<"-1 ";
        else cout<<dis[i]<<' ';
    }
	return 0;
}

C - 图寻(tuxun)

A 有 \(a\) 个点,B 有 \(b\) 个点,现在有一些不交的圆在平面上,B 会将点均匀撒在某一个圆上,而 A 只知道第 \(i\) 个圆是 B 所在的圆的概率 \(p_i/10^8\)。某一方胜利定义为距离 B 所在圆心最近的点中等概率选取一个点为那一方的点。构造一组 A 的撒点方案使得 A 的胜率最高,输出胜率以及构造的方案,误差小于 \(10^{-\lambda}\) 即为正确。

\(n\le 10^5,a,b,x_i,y_i,r_i\le 10^9,\lambda\le 8\)

考虑只有一个圆的情况,A 的胜率为 \(\frac{a}{a+b}\)。推广开来,可以发现圆的位置和半径并没有用。假设构造的方案序列为 \(t\),则 A 的胜率为

\[\sum\limits_{i=1}^n p_i\frac{t_i}{t_i+b} \]

\(f(x,i)=p_i\frac{x}{x+b}\) 容易知道对于固定的 \(i\)\(x\) 越大 \(\Delta f(x,i)\) 减小,记 \(g(x,i)=f(x,i)-f(x-1,i)\),则 \(g\) 单调递减。每次向胜率最高(\(g\) 最大的 \(i\))的圆上加点,并更新 \(g(x,i)\leftarrow g(x+1,i)\)。这个用优先队列维护以下做到 \(O(a\log n)\)

由于我们加点的 \(g\) 是单调递减的,考虑二分这个最小值 \(M\),则对于每个 \(i\),有

\[M\le g(x,i)=\frac{p_ib}{(x+b)(x+b-1)} \]

\[x\ge\frac{1+\sqrt{1+\frac{4p_ib}{M}}}{2}-b \]

解方程可以得到每个 \(x_i\),判断是否会超过 \(a\) 即可,注意得到的一组解可能不满足 \(\sum x_i=a\),这些剩下的点再用上面的优先队列插进去即可。时间复杂度 \(O(n\log a)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+7;
using db=long double;
int lamda;
int n,A,B;
db p[maxn];
int a[maxn],pos;
bool check(db x){
    db res=0;
    for(int i=1;i<=n;i++) res+=max((db)0,floorl((1+sqrtl(1+4*p[i]/x))/2.0)-B);
    return res<=A;
}
signed main(){
	freopen("tuxun.in","r",stdin);
	freopen("tuxun.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>A>>B>>lamda;
	for(int i=1,______;i<=n;i++){
        cin>>______>>______>>______;
	}
	for(int i=1;i<=n;i++){
        cin>>p[i]; p[i]/=100000000.0;
	}
    db l=0,r=1,as=0;
    for(int i=1;i<=400;i++){
        db mid=(l+r)/2;
        if(check(mid)) r=mid,as=mid;
        else l=mid;
    }
    for(int i=1;i<=n;i++) a[i]=max((db)0,floorl((1+sqrtl(1+4*p[i]/as))/2.0)-B),A-=a[i];
    for(int i=1;A;i=i%n+1,A--) a[i]++;
    db ans=0;
    for(int i=1;i<=n;i++) ans+=(db)a[i]/(a[i]+B)*p[i];
    cout<<fixed<<setprecision(20)<<ans<<'\n';
    for(int i=1;i<=n;i++) cout<<a[i]<<' ';
	return 0;
}
posted @ 2024-11-06 22:04  view3937  阅读(4)  评论(0编辑  收藏  举报
Title