0%

思路讲解

在竞技编程中,对于求解概率或期望值的问题,通常可以运用以下技巧:与其求某个最大值与某个特定值重合的概率,不如求该最大值小于或等于该特定值的概率。这个技巧也适用于这个问题:

说实话,这个我确实想到了,但是这个想法被我pass了。其实不应该忽视求什么比较容易的想法的,或者说想到了一些可以求的量,应该去想怎么样。

那么注意到那,我们可以先求概率,接着再求期望,那么P(K)表示≤k最大值小于等于K的概率

image

image

这个就是Pk-1和Pk的关系,那么我们现在的重点就是说我们如何快速计算这个,快速完成递推那?

那么这个办法就有很多,我选择是使用优先队列。

AC代码

https://atcoder.jp/contests/abc411/submissions/66998392

心路历程(WA,TLE,MLE……)

没有提交之前WA了几次,其实是因为溢出了,那么我们的binpow模版需要在进入前就对a进行取模。

1
2
3
4
5
6
7
8
9
10
ll binpow(ll a,ll k,const ll &p){	// 迭代快速幂
ll res=1;
a%=mod;
while(k>0){
if((k&1)==1) res=a*res%p;
a=a*a%p;
k>>=1;
}
return res;
}

思路讲解

题面总结

给定一个长度为 N 的数组 A,其中 A[i] ∈ [1, K]。需要构造一个排列 B(1 到 N 的排列),使得对于所有 i ∈ [1, K],满足 A[B[i]] = i。

对于每个起始位置 i(1 ≤ i ≤ N),求出从位置 i 开始构造满足条件的排列 B 时,相邻元素差值绝对值之和的最小值。

换句话说,要最小化 ∑|B[j] - B[j-1]|,其中 B[1] = i,且 A[B[j]] = j。

输入格式:

  • 第一行:测试用例数 T

  • 每个测试用例:

    • 第一行:N 和 K
    • 第二行:N 个整数表示数组 A

输出格式:

对于每个测试用例,输出 N 个整数,第 i 个整数表示从位置 i 开始的最小代价。

约束条件:

  • 1 ≤ T ≤ 10³

  • 1 ≤ K ≤ N ≤ 10⁶

  • 1 ≤ A[i] ≤ K

  • 保证至少存在一个满足条件的排列 B

虽然不一定选择最靠近的,但是一定是选择右端最靠近或者左端最靠近点的一侧。

这个如何证明那?

那么我们感性的理解,如果在最优序列中,再次移动需要到 j 的各种情况。

从图中可以看出,l‘不优,即便是在 j 位于l’ 与 x之间的时候

那么我们发现,这个 j 落在外面,l,r 中还是必有一个最优,落在里面那更不必多说了。

okay,那我们也算是成功减小了范围,但是还是不知道怎么做呀?继续读题解

啊哈,那么我们发现那,这其实是一个前缀dp,即状态转移只和前面一个状态相关,那么前面一个状态里只有 l 和 r,那么答案就呼之欲出了,从前面这个来转移。

那么我们用记忆化搜索,可能这个让人更好理解一点。

那么这个正难则反思想是应用于dp的,但是这个dp也有点难写,过了就行,开摆。

AC代码

https://www.codechef.com/viewsolution/1167791134

心路历程(WA,TLE,MLE……)

调试的时候发现这里写错了,应该写1,我写成2了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FOR(i,1,N){
auto it=lower_bound(all(g[1]),i);
ll ans=INF;
if(it!=g[1].begin()){
auto it_=prev(it);
ll diff=abs(*it_-i);
ll res=dfs(dfs,*it_,1);
ans=min(diff+res,ans);
}
if(it!=g[1].end()){
ll diff=abs(*it-i);
ll res=dfs(dfs,*it,1);
ans=min(diff+res,ans);
}
cout<<ans<<" ";
}

思路讲解

思路就是根号分治

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
FOR(_,1,Q){
ll op,a,b,c;
cin>>op;
if(op==0){
cin>>a>>b>>c;
ll ida=upper_bound(L+1,L+1+idx,a)-L;
ida-=1;
ll idb=upper_bound(L+1,L+1+idx,b)-L;
idb-=1;
ll ans=0;
FOR(i,0,SZ(g[ida])-1){
if(g[ida][i][1]>=a && g[ida][i][1]<=b && g[ida][i][0]>=c){
++ans;
}
}
if(ida!=idb){ // 注意这个地方,小心相同重复统计导致WA
FOR(i,0,SZ(g[idb])-1){
if(g[idb][i][1]>=a && g[idb][i][1]<=b && g[idb][i][0]>=c){
++ans;
}
}
}
FOR(i,ida+1,idb-1){
ll id=lower_bound(all(g[i]),(arr2){c,0})-g[i].begin();
ans+=SZ(g[i])-id;
}
cout<<ans<<"\n";
}else{
cin>>a>>b;
ll id=upper_bound(L+1,L+1+idx,a)-L;
id-=1;
FOR(i,0,SZ(g[id])-1){
if(g[id][i][1]==a){
g[id][i][0]=b;
break;
}
}
sort(all(g[id]));
}
}

如果问小于等于c的可以这么写。

1
2
3
4
FOR(i,ida+1,idb-1){
ll id=upper_bound(all(g[i]),(arr2){c,INF})-g[i].begin();
ans+=id;
}

AC代码

https://www.luogu.com.cn/record/220963190

https://www.luogu.com.cn/record/220973015 双倍经验,SP3261 RACETIME - Race Against Time

https://www.luogu.com.cn/record/220977711 三倍经验,UVA12003 Array Transformer

心路历程(WA,TLE,MLE……)

思路讲解

一般来说,这种绿题图论建模都是有套路的,一般来说就是在一个联通图内匹配符合一些性质(可能匹配数量直接和V和E相关,也有可能跑一个dfs就出来了)。

我仔细看了一下,感觉可以转化为无向图边的重定向问题 CF-1021-D. Baggage Claim

这些是整段代码最重要的部分。其实这部分成立的原因是该子图是联通的(虽然是废话)。

那么我们可以想到一种构造方式,就是从*点(完美匹配点,若没有就从双边点向外延伸,若还没有就无所谓了)出发,然后定向,形成像树一样的形状。

image

1
2
3
4
5
ll size=fa.size(i);
ll edge=fa.edge[fa.find(i)];
ll mcc=fa.mcc[fa.find(i)];
// 现在这里的ans意思是我可以白嫖多少元素
ans+=2*mcc+min(edge,size-mcc);

AC代码

https://www.codechef.com/viewsolution/1167249816

心路历程(WA,TLE,MLE……)

WA https://www.codechef.com/viewsolution/1167153030

就是贪心做法,优先处理比较独特的。

显然会被相等的一些hack数据hack

1
2
3
4
5
1
3
1 3 2 3 1 2

3

思路讲解

hard版,这个题解中的每句话都值得细细品味。至少每个段落都得看,都得学。

https://www.codechef.com/problems/GRIDPATHHD?tab=solution

AC代码

https://www.codechef.com/viewsolution/1167145951

https://www.codechef.com/viewsolution/1167615239

心路历程(WA,TLE,MLE……)

EZ不小心错了一次,实际上是INF开小了。

第186行这里容易符号写反。

https://www.codechef.com/viewsolution/1167612305

1
if(mid!=N+1-cnt[2])res=tr[1].findk(mid)-mid-tr[2].findk(N+2-mid)+(N+2-mid);