#7 CF626G & CF627F & CF605E
Raffles
题目描述
解法
首先考虑没有询问怎么做,考虑对第 \(i\) 个奖池增加一张彩票的贡献是(设现在的彩票数是 \(c_i<l_i\)):
那么按照这个贡献排序之后贪心即可,十分简单。
那么修改怎么办呢?我自己的想法是使用调整法,修改之后最优方案可能会变化,那么我们减少某个奖池的彩票 \(1\),增加某个奖池的彩票 \(1\),用 \(\tt set\) 即可维护这个操作。
问题是操作次数,感性上操作次数不会很多,实际上最多发生一次调整。这里简单证明一下,我们考虑 \(l_i\) 增大的情况,对于同时选取增量的 \(x\) 和 \(x+1\),贡献分别是:
那么调整之后贡献分别是:
我们观察调整之后 \(x\) 贡献大于调整之前 \(x+1\) 的贡献,既然调整之前 \(x+1\) 都被选取了,说明调整之后 \(x\) 一定会被选取,再讨论几种类似的情况即可证明这个结论。
时间复杂度 \(O(n\log n)\),有点卡精度,要用 \(\tt longdouble\) 和 \(\tt eps\)
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <set>
using namespace std;
const int M = 200005;
#define db long double
const db inf = 1e18;
const db eps = 1e-10;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,p[M],l[M],c[M];db ans;
db f(int x,int c)
{
if(c==-1) return inf;
if(c>=l[x]) return 0L;
return 1.0L*p[x]*l[x]/(c+1+l[x])/(c+l[x]);
}
db g(int x)
{
int t=min(l[x],c[x]);
return 1.0L*p[x]*t/(t+l[x]);
}
db Abs(db x) {return x>0?x:-x;}
struct node
{
db o;int x,y;
node(int X=0,int Y=0) : x(X) , y(Y) {o=f(x,y);}
bool operator < (const node &b) const
{return Abs(o-b.o)>eps?o<b.o:x<b.x;}
};multiset<node> s,e;
void add()
{
auto it=--s.end();ans+=it->o;int x=it->x;
e.erase(node(x,c[x]-1));e.insert(*it);
s.erase(it);s.insert(node(x,++c[x]));
}
void rem()
{
auto it=e.begin();ans-=it->o;int x=it->x;
s.erase(node(x,c[x]));s.insert(*it);
e.erase(it);e.insert(node(x,(--c[x])-1));
}
signed main()
{
n=read();m=read();k=read();
for(int i=1;i<=n;i++) p[i]=read();
for(int i=1;i<=n;i++) l[i]=read();
for(int i=1;i<=n;i++)
s.insert(node(i,0)),e.insert(node(i,-1));
while(m--) add();
while(k--)
{
int op=read(),x=read();
s.erase(node(x,c[x]));e.erase(node(x,c[x]-1));
ans-=g(x);l[x]+=(op==1)?1:-1;
s.insert(node(x,c[x]));e.insert(node(x,c[x]-1));
ans+=g(x);
while((--s.end())->o>(e.begin()->o)+eps) rem(),add();
printf("%.10Lf\n",ans);
}
}
Island Puzzle
题目描述
解法
话说这题关键方法差不多自己想清楚了,有一些细节和代码实现抄了 \(\tt xht\) 的。
首先考虑对操作的一些观察:首先操作具有可逆性,这说明调整法是可用的。此外由于 \(0\) 只有一个,我们可以把操作看成 \(0\) 的路径,正向\(/\)反向经过一个点的操作效果会抵消(经过环是同向所以具有不同的操作效果)
考虑树的情况,根据结论我们直接把 \(0\) 从初始位置 \(s\) 移动到目标位置 \(t\) 即可,可以先把它判掉。
再考虑如果给你一个环,怎么把初始状态变化到目标状态?首先我们把 \(0\) 的位置去掉,然后把剩下的点接成环,我们发现如果 \(0\) 逆时针走一圈回到原点,那么操作效果是剩下的点顺时针轮换一圈,所以求出圈数之后暴力轮换即可。
那么如何在原树找到这条环边呢?把 \(0\) 移动到 \(t\) 之后,我们考虑所有 \(a[i]\not=b[i]\) 的点 \(i\) 构成集合 \(S\),根据环的操作效果,\(S\) 和最低点的父亲 \(p\) 必然构成环,所以我们判据就是:\(p\) 是唯一的,并且 \(S\cup\{p\}\) 是一条路径。
然后我们先从 \(t\) 走到最低点 \(p\),然后用环的轮换方法处理,最后再从 \(p\) 走回 \(t\),需要判断可以轮换成目标权值。
接下来的问题就是最小步数了,首先轮换有顺时针和逆时针两种方法,都算出来取最小值即可。但是我们的步数可能不是最小的,因为我们的两部分路径可能还有重复的部分是可以省去的:
那么最后的代价表达式是(设 \(sz\) 为环的大小,\(c\) 表示轮换次数):
注意算代价时要区分顺逆时针,环应该是以 \(u_0\) 为起点排列的,这样逆时针移动才有顺时针轮换的效果。
#include <cstdio>
#include <vector>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 200005;
#define ll long long
#define pb push_back
#define mio {puts("-1");exit(0);}
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,s,t,lu,ld,a[M],b[M],v[M],d[M];
vector<int> A,B,h,o,g[M];
bool dfs1(int u,int fa)//find the path s->t
{
o.pb(u);
if(u==t) return 1;
for(int v:g[u]) if(v^fa)
if(dfs1(v,u)) return 1;
o.pop_back();
return 0;
}
void dfs2(int u,int fa,int d)//tag all the difference
{
if(a[u]!=b[u])
{
h.pb(u);
//maintain the shallow father
if(d==ld+1 && fa!=lu) mio
if(d<ld+1) ld=d-1,lu=fa;
}
for(int v:g[u]) if(v^fa)
dfs2(v,u,d+1);
}
void dfs3(int u,int fa)//range them in order
{
if(u!=lu && a[u]!=b[u]) A.pb(a[u]),B.pb(b[u]);
for(int v:g[u]) if(v^fa) dfs3(v,u);
}
int dfs4(int u,int fa,int d,int T)
{
if(u==T) return d;
for(int v:g[u]) if(v^fa)
{
int w=dfs4(v,u,d+1,T);
if(w) return w;
}
return 0;
}
int dis(int x,int y) {return dfs4(x,0,0,y);}
int calc()
{
int p=0,len=A.size();
for(int i=0;i<len;i++)
if(A[0]==B[i]) p=i;
if(!p) return 0;
for(int i=0;i<len;i++)
if(A[i]!=B[(i+p)%len]) return 0;
return p;
}
ll get(int x,int y)
{
ll c=calc();
return dis(s,x)+(c-1)*(A.size()+1)+1+dis(y,t);
}
signed main()
{
n=read();
for(int i=1;i<=n;i++) a[i]=read(),s=a[i]?s:i;
for(int i=1;i<=n;i++) b[i]=read(),t=b[i]?t:i;
for(int i=1;i<n;i++)
{
int u=read(),v=read();
g[u].pb(v),g[v].pb(u);
}
dfs1(s,0);int fl=1;
for(int i=0;i+1<o.size();i++)
swap(a[o[i]],a[o[i+1]]);
for(int i=1;i<=n;i++) fl&=a[i]==b[i];
if(fl) {printf("0 %d\n",o.size()-1);return 0;}
//find the edge (u_0,u_1)
vector<int> u;
ld=n;dfs2(t,0,0);h.pb(lu);
for(int x:h) v[x]=1;
for(int x:h) for(auto y:g[x])
if(v[y]) d[y]++;
for(auto x:h)
{
if(d[x]==1) u.pb(x);
else if(d[x]!=2) mio
}
if(u.size()!=2) mio
if(u[0]>u[1]) swap(u[0],u[1]);
dfs3(u[0],0);
if(!calc()) mio
ll ans=get(u[0],u[1]);
reverse(A.begin(),A.end());
reverse(B.begin(),B.end());
ans=min(ans,get(u[1],u[0]));
printf("%d %d %lld\n",u[0],u[1],ans);
}
Intergalaxy Trips
题目描述
解法
设 \(E(u)\) 表示从 \(u\) 走到 \(n\) 的最小天数,然后有是个人就会写的转移:
稍微解释一下就是 \(E(x)<E(v)\) 的 \(x\) 都不开放,\(v\) 才会有贡献,如果 \(E(v)<E(u)\) 的 \(v\) 都不开放,那么就只能走 \(E(u)\) 了(相当于走 \(u\) 的自环),但是这个转移的前提是知道 \(E(i)\) 的大小关系,否则甚至高斯消元都无法解决问题,那么我们从小到大来确定每个 \(E(i)\),一旦确定之后它的值就不会再改变了(因为自环的概率是 \(1\))
发现这个过程很像 \(\tt dijkstra\),我们先让 \(E(n)=0\),让后每次去找最小的 \(E(v)\),更新其他节点的 \(E(u)\) 即可。实现细节:分开维护等号右边的值和 \(\prod_{E(v)<E(u)}(1-p_{u,v})\) 会更好计算一些。时间复杂度 \(O(n^2)\)
如果你没有想到上面的方法怎么办?考虑随机一个 \(E\) 的大小关系序列,那么我们知道这个序列就可以 \(O(n^2)\) 转移了,并且如果大小关系序列不正确那么答案只会变大。
可以用随机求出的 \(E\) 的大小关系当成下一次的大小关系序列,一直这样循环到超时即可。虽然我不是很理解这种做法的正确性,但是在允许精度误差的本题上有比较优异的表现。
总结
转移顺序还是一如既往的重要,如果转移的顺序是值,并且值大的不会影响值小的。那么可以用类似 \(\tt dijkstra\) 的方法,每次取出最小值并且固定,考虑它对其他值的影响即可。
#include <cstdio>
const int M = 1005;
#define db double
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,vis[M];db g[M][M],E[M],p[M];
signed main()
{
n=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
g[i][j]=read()/100.0;
if(n==1) {puts("0");return 0;}
for(int i=1;i<n;i++)
E[i]=1,p[i]=1-g[i][n];
vis[n]=1;
while(1)
{
int u=0;db mn=1e18;
for(int i=1;i<=n;i++)
if(!vis[i] && E[i]/(1-p[i])<mn)
mn=E[i]/(1-p[i]),u=i;
vis[u]=1;
if(u==1) {printf("%.10f\n",mn);return 0;}
for(int i=1;i<=n;i++) if(!vis[i])
E[i]+=mn*g[i][u]*p[i],p[i]*=(1-g[i][u]);
}
puts("qwq");
}