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 的胜率为
记 \(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\),有
解方程可以得到每个 \(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;
}