0%

思路讲解(gemini翻译的题解)


首先,我们来解决一个简化版的问题,即假设数组 d 中全是 0(换句话-说,对于需要形成的排列 c 没有任何预设的限制)。

我们假设排列 a[1,2,3,4],排列 b[3,1,2,4]

假设我们已经选择了数组 c 的第一个元素为数组 a 的第一个元素(即 c[0] = a[0] = 1)。这样一来,我们就不能选择数组 b 的第一个元素了(b[0] = 3)。由于我们希望数组 c 是一个完整的排列,那么数字 3 就必须在 c 中出现,所以我们必须从数组 a 中找到 3 并将其加入到数组 c 中。

我们在数组 a 中寻找 3,发现它在索引 2 的位置(a[2]=3),于是我们选择 c[2] = a[2] = 3。这个选择导致我们不能再选择 b 中对应索引的元素(即 b[2]=2)。因此,我们又必须从 a 中找到 2 并加入 c。我们在 a 中找到 2 在索引 1 的位置(a[1]=2),所以选择 c[1] = a[1] = 2。这次,b 在该索引对应的元素是 1b[1]=1),而 1 已经因为我们最初的选择被包含在数组 c 中了。这样,我们就不再被强制要求从 a 中选择新的元素了,形成了一个闭环。

我们观察到,我们最初选择的元素 1,以及之后被“强制”选择的元素 23,即 [1,3,2](按选择顺序),与它们在 b 数组中对应位置的元素 [3,2,1] 构成了相同元素的排列。这些元素形成了一个“分组”。对于每个大小超过 1 的分组,我们都有 2 个选择:

  1. 将这个分组对应的所有元素都从 a 中选取。

  2. 将这个分组对应的所有元素都从 b 中选取。

所以,这个简化版问题的答案就是:找出所有这样相互关联的分组,统计其中大小超过 1 的分组数量(我们称之为 p),最终答案就是 2p。


那么,如果数组 d 不全是 0 该怎么办呢?

这种情况我们只需要在之前的逻辑上增加一个约束。对于我们找到的每一个分组,我们必须检查其所有成员在 d 数组中对应的位置是否全为 0

  • 如果一个分组对应的所有 d 数组位置上的值都是 0,那么这个分组就有两种选择(要么全选 a,要么全选 b),对最终答案的贡献是乘以 2

  • 如果一个分组中,哪怕只有一个成员在 d 数组中对应的位置不为 0(意味着这个位置的选择已经被 d 数组固定了),那么整个分组的选择也就被唯一确定了,它只有一种选择方案。这种分组对最终答案的贡献是乘以 1,我们就不需要把它计入 p 中。

这个解法可以通过多种方式实现,但在我看来,**使用并查集(Disjoint Set Union, DSU)**来将每个分组的元素合并在一起,是实现这个逻辑最优雅的方式。

AC代码

https://codeforces.com/contest/1670/submission/330958420

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

1009线段染色

2025“钉耙编程”中国大学生算法设计暑期联赛(3)

https://acm.hdu.edu.cn/contest/problem?cid=1174&pid=1009

https://vjudge.net/problem/HDU-8011

又是沟槽的概率题目。

感觉不是我能碰瓷的题目,之后有兴趣了再来看吧。

https://grok.com/chat/dc6a06d2-50d6-4bb8-a88b-87a1c553307d

题目大意

题意简述

在一个长度为 nn 的数轴上(包含整点 1n1 \sim n),给出了 mm 条线段,第 ii 条线段覆盖区间 [li,ri][l_i, r_i]

针对数轴上的每个整点 ii,进行一次独立的染色操作,染色的成功率为 pi=ai10p_i = \frac{a_i}{10}

定义一条线段被染色,当且仅当该线段覆盖的整点中至少有一个被染色。

所有给定的 mm 条线段都被染色的概率。

输入格式

第一行包含一个整数 tt,表示测试数据组数。

对于每组数据:

  1. 第一行两个整数 n,mn, m,分别表示数轴长度和线段数量。

  2. 第二行 nn 个整数 a1,a2,,ana_1, a_2, \dots, a_n,表示每个点的染色概率参数(pi=ai/10p_i = a_i/10)。

  3. 接下来 mm 行,每行两个整数 li,ril_i, r_i,表示一条线段的左右端点。

输出格式

输出一个整数,表示概率对 109+710^9+7 取模后的结果。

数据范围

  • 1t5×1041 \le t \le 5 \times 10^4

  • 1n2×105,0m2×1051 \le n \le 2 \times 10^5, 0 \le m \le 2 \times 10^5

  • n,m2.5×106\sum n, \sum m \le 2.5 \times 10^6

  • 0ai100 \le a_i \le 10

样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
6
4 2
5 5 5 5
2 3
3 4
4 2
5 5 5 5
2 2
3 4
1 0
3
4 1
0 2 3 4
2 4
4 2
5 0 5 5
1 3
2 4
4 3
1 1 1 0
2 4
3 4
4 4

样例输出

1
2
3
4
5
6
625000005
375000003
1
48000001
625000005
0

样例解释

第一组数据:

  • n=4n=4,所有点染色概率均为 0.50.5

  • 线段为 [2,3][2, 3][3,4][3, 4]

  • 要使两条线段都被染色,需满足:(点2被染 或 点3被染) 且 (点3被染 或 点4被染)。

  • 情况分类:

    1. 点3被染(概率 0.50.5):此时两条线段都已满足条件。
    2. 点3未被染(概率 0.50.5):此时必须点2被染(概率 0.50.5)且点4被染(概率 0.50.5),概率为 0.5×0.5×0.5=0.1250.5 \times 0.5 \times 0.5 = 0.125
  • 总概率 =0.5+0.125=0.625=58= 0.5 + 0.125 = 0.625 = \frac{5}{8}

  • 58(mod109+7)=625000005\frac{5}{8} \pmod{10^9+7} = 625000005

第二组数据:

  • 线段为 [2,2][2, 2][3,4][3, 4]

  • 线段1仅包含点2,必须点2被染(概率 0.50.5)。

  • 线段2包含点3, 4,需至少一个被染,概率为 1(10.5)×(10.5)=0.751 - (1-0.5)\times(1-0.5) = 0.75

  • 两者独立,总概率 =0.5×0.75=0.375=38= 0.5 \times 0.75 = 0.375 = \frac{3}{8}

思路讲解

我们重新回来看这道题目啊,重新回来看一下这道题目。

这 AI 提示二搁这乱写的,这数轴上的每一个点都是染色点,哪来的不染色点啊?那属于搞笑来了,卧槽。

OK,我大概看懂了这个 AI 提示3了。这 AI 提示2属于是哈哈哈省略的有些过多了。啊,如果你要看 AI 提示的话,可以直接看这个 AI 提示3。嗯。

image

应该是如上图这个意思啊,我们先尝试一下能不能实现一下。因为这个转移合法性是具有单调性的。就是说,如果说这个 LR 不符合要求的话,那么更小的 L 是更加不可能符合要求的。那么这个还是非常容易就可以找到的。

啊,然后你按照这个思路写了一个 DP 程序以后,现在问题就是答案到底是什么?我们会发现答案不是 DP[N] 啊,答案不是 DP[N] 那怎么答案从何而来?答案是什么?现在的问题是这个。

我们最大的问题是少考虑了虚拟起点0。说白了,它可以从 0 转移过来。还有一个地方就是它转移过来的中间那一段,它不能够被染色。因此需要乘上一个不被染色因数。说白了就是计算一下中间那一段它的这个乘积嘛。

dpi=pi×j=F(i)i1(dpj×k=j+1i1qk)dp_i = p_i \times \sum_{j=F(i)}^{i-1} (dp_j \times \prod_{k=j+1}^{i-1} q_k)

后面的这一坨乘积其实不好求啊,不是很好求,因为对于每一个 DP【 j 】来说,它的这个乘积的项数是不一样的。哎,不过我们可以采用类似于维护区间和区间修改的树状数组一样。这个可能说的有点抽象。

我们使用 iPad 画图来说明一下我们的这个意思。

image

说白了就是在线段树中的 j 存储不同的逆元值,存储的 DP j 乘以不同的逆元值,进而得到乘以相同的值,达到乘以不同值的这个效果。觉得我这个图还是画的比较清楚的。

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
vector<ll> pre_mul(N+2,1);
for (int i=1;i<=N;++i) {
ll l=0,r=i-1;
while (l<r) {
ll mid=l+r>>1;
if (check(mid,i)) {
r=mid;
}else {
l=mid+1;
}
}
// 比 EP 还小的话,那么这个 L 所能够得到的值,它一定是一个0。和 EP 相等是没事的。
// 因为说白了,我们是求一个后缀和嘛。如果这个点必定被染色,那么在其之前的点是不可能和后面的 DP 不重合的。
l=max(l,ep);
ll res=tr.query(1,l,i-1).sum;
res*=P[i]*inv10%mod;
res%=mod;
res*=pre_mul[i-1];
res%=mod;
if (i>=mxL) {
// 这里是,就是说明这个点以后的所有点必须都是是未染色的。这样子可以保证不同情况之间,它们的概率是没有交集的。
ans+=res*qsuf[i+1];
ans%=mod;
}
pre_mul[i]=pre_mul[i-1]*(10-P[i])%mod*inv10%mod;
if (pre_mul[i]==0) {
pre_mul[i]=1;
ep=i;
}
tr.assign(1,i,{0,0,res*binpow(pre_mul[i],mod-2)%mod});
}

AC 代码

AC
https://acm.hdu.edu.cn/contest/view-code?cid=1174&rid=28065

https://vjudge.net/solution/67894251

1008—01环

赛时最后靠AI找到了一个hack数据

1
2
3
4
5
6
hack:
1
10
1001010110

ans: 2

就是这个其他时候,交换都是单向的,只有一个选择,但是第一个,交换有两个选择,就都试一下。

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
ll cal(string &S,char ch){
string origin=S;
S+=S[0];
S+=S[1];
ll ans=0,res=INF;
char chh='0'+((ch-'0')^1);
FOR(i,0,N-1){
char tar= i%2==0?ch:chh;
if(S[i]==tar){
continue;
}
if(i==0){
if(S[N-1]==tar){
swap(S[0],S[N-1]);
S[N]=S[0];
++ans;
continue;
}
}
if(S[i+1]==tar){
swap(S[i],S[i+1]);
if(i==0){
swap(S[N],S[N+1]);
}
ans++;
}else{
S[i]='0'+((S[i]-'0')^1);
if(i==0){
S[N]=S[0];
}
ans++;
}
}
S=origin; // 跑第二遍
res=ans;ans=0;
FOR(i,0,N-1){
char tar= i%2==0?ch:chh;
if(S[i]==tar){
continue;
}
if(S[i+1]==tar){
swap(S[i],S[i+1]);
if(i==0){
swap(S[N],S[N+1]);
}
ans++;
}else{
S[i]='0'+((S[i]-'0')^1);
if(i==0){
S[N]=S[0];
}
ans++;
}
}
res=min(ans,res);
return res;
}

1002小抹爱锻炼

https://acm.hdu.edu.cn/contest/view-code?cid=1174&rid=817

赛时一发就过了。队友的思路,我的代码。

1012核心共振

切比雪夫距离与曼哈顿距离转化+前缀和+队友的公式。

因为有个地方溢出了,WA了一发,改了那个地方就A了。

1007性质不同的数字

其实还好,没有想象中那么神秘,那么这道题目其实是在问不同的区间覆盖情况有几种,那么实际上我们就差分(异或差分)+离散化就行了。

注意这里不能简单+1(下方的代码是对的)(简单的+1就是查lr[i].SE的idx,然后加的地方直接就是idx+1,这个是不行的,我们不能默认这两个区间是相交的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());
vll diff(SZ(li)+5,0);
FOR(i,1,N){
ll l=lower_bound(all(li),lr[i].FI)-li.begin();
ll r=lower_bound(all(li),lr[i].SE+1)-li.begin();
l++,r++;
ll rnd=rng();
diff[l]^=rnd;
diff[r]^=rnd;
}
vll Sum(SZ(li)+5,0);
FOR(i,1,SZ(li)+2){
Sum[i]=Sum[i-1]^diff[i];
}
set<ll> st;
FOR(i,1,SZ(li)+2){
st.insert(Sum[i]);
}
cout<<SZ(st)<<"\n";

1004三带一

sum为牌数总和,ans为答案,rem为剩余牌数sum为牌数总和,ans为答案,rem为剩余牌数

rem=sum3×ansrem=sum-3\times ans 那么剩余的牌的数量一定大于 remansrem\ge ans

tirem(ai3ti)    airem2tit_i\le rem-(a_i-3t_i)\ \ \Rightarrow \ \ a_i-rem\le 2t_i

那么第一个式子没什么难的,剩余的这个rem‘B’肯定要比你的三元组多呀。然后移项一下,这个式子摇身一变,变为了tit_i的下界了,这个好像也挺简单的~~(就是赛时想不到)~~。

然后向上取整除法的写法需要特判<0的情况,因为众所周知,C++的/是向0取整的,

1
2
3
4
5
inline ll chu(ll x){
if(x<0) return x/2;
if(x%2==0) return x/2;
return x/2+1;
}

check函数的写法,这个low怎么说呢,也没啥物理意义,就是纯数学推出来的式子,你就说是不是下界吧()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
auto check=[&](ll ans)->bool{
// 剩余多少个AAAB中的‘B’
ll rem=sum-3*ans;
if(rem<ans){ // 剩余的‘B’都不够,巧妇难为无米之炊
return false;
}
ll high=0,slow=0;
FOR(i,1,13){
ll lhigh=A[i]/3;
ll low=chu(A[i]-rem);
if(low>lhigh){
return false;
}
high+=lhigh;
slow+=max(0ll,low);
}
if(high>=ans && ans>=slow){
return true;
}
return false;
};

https://acm.hdu.edu.cn/contest/view-code?cid=1174&rid=22887

1009线段染色

https://acm.hdu.edu.cn/contest/problem?cid=1174&pid=1009

又是沟槽的概率题目。

感觉不是我能碰瓷的题目,之后有兴趣了再来看吧。

https://grok.com/chat/dc6a06d2-50d6-4bb8-a88b-87a1c553307d