郑州寄 8.8
前言
业精于勤荒于嬉,行成于思毁于随
正文(模拟赛)
卦象:平
感受:T1 口胡,花费 3min,代码写了 40min,难受。写完 T1 对着 T2 发了 20min 的呆,感觉正解需要整点数据结构,但没想明白怎么维护。所以只打了 \(80pts\),大概在 10:00 左右写完 T2。开 T3、T4,显然是不会 T3 正解,很像以前的一道拉插题目,但是拉插早就还给 shr 了,看来需要加训。不过那个拉插题目也有朴素 DP 做法,然而这个模拟赛题没想清楚。试图开 T4,以为是 DAG 上背包的板子题,没曾想,背包容量高达 \(10^9\)。这就很像高效进阶上的深搜题了,再结合 \(n \le 30\),hhh,这辈子不可能写这种东西的(甚至还是多重背包)。因此,回去看 T3,还是不会。被迫去想 T2 正解,不会不会不会,啥都不会。此时云落就像那个 oi 重开模拟器里的红温和破防 buff,啥也不会,咋办???再加上今天这个空调开的忽冷忽热的,写一会代码就想去厕所,而一去厕所又会受到一套魔法伤害,人麻了。T3 和 T4 的部分分还是太有生活了,我都需要值域相关的部分分,结果部分分设计全是 \(n \le ???\) 的形式,难绷。哎,今天又双叒叕是补题压力巨大的一天。明明感觉就是 CSP-S 难度的模拟赛,为啥就是不会呢……
T1
签,因码字速度慢耗费将近一个点
题意
有一个 \(n\) 行 \(m\) 列的网格。每个网格上已经有一个人,或者是空的,没有人。且保证至少还有一个位置没有人。现在梅需要选择一个空位,并且移动到那个空位上。
梅落座后,死灵法师会随机让一个人在座的人(包括梅)感染瘟疫,在接下来的每一天中,感染瘟疫的人会向四联通的人传染瘟疫(即向上,下,左,右与其直接相连的四个人传染瘟疫)
梅想要最小化自己感染瘟疫的概率,请告诉她如果她以最优的方式选择空位,感染瘟疫的概率最小是多少
题解
简单维护连通块大小,难度橙
代码
不贴了
T2
想到了一些比较简单 \(80pts\) 做法,也猜到了调和级数复杂度,但没想到暴力跳跃就是正解,真是(消音)的菜完了
题意
数轴上分布着位置两两不同的 \(n\) 个点,第 \(i\) 个点的位置是 \(a_i\)。
定义共振效果:如果一个点被标记,那么距离 \(k\) 以内的所有点都会在这一秒被标记(共振无法触发连锁反应)
你可以标记任意一个点,接下来引发共振效果。对于所有标记初始点的方案,标记所有点的时间的最小值为答案
你需要对所有的 \(1 \le k \le 10^6\) 求出答案
题解
结论:从最左端的点开始向后跳跃,跳跃步数折半再上取整就是答案
考虑预处理 ans[k],显然可以暴力跳跃,时间复杂度是调和级数的
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m,a[N],nxt[N],ans[N];
int main(){
freopen("beacon.in","r",stdin);
freopen("beacon.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
int l=N,r=0;
for(int i=1,x;i<=n;i++){
cin>>x;nxt[x]=x;a[i]=x;
l=min(l,x),r=max(r,x);
}
int mx=0;
for(int i=2;i<=n;i++)mx=max(mx,a[i]-a[i-1]);
for(int i=l;i<=r;i++)
if(!nxt[i])nxt[i]=nxt[i-1];
for(int x=mx;x<N;x++){
int i=l;
while(i<r){
i=nxt[min(i+x,r)];
ans[x]++;
}
}
for(int i=1,x;i<=m;i++){
cin>>x;
if(x<mx)cout<<-1<<' ';
else cout<<(ans[x]+1)/2<<' ';
}
cout<<'\n';
return 0;
}
T3
老老实实学 划艇 的朴素 DP 做法
题意

题解
可恶的实数,我们需要几何概型!
容易发现所有可能的区间,与目标区间是两个相同的子问题,所以做完分母做分子就行了
然后容易想到把区间端点离散化,第 \(x\) 小的记为 \(b_x\),跑 DP
而 DP 是简单的,记 \(f_{x,i}\) 表示前 \(x\) 个区间扔了 \(i\) 个点的方案数(几何意义下的)
刷表转移
首先区间 \([b_x,b_{x+1}]\) 如果不放,那么有
否则,枚举放了 \(j-i\) 个点,那么有
解释一下,分子上面乘的系数表示可以在区间内随机撒点,而分母则是定序,因为在所有的随机撒点方案中只有一种是符合大小关系的
代码
记得初始化!记得清空!!!
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=505,MOD=1e9+7;
int n,d,a[N],l[N],r[N];
int fac[N],ifac[N];
int b[N<<1],c,f[N<<1][N],p,q;
inline void chkmx(int &x,int y){x=max(x,y);return;}
inline void chkmn(int &x,int y){x=min(x,y);return;}
inline void ADD(int &x,int y){
x%=MOD;y%=MOD;x=(x+y)%MOD;
return;
}
inline int qpow(int a,int b){
int res=1;
while(b){
if(b&1)res=res*a%MOD;
a=a*a%MOD;b>>=1;
}
return res;
}
inline int work(){
memset(b,0,sizeof(b));c=0;
memset(f,0,sizeof(f));
for(int i=1;i<=n;i++)b[++c]=l[i],b[++c]=r[i];
sort(b+1,b+c+1);c=unique(b+1,b+c+1)-b-1;
f[1][0]=1;
for(int x=1;x<=c-1;x++)
for(int i=0;i<=n;i++){
ADD(f[x+1][i],f[x][i]);
int pw=1,len=b[x+1]-b[x];
for(int j=i+1;j<=n;j++){
if(b[x]<l[j]||b[x+1]>r[j])break;
pw=(pw*len)%MOD;
ADD(f[x+1][j],f[x][i]*(pw*ifac[j-i]%MOD)%MOD);
}
}
return f[c][n];
}
inline void init(){
fac[0]=1;
for(int i=1;i<N;i++)fac[i]=(fac[i-1]*i)%MOD;
ifac[0]=1;
for(int i=1;i<N;i++)ifac[i]=qpow(fac[i],MOD-2)%MOD;
return;
}
signed main(){
freopen("wizard.in","r",stdin);
freopen("wizard.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
init();
// for(int i=1;i<=10;i++)cerr<<fac[i]<<' '<<ifac[i]<<endl;
cin>>n>>d;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)cin>>l[i]>>r[i];
q=work();
for(int i=1;i<=n;i++)chkmx(l[i],a[i]-d),chkmn(r[i],a[i]+d);
p=work();
// cerr<<p<<' '<<q<<endl;
cout<<p*qpow(q,MOD-2)%MOD<<'\n';
return 0;
}
T4
可恶的神秘复杂度题
题意
给定一个无重边无自环 DAG,结点 \(i\) 上存在 \(k_i\) 个价值为 \(v_i\),重量为 \(w_i\) 的物品,求在所有 \(1 \to n\) 的路径方案中,在背包容量 \(r\) 范围内获得物品价值最大的,输出最大价值,不可达则 \(-1\)
注意看数据范围!!!
\(n \le 30 ,\ m \le \frac{n(n+1)}{2} ,\ 1 \le r \le 10^9 ,\ 1 \le w_i \le 5 ,\ 1 \le v_i,k_i \le 10^9\)
题解
结论:对于物品 \(i\),只需要保留 \(w_i\) 个参与背包计算,剩余的部分按照性价比(即 \(\frac{v_i}{w_i}\))排序后贪心即可
证明先挖个坑,以后填
对于链的部分分,可以直接按照上面的做,时间复杂度 \(O(n^2 \times c^2)\)
而在 DAG 上,我们容易想到枚举经过点的子集,时间复杂度 \(O(\text{不能过})\)
然后我们发现,如果子集之间存在真包含关系,那么集合大小更、较大的在答案上的贡献一定不劣于集合大小较小的
进一步地,我们可以进行一个“反向松弛”的操作
对于一条边 \((u,v)\),如果存在一条不经过 \((u,v)\) 的 \(u \to v\) 的路径,那么 \((u,v)\) 则是可以删除的。删除该边的依据就是上面子集包含关系的分析
然后,题解表示,剪枝后的图可以通过
(具体地,看 这里 叭!)
代码
这就是输出 \(-1\) 的魅力吗?
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define vi vector<int>
#define pb push_back
#define ppb pop_back
using namespace std;
const int N=35,M=1e3+5;
int n,m,r;bool G[N][N],vis[N];
struct node{int w,v,k;}a[N];
int head[N],tot;
struct Edge{int to,nxt;}e[N*N];
vi o,p;int f[M],ans=-1;
inline void add(int u,int v){
e[++tot]={v,head[u]};head[u]=tot;
return;
}
inline void judge(int S,int T){
for(int i=1;i<=n;i++)vis[i]=false;
G[S][T]=false;
queue<int> q;q.push(S);vis[S]=true;
while(!q.empty()){
int u=q.front();q.pop();
for(int v=1;v<=n;v++){
if(!G[u][v]||vis[v])continue;
q.push(v);vis[v]=true;
}
}
G[S][T]=true;
if(!vis[T])add(S,T);
return;
}
inline bool cmp(int x,int y){
return a[x].v*a[y].w>a[y].v*a[x].w;
}
inline void cal(){
p=o;sort(p.begin(),p.end(),cmp);
memset(f,0,sizeof(f));
// DP
int sum=0;
for(int i:p){
int t=min(a[i].k,5ll);
sum+=t*a[i].w;
for(int j=sum;j>=0;j--)
for(int k=1;k<=t&&k*a[i].w<=j;k++)
f[j]=max(f[j],f[j-k*a[i].w]+k*a[i].v);
}
// Greed
for(int j=0;j<=min(sum,r);j++){
int rem=r-j,res=f[j];
for(int i:p){
int k=min(max(a[i].k-5ll,0ll),rem/a[i].w);
rem-=k*a[i].w,res+=k*a[i].v;
}
ans=max(ans,res);
}
return;
}
inline void dfs(int u){
o.pb(u);
if(u==n)cal();
for(int i=head[u];i;i=e[i].nxt)dfs(e[i].to);
o.ppb();
return;
}
signed main(){
freopen("rider.in","r",stdin);
freopen("rider.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m>>r;
for(int i=1;i<=n;i++)cin>>a[i].w>>a[i].v>>a[i].k;
for(int i=1,u,v;i<=m;i++)cin>>u>>v,G[u][v]=true;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(G[i][j])judge(i,j);
dfs(1);
cout<<ans<<'\n';
return 0;
}
小结
正文(加餐)
线段树 \(+\) 离线算法(不是哥们?)选讲,难绷
关键词:势能分析、标记下放、区间合并、分治
题呢?题呢?题呢?
一下午的线段树,标记下放;一晚上的 \(k\) 维偏序、离线扫描线
后记
世界孤立我任它奚落
完结撒花!

浙公网安备 33010602011771号